class AsyncThreadController(AsyncAbstractController): def __init__(self, # |parent| parent=None): super(AsyncThreadController, self).__init__(parent) # Create a worker and a thread it runs in. This approach was # inspired by example given in the `QThread docs # <http://qt-project.org/doc/qt-4.8/qthread.html>`_. self._worker = _AsyncWorker() self._workerThread = QThread(parent) # Attach the worker to the thread's event queue. self._worker.moveToThread(self._workerThread) # Hook up signals. self._worker.startSignal.connect(self._worker.onStart) # Everything is ready. Start the worker thread, so it will # be ready for functions to run. self._workerThread.start() # |_start| def _start(self, future): self._worker.startSignal.emit(future) # |terminate| def _terminate(self): # Shut down the thread the Worker runs in. self._workerThread.quit() self._workerThread.wait() # Finally, detach (and probably garbage collect) the objects # used by this class. del self._worker del self._workerThread
class AsyncThreadController(_AsyncAbstractController): def __init__(self, # See parent_. parent=None): super().__init__(parent) # Create a worker and a thread it runs in. This approach was # inspired by the example given in the QThread_ docs. self._worker = _AsyncWorker() self._workerThread = QThread(self) # Attach the worker to the thread's event queue. self._worker.moveToThread(self._workerThread) # Hook up signals. self._worker.startSignal.connect(self._worker.onStart) # Everything is ready. Start the worker thread, so it will # be ready for functions to run. self._workerThread.start() # See `_start`_. def _start(self, future): self._worker.startSignal.emit(future) # See `_terminate`_. def _terminate(self): # Shut down the thread the worker runs in. self._workerThread.quit() self._workerThread.wait() # Delete the worker, since it doesn't have a parent and therefore # won't be deleted by the Qt object tree. sip.delete(self._worker) del self._worker
class MatcherUI(QDialog): thread_invoker = pyqtSignal() def __init__(self, argv, terminate=False): super(MatcherUI, self).__init__() self.btnStop = QPushButton('Stop') self.image_pane = QLabel() self.progress = QProgressBar(self) self.layout = QVBoxLayout() self.layout.addWidget(self.btnStop) self.layout.addWidget(self.progress) self.layout.addWidget(self.image_pane) self.setLayout(self.layout) self.thread = QThread() self.thread.start() self.worker = Worker(argv) self.worker.moveToThread(self.thread) self.thread_invoker.connect(self.worker.task) self.thread_invoker.emit() self.worker.frameRendered.connect(self.updatePixmap) self.worker.finished.connect(self.finishIt) self.worker.progress.connect(self.progress.setValue) self.terminate = terminate self.btnStop.clicked.connect(lambda: self.worker.stop()) self.btnStop.clicked.connect(self.terminateIt) self.terminated = False def updatePixmap(self, image): #it is called only when the pixmap is really updated by the thread. #resize image in advance. #w,h = image.width(), image.height() #scaled_image = image.scaled(int(w*self.preview_ratio), int(h*self.preview_ratio)) pixmap = QPixmap.fromImage(image) self.image_pane.setPixmap(pixmap) #is it ok here? self.update() def terminateIt(self): self.close() if self.terminate: sys.exit(1) #terminated self.terminated = True def finishIt(self): self.close() def closeEvent(self, event): self.stop_thread() def stop_thread(self): self.worker.stop() self.thread.quit() self.thread.wait()
class MetaDataCollector(QObject): sig_metadata_ready = pyqtSignal(Location, dict) sig_request_metadata = pyqtSignal(Location) sig_delete_metadatas = pyqtSignal(list) def __init__(self, vfs: StdioFilesystem) -> None: super().__init__() self._worker = MetaDataCollectorWorker(vfs) self._thread = QThread() self._worker.moveToThread(self._thread) self._thread.started.connect(self._worker.init) self.sig_request_metadata.connect(self._worker.on_metadata_requested) self.sig_delete_metadatas.connect(self._worker.on_delete_metadata_requested) self._worker.sig_metadata_ready.connect(self._on_metadata_ready) self._thread.start() def close(self): self._worker._close = True self._thread.quit() self._thread.wait() def request_metadata(self, location: Location) -> None: self.sig_request_metadata.emit(location) def request_delete_metadatas(self, locations: List[Location]) -> None: self.sig_delete_metadatas.emit(locations) def _on_metadata_ready(self, location: Location, metadata: Any): self.sig_metadata_ready.emit(location, metadata)
class MeasurementThread(QObject): specSignal = pyqtSignal(np.ndarray) progressSignal = pyqtSignal(float, str) finishSignal = pyqtSignal(np.ndarray) def __init__(self, spectrometer, parent=None): if getattr(self.__class__, '_has_instance', False): RuntimeError('Cannot create another instance') self.__class__._has_instance = True try: super(MeasurementThread, self).__init__(parent) self.spectrometer = spectrometer self.abort = False self.thread = QThread(parent) self.moveToThread(self.thread) self.thread.started.connect(self.process) self.thread.finished.connect(self.stop) except: (type, value, traceback) = sys.exc_info() sys.excepthook(type, value, traceback) def start(self): self.thread.start(QThread.HighPriority) @pyqtSlot() def stop(self): self.abort = True self.thread.quit() self.thread.wait(5000) def __del__(self): self.__class__.has_instance = False #try: # self.specSignal.disconnect() # self.progressSignal.disconnect() # self.finishSignal.disconnect() #except: # (type, value, traceback) = sys.exc_info() # sys.excepthook(type, value, traceback) def work(self): self.specSignal.emit(self.spec) @pyqtSlot() def process(self): while not self.abort: try: self.spec = self.spectrometer.intensities() self.spec = self.spec[0:1024] self.work() except: (type, value, traceback) = sys.exc_info() print(type) print(value) print(traceback) sys.excepthook(type, value, traceback)
class HistogramWidget(QWidget): def __init__(self, parent=None): super(HistogramWidget, self).__init__(parent) self.m_levels = 128 self.m_isBusy = False self.m_histogram = [] self.m_processor = FrameProcessor() self.m_processorThread = QThread() self.m_processor.moveToThread(self.m_processorThread) self.m_processor.histogramReady.connect(self.setHistogram) def __del__(self): self.m_processorThread.quit() self.m_processorThread.wait(10000) def setLevels(self, levels): self.m_levels = levels def processFrame(self, frame): print("In processFrame()") if self.m_isBusy: return self.m_isBusy = True print("Invoking method") QMetaObject.invokeMethod(self.m_processor, 'processFrame', Qt.QueuedConnection, Q_ARG(QVideoFrame, frame), Q_ARG(int, self.m_levels)) @pyqtSlot(list) def setHistogram(self, histogram): self.m_isBusy = False self.m_histogram = list(histogram) self.update() def paintEvent(self, event): painter = QPainter(self) if len(self.m_histogram) == 0: painter.fillRect(0, 0, self.width(), self.height(), QColor.fromRgb(0, 0, 0)) return barWidth = self.width() / float(len(self.m_histogram)) for i, value in enumerate(self.m_histogram): h = value * height() # Draw the level. painter.fillRect(barWidth * i, height() - h, barWidth * (i + 1), height(), Qt.red) # Clear the rest of the control. painter.fillRect(barWidth * i, 0, barWidth * (i + 1), height() - h, Qt.black)
class WorkerThread(QObject): sig_close_requested = pyqtSignal() def __init__(self) -> None: super().__init__() self._worker: Optional[Worker] = None self._thread: Optional[QThread] = None def set_worker(self, worker: Worker): """Sets the Worker associated with this thread. Note this function has to be called before connecting any signals, otherwise the signals won't be associated with the right thread. """ assert self._worker is None self._worker = worker self._thread = QThread() self._worker.moveToThread(self._thread) self._thread.started.connect(self._worker.on_thread_started) # close() is a blocking connection so the thread is properly # done after the signal was emit'ed and we don't have to fuss # around with sig_finished() and other stuff self.sig_close_requested.connect(self._worker.close, type=Qt.BlockingQueuedConnection) def start(self) -> None: assert self._worker is not None assert self._thread is not None self._thread.start() def is_running(self) -> bool: assert self._thread is not None return bool(self._thread.isRunning()) def close(self) -> None: assert self._thread is not None assert self._worker is not None assert self._worker._close is False, "WorkerThread.close() was called twice" self._worker._close = True self.sig_close_requested.emit() self._thread.quit() self._thread.wait()
class SearchStream(QObject): sig_close_requested = pyqtSignal() def __init__(self, abspath: str, pattern: str) -> None: super().__init__() self._worker = SearchStreamWorker(abspath, pattern) self._thread = QThread(self) self._worker.moveToThread(self._thread) self._thread.started.connect(self._worker.init) # close() is a blocking connection so the thread is properly # done after the signal was emit'ed and we don't have to fuss # around with sig_finished() and other stuff self.sig_close_requested.connect(self._worker.close, type=Qt.BlockingQueuedConnection) def start(self) -> None: self._thread.start() def close(self) -> None: assert self._worker._close is False self._worker._close = True self.sig_close_requested.emit() self._thread.quit() self._thread.wait() @property def sig_file_added(self): return self._worker.sig_file_added @property def sig_finished(self): return self._worker.sig_finished @property def sig_message(self): return self._worker.sig_message @property def sig_error(self): return self._worker.sig_error
class MainWindow(QtWidgets.QMainWindow): def __init__(self): super().__init__() self.showMaximized() self.setWindowTitle("EVGP Race Control System") self.is_server_started = False teams_list_file_path = "racers_list.yaml" if not os.path.exists(teams_list_file_path): logging.warning("No racers_list.yaml found!") msgBox = QMessageBox() msgBox.setIcon(QMessageBox.Warning) msgBox.setText( "No racers_list.yaml found.\nPress Open and use the file explorer to select the racer list YAML file" ) msgBox.setWindowTitle("No racers_list.yaml found!") msgBox.setStandardButtons(QMessageBox.Open) returnValue = msgBox.exec() if returnValue == QMessageBox.Open: teams_list_file_path = QFileDialog.getOpenFileName( self, 'Open Racer List File', os.getcwd(), "Yaml files (*.yaml)")[0] self.model = RCSModel(teams_list_file_path) layout = QGridLayout() layout.setColumnStretch(0, 10) layout.setColumnStretch(1, 10) #FILTERED ACTIVE RACE TABLE self.activeRaceProxyModel = RCSSortFilterProxyModel(True) self.activeRaceProxyModel.setDynamicSortFilter(True) self.activeRaceProxyModel.setSourceModel(self.model) self.activeRaceTable = QtWidgets.QTableView() self.activeRaceTable.setModel(self.activeRaceProxyModel) self.activeRaceTable.setSizeAdjustPolicy( QtWidgets.QAbstractScrollArea.AdjustToContents) self.activeRaceTable.horizontalHeader().setStretchLastSection(True) self.activeRaceTable.horizontalHeader().setSectionResizeMode( QtWidgets.QHeaderView.ResizeToContents) self.activeRaceTable.setSelectionBehavior( QtWidgets.QTableView.SelectRows) self.activeRaceTable.setSelectionMode( QtWidgets.QTableView.SingleSelection) layout.addWidget(self.activeRaceTable, 1, 0) self.standbyRaceProxyModel = RCSSortFilterProxyModel(False) self.standbyRaceProxyModel.setDynamicSortFilter(True) self.standbyRaceProxyModel.setSourceModel(self.model) self.standbyRaceTable = QtWidgets.QTableView() self.standbyRaceTable.setModel(self.standbyRaceProxyModel) self.standbyRaceTable.setSizeAdjustPolicy( QtWidgets.QAbstractScrollArea.AdjustToContents) self.standbyRaceTable.horizontalHeader().setSectionResizeMode( QtWidgets.QHeaderView.ResizeToContents) self.standbyRaceTable.horizontalHeader().setStretchLastSection(True) self.standbyRaceTable.setSelectionBehavior( QtWidgets.QTableView.SelectRows) self.standbyRaceTable.setSelectionMode( QtWidgets.QTableView.SingleSelection) layout.addWidget(self.standbyRaceTable, 1, 1) self.activeRaceTableLabel = QLabel("Active Race Table") self.activeRaceTableLabel.setAlignment(Qt.AlignCenter) self.standbyRaceTableLabel = QLabel("Other Teams Table") self.standbyRaceTableLabel.setAlignment(Qt.AlignCenter) layout.addWidget(self.activeRaceTableLabel, 0, 0) layout.addWidget(self.standbyRaceTableLabel, 0, 1) self.standbyRaceTable.setSortingEnabled(True) self.standbyRaceTable.sortByColumn(1, Qt.AscendingOrder) self.activeRaceTable.setSortingEnabled(True) self.activeRaceTable.sortByColumn(1, Qt.AscendingOrder) self.selectedIndex = None self.standbyRaceTable.selectionModel().selectionChanged.connect( self.standby_race_table_selection_handler) self.activeRaceTable.selectionModel().selectionChanged.connect( self.active_race_table_selection_handler) self.horizontalGroupBox = QGroupBox("") self.horizontalGroupBox.setLayout(layout) # All relevant buttons in sidebar self.button_sidebar_vBox = QVBoxLayout() layout.addLayout(self.button_sidebar_vBox, 1, 3) self.button_sidebar_vBox.setAlignment(Qt.AlignTop) # Move Racers Buttons self.button_container_stylesheet = "QWidget#ButtonContainer{background-color: rgb(200, 200, 200);\n border-radius: 5;\n}" move_racers_button_container = QWidget() move_racers_button_container.setObjectName("ButtonContainer") move_racers_button_container.setStyleSheet( self.button_container_stylesheet) move_racers_button_container.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum) move_racers_layout = QVBoxLayout(move_racers_button_container) move_racers_layout.addWidget(QLabel("ADMIN CONTROLS")) move_to_active_race_button = QPushButton("Move to Active Race") move_to_active_race_button.clicked.connect(self.move_to_active_race) move_racers_layout.addWidget(move_to_active_race_button) remove_from_active_race_button = QPushButton("Remove from Active Race") remove_from_active_race_button.clicked.connect( self.remove_from_active_race) move_racers_layout.addWidget(remove_from_active_race_button) self.button_sidebar_vBox.addWidget(move_racers_button_container) # Team State Control Buttons team_state_button_container = QWidget() team_state_button_container.setObjectName("ButtonContainer") team_state_button_container.setStyleSheet( self.button_container_stylesheet) team_state_button_container.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum) team_state_btns = self.create_team_state_buttons() team_state_layout = QVBoxLayout(team_state_button_container) team_state_layout.addWidget(QLabel("TEAM CONTROLS")) for btn in team_state_btns: team_state_layout.addWidget(btn) self.button_sidebar_vBox.addWidget(team_state_button_container) # Race State Control Buttons race_state_button_container = QWidget() race_state_button_container.setObjectName("ButtonContainer") race_state_button_container.setStyleSheet( self.button_container_stylesheet) race_state_button_container.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum) race_state_btns = self.create_race_state_buttons() race_state_layout = QVBoxLayout(race_state_button_container) race_state_layout.addWidget(QLabel("RACE CONTROLS")) for btn in race_state_btns: race_state_layout.addWidget(btn) self.button_sidebar_vBox.addWidget(race_state_button_container) self.info_group_box = QGroupBox("Race Status Information") self.race_state_label = QLabel("Race State: IN_GARAGE") self.info_label = QLabel("Race Status: No Race running.") info_layout = QVBoxLayout() info_layout.addWidget(self.race_state_label) info_layout.addWidget(self.info_label) self.info_group_box.setLayout(info_layout) layout.addWidget(self.info_group_box, 3, 0) self.buttonController = ButtonStateController( race_state_btns[0], race_state_btns[1], race_state_btns[2], race_state_btns[3], race_state_btns[4], team_state_btns[0], move_to_active_race_button, remove_from_active_race_button, self.race_state_label, self.info_label) self.model.race_state_change_signal.connect( self.buttonController.race_state_updated) verticalSpacer = QtWidgets.QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) layout.addItem(verticalSpacer, 4, 0, rowSpan=1, columnSpan=3) self.create_menu_bar() # wait for start of server self.server_wait_label = QLabel( "Waiting for TCP Server to start. Please hold on.") self.server_wait_label.setAlignment(Qt.AlignCenter) self.setCentralWidget(self.server_wait_label) self.start_server() # Make sure we stop the server on window close def closeEvent(self, event): self.stop_server() event.accept() def create_menu_bar(self): menuBar = QtWidgets.QMenuBar(self) self.setMenuBar(menuBar) helpMenu = menuBar.addMenu("&Help") self.reset_gui_buttons_actions = QtWidgets.QAction("Reset GUI buttons") self.reset_gui_buttons_actions.triggered.connect( self.buttonController.enable_all_buttons) helpMenu.addAction(self.reset_gui_buttons_actions) self.about_action = QtWidgets.QAction("About") self.about_action.triggered.connect(self.show_about_message) helpMenu.addAction(self.about_action) def show_about_message(self): text = "The Electric Vehicle GrandPrix Autonomous Race Control System " \ "is brought to you by the RoboJackets at Georgia Tech.<br>" \ "Contribute to the RCS at <a href='https://github.com/RoboJackets/evgp-rcs'>https://github.com/RoboJackets/evgp-rcs</a>" QMessageBox.question(self, 'About the EVGP Race Control System', text, QMessageBox.Ok) def create_race_state_buttons(self): grid_active_race_button = QPushButton("GRID ACTIVE RACE") grid_active_race_button.clicked.connect( lambda: self.race_state_change_callback(RaceState.GRID_ACTIVE)) start_race_button = QPushButton("START RACE") start_race_button.setEnabled(False) start_race_button.clicked.connect( lambda: self.race_state_change_callback(RaceState.GREEN_GREEN)) red_flag_race_button = QPushButton("RED FLAG RACE") red_flag_race_button.clicked.connect( lambda: self.race_state_change_callback(RaceState.RED_FLAG)) e_stop_race_button = QPushButton("E-STOP RACE") e_stop_race_button.clicked.connect( lambda: self.race_state_change_callback(RaceState.RED_RED)) finish_race_button = QPushButton("FINISH RACE") finish_race_button.clicked.connect( lambda: self.race_state_change_callback(RaceState.IN_GARAGE)) return [ grid_active_race_button, start_race_button, red_flag_race_button, e_stop_race_button, finish_race_button ] def create_team_state_buttons(self): in_garage_team_button = QPushButton("IN GARAGE TEAM") in_garage_team_button.clicked.connect( lambda: self.team_state_change_callback(RaceState.IN_GARAGE)) red_flag_team_button = QPushButton("RED FLAG TEAM") red_flag_team_button.clicked.connect( lambda: self.team_state_change_callback(RaceState.RED_FLAG)) e_stop_team_button = QPushButton("E-STOP TEAM") e_stop_team_button.clicked.connect( lambda: self.team_state_change_callback(RaceState.RED_RED)) return [ in_garage_team_button, red_flag_team_button, e_stop_team_button ] def team_state_change_callback(self, state): if self.selectedIndex is not None: self.model.team_state_change(self.selectedIndex, state) def race_state_change_callback(self, state): self.model.race_state_change(state) def move_to_active_race(self): if self.selectedIndex is not None: changed = self.model.move_to_active_race(self.selectedIndex) if changed: self.clearAllSelections() def remove_from_active_race(self): if self.selectedIndex is not None: changed = self.model.move_to_standby_race(self.selectedIndex) if changed: self.clearAllSelections() def start_server(self): if not self.is_server_started: self.is_server_started = True port = 12017 server_backlog = 10 send_hz = 10 ip_list = self.model.teams_list.keys() self.server = TCPServer(port, server_backlog, whitelist=ip_list, hz=send_hz) self.server.new_connection.connect( self.model.new_connection_handler) self.server.lost_connection.connect( self.model.lost_connection_handler) self.server.new_response.connect(self.model.new_response_handler) self.server.server_ready.connect(self.server_ready_handler) self.model.team_state_change_signal.connect( self.server.on_race_state_change) self.server_thread = QThread() self.server.moveToThread(self.server_thread) self.server_thread.started.connect(self.server.run_server) self.server_thread.start() def stop_server(self): if self.is_server_started: self.server.stop() self.is_server_started = False self.server_thread.quit() @QtCore.pyqtSlot(bool) def server_ready_handler(self, isReady): if isReady: self.setCentralWidget(self.horizontalGroupBox) if not isReady: self.server_wait_label.setText( "Server Error: Please restart program.") QMessageBox.question( self, 'Server Error', "Server failed to start.\nPress \"Close\" to quit program, then fix your network issues and restart this program.", QMessageBox.Close) self.close() @QtCore.pyqtSlot(QItemSelection, QItemSelection) def standby_race_table_selection_handler(self, filterTableSelection, filterTableDeselected): if filterTableSelection.indexes(): self.activeRaceTable.selectionModel().clearSelection() self.selectedIndex = self.standbyRaceProxyModel.mapToSource( filterTableSelection.indexes()[0]).row() @QtCore.pyqtSlot(QItemSelection, QItemSelection) def active_race_table_selection_handler(self, tableSelection, tableDeselected): if tableSelection.indexes(): self.standbyRaceTable.selectionModel().clearSelection() self.selectedIndex = self.activeRaceProxyModel.mapToSource( tableSelection.indexes()[0]).row() def clearAllSelections(self): self.activeRaceTable.selectionModel().clearSelection() self.standbyRaceTable.selectionModel().clearSelection() self.selectedIndex = None
class MDTGUISingleModel(QMainWindow, Ui_MainWindow): def __init__(self, shared_state, computations_thread): super().__init__() self.setupUi(self) self._shared_state = shared_state self._computations_thread = computations_thread self._computations_thread.signal_starting.connect(self.computations_started) self._computations_thread.signal_finished.connect(self.computations_finished) self._stdout_old = sys.stdout self._stderr_old = sys.stderr self._logging_update_queue = Queue() self._logging_update_thread = QThread() self._message_receiver = MessageReceiver(self._logging_update_queue) self._message_receiver.text_message_signal.connect(self.update_log) self._message_receiver.moveToThread(self._logging_update_thread) self._logging_update_thread.started.connect(self._message_receiver.run) self._logging_update_thread.start() sys.stdout = ForwardingListener(self._logging_update_queue) sys.stderr = ForwardingListener(self._logging_update_queue) LogDispatchHandler.add_listener(ForwardingListener(self._logging_update_queue)) print_welcome_message() self.actionExit.setShortcuts(['Ctrl+q', 'Ctrl+w']) self.action_RuntimeSettings.triggered.connect(lambda: RuntimeSettingsDialog(self).exec_()) self.actionAbout.triggered.connect(lambda: AboutDialog(self).exec_()) self.action_GetExampleData.triggered.connect(lambda: GetExampleDataDialog(self, shared_state).exec_()) self.executionStatusLabel.setText('Idle') self.executionStatusIcon.setPixmap(QtGui.QPixmap(":/main_gui/icon_status_red.png")) self.fit_model_tab = FitModelTab(shared_state, self._computations_thread) self.fit_model_tab.setupUi(self.fitModelTab) self.generate_mask_tab = GenerateBrainMaskTab(shared_state, self._computations_thread) self.generate_mask_tab.setupUi(self.generateBrainMaskTab) self.view_results_tab = ViewResultsTab(shared_state, self._computations_thread) self.view_results_tab.setupUi(self.viewResultsTab) self.generate_roi_mask_tab = GenerateROIMaskTab(shared_state, self._computations_thread) self.generate_roi_mask_tab.setupUi(self.generateROIMaskTab) self.generate_protocol_tab = GenerateProtocolTab(shared_state, self._computations_thread) self.generate_protocol_tab.setupUi(self.generateProtocolTab) self.tabs = [self.fit_model_tab, self.generate_mask_tab, self.generate_roi_mask_tab, self.generate_protocol_tab, self.view_results_tab] self.MainTabs.currentChanged.connect(lambda index: self.tabs[index].tab_opened()) def closeEvent(self, event): sys.stdout = self._stdout_old sys.stderr = self._stderr_old self._message_receiver.is_running = False self._logging_update_thread.quit() self._logging_update_thread.wait(10) super().closeEvent(event) def send_sigint(self, *args): self.close() @pyqtSlot() def computations_started(self): self.executionStatusLabel.setText('Computing') self.executionStatusIcon.setPixmap(QtGui.QPixmap(":/main_gui/icon_status_green.png")) @pyqtSlot() def computations_finished(self): self.executionStatusLabel.setText('Idle') self.executionStatusIcon.setPixmap(QtGui.QPixmap(":/main_gui/icon_status_red.png")) @pyqtSlot(str) def update_log(self, string): sb = self.loggingTextBox.verticalScrollBar() scrollbar_position = sb.value() autoscroll = scrollbar_position == sb.maximum() self.loggingTextBox.moveCursor(QtGui.QTextCursor.End) self.loggingTextBox.insertPlainText(string) if autoscroll: sb.setValue(sb.maximum()) else: sb.setValue(scrollbar_position)
class ControlSystem(object): def __init__(self, parent_app, title="PyControlSystem", server_ip='127.0.0.1', server_port=5000, debug=False): # Get the root folder of this script self._root = os.path.abspath(os.path.dirname(__file__)) self._title = title current_time = time.strftime('%a-%d-%b-%Y_%H-%M-%S-EST', time.localtime()) self._data_logger = DataLogger(filename=r"D:\mist-1_cs_logs\smist-1_log_{}.h5".format(current_time)) self._data_logger.initialize() # Initialize communicator thread as None self._communicator = None # Store reference to 'parent' QApplication and set some parameters self._app = parent_app # TODO: Create Default XML file and Settings dialog to choose using this or not. if qdarkstyle is not None: self._app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5()) self._app.aboutToQuit.connect(self.on_quit_button) # connect the closing event to the quit button procedure # --- Set up Qt UI and connect some UI signals --- # self._window = MainWindow.MainWindow() self._window.setWindowTitle(self._title) self._window.ui.btnSave.triggered.connect(self.on_save_button) self._window.ui.action_save_session.triggered.connect(self.on_save_button) self._window.ui.btnSaveAs.triggered.connect(self.on_save_as_button) self._window.ui.action_save_session_as.triggered.connect(self.on_save_as_button) self._window.ui.btnLoad.triggered.connect(self.on_load_button) self._window.ui.action_load_session.triggered.connect(self.on_load_button) self._window.ui.btnQuit.triggered.connect(self.on_quit_button) self._window.ui.action_quit.triggered.connect(self.on_quit_button) # Connect printing functions self._window.ui.action_print.triggered.connect(self.handle_print) self._window.ui.action_print_preview.triggered.connect(self.handle_preview) self._print_editor = QTextEdit() # Create a QTextEdit object that holds the information to be printed self._print_font = QFont() self._print_font.setFamily("Calibri") self._print_font.setPointSize(12) self._print_editor.setFont(self._print_font) self._print_editor.hide() self._window.ui.btnStartPause.clicked.connect(self.on_start_pause_click) self._window.ui.btnStop_2.clicked.connect(self.on_stop_click) self._window.ui.btnStop_2.setEnabled(False) self._window.ui.btnResetPinnedPlot.clicked.connect(self.reset_pinned_plot_callback) # Connect the menu buttons self._window.ui.action_slack.triggered.connect(self.show_slack_dialog) self._window.ui.btnSetupDevicePlots.clicked.connect(self.show_PlotChooseDialog) self._window.ui.btnAddProcedure.clicked.connect(self.show_ProcedureDialog) self._window._sig_entry_form_changed.connect(self.connect_device_channel_entry_form) # --- Plotting timer --- # self._plot_timer = QTimer() self._plot_timer.timeout.connect(self.update_value_displays) # self._plot_timer.start(25) # Handling of tabs to windows to tabs self._plots_in_window = False # Flag that tells the GUI whether the plot tab is in a window or a tab self._window.ui.tabMain.sig_window_to_tab.connect(self.window_to_tab) self._window.ui.tabMain.sig_tab_to_window.connect(self.tab_to_window) # --- Initialize server --- # self.debug = debug self._server_url = 'http://{}:{}/'.format(server_ip, server_port) try: r = requests.get(self._server_url + 'initialize/') if r.status_code == 200: self._window.status_message(r.text) else: print('[Error initializing server] {}: {}'.format(r.status_code, r.text)) except Exception as e: print('Exception was: {}'.format(e)) print("Did you start the server first? Do you have the correct IP address?") exit() # --- Get devices connected to server --- # # r = requests.get(self._server_url + 'device/active') # if r.status_code == 200: # devices = json.loads(r.text) # for device_id, device_info in devices.items(): # self._window.status_message('Found {} with ID {} on port {}.'.format( # device_info['identifier'], # device_id, # device_info['port'])) # else: # print('[Error getting devices] {}: {}'.format(r.status_code, r.text)) # sys.exit(0) # --- Set up communication pipes --- # self._keep_communicating = False self._retry_devices = False self._polling_rate = 50.0 self._com_period = 1.0 / self._polling_rate # --- Set up data dictionaries and lists --- # self._devices = {} self._procedures = {} self._critical_procedures = {} self._emergency_stop_signals = {} self._plotted_channels = [] self._locked_devices = [] # --- Keep persistent communicator thread --- # self._com_thread = QThread() self._pinned_curve = self._window._pinnedplot.curve self._pinned_channel = None self._slack_token = None self._slack_channel = None self._device_file_name = '' self._window.status_message('Initialization complete.') def window_to_tab(self, content): if content.objectName() == "plotting": self._plots_in_window = False def tab_to_window(self, content): if content.objectName() == "plotting": self._plots_in_window = True # ---- Server Communication ---- # def setup_communication_threads(self): """ Create gui/server pipe pair, start communicator """ self._pipe_gui, pipe_server = Pipe() self._com_process = Process(target=query_server, args=(pipe_server, self._server_url, self.debug,)) self._keep_communicating = True self._communicator = Communicator(self._pipe_gui, self._app) self._communicator.moveToThread(self._com_thread) self._com_thread.started.connect(self._communicator.communicate) self._communicator.sig_status.connect(self.on_communicator_status) self._communicator.sig_poll_rate.connect(self.on_communicator_poll_rate) self._communicator.sig_device_info.connect(self.on_communicator_device_info) self._com_thread.start() # Tell the query process the current polling rate: pipe_message = ["com_period", self._com_period] self._communicator.send_message(pipe_message) # Get initial device/channel list and send to query process: self.device_or_channel_changed() # Start the query process: self._com_process.start() # this thread auto retries device connection every 30 seconds self._retry_devices = True self._retry_thread = threading.Thread(target=self.update_device_retry_labels, args=()) self._retry_thread.start() def shutdown_communication_threads(self): self._keep_communicating = False try: self._com_process.terminate() self._com_process.join() except AttributeError: # if process doesn't exist pass try: self._communicator.terminate() self._com_thread.quit() except AttributeError: pass self._retry_devices = False try: del self._retry_thread except AttributeError: pass # @pyqtSlot(str) def on_communicator_status(self, data: str): """ update status bar with thread message """ self._window.status_message(data) # @pyqtSlot(float) def on_communicator_poll_rate(self, data: float): """ update polling rate in GUI """ self._window.set_polling_rate('{0:.2f}'.format(data)) # @pyqtSlot(dict) def on_communicator_device_info(self, data: dict): """ Read in message from the server, and update devices accordingly """ parsed_response = data # print(parsed_response) for device_name, device in self._devices.items(): device_id = device.device_id if device.locked or device_id not in parsed_response.keys(): continue # if device_name == "naims": # print("Working on naims is {}".format(device_id in parsed_response.keys())) # if "ERROR" in parsed_response[device_id]: if any(resp in parsed_response[device_id] for resp in ("ERROR", "TIMEOUT")): device.lock(message=parsed_response[device_id]) if device not in self._locked_devices: self._locked_devices.append(device) continue if device in self._locked_devices: self._locked_devices.remove(device) device.overview_widget.hide_error_message() try: timestamp = parsed_response[device_id]['timestamp'] device.polling_rate = parsed_response[device_id]['polling_rate'] except KeyError: # did not get a valid response or dict might be empty continue for channel_name, value in parsed_response[device_id].items(): # metadata the server sent back that doesn't contain channel values if channel_name in ['timestamp', 'polling_rate']: continue # # if device_name == "naims": # print("Updating channel {} with value {}".format(channel_name, value)) channel = device.get_channel_by_name(channel_name) if channel is None: device.lock(message='Could not find channel with name {}.'.format(channel_name)) if device not in self._locked_devices: self._locked_devices.append(device) continue # Scale value back to channel channel.value = value / channel.scaling self.update_stored_values(device_name, channel_name, timestamp) # try: # self.log_data(channel, timestamp) # except Exception as e: # if self.debug: # print("Exception '{}' caught while trying to log data.".format(e)) # @pyqtSlot() def device_or_channel_changed(self): """ Sends a device changed request to the pipe """ device_dict_list = [{ 'device_driver': device.driver, 'device_id': device.device_id, 'locked_by_server': False, 'channel_ids': [name for name, mych in device.channels.items() if mych.mode in ['read', 'both']], 'precisions': [mych.precision for name, mych in device.channels.items() if mych.mode in ['read', 'both']], 'values': [None for name, mych in device.channels.items() if mych.mode in ['read', 'both']], 'data_types': [str(mych.data_type) for name, mych in device.channels.items() if mych.mode in ['read', 'both']] } for device_name, device in self._devices.items() if not (device.locked or len([name for name, mych in device.channels.items() if mych.mode in ['read', 'both']]) == 0)] pipe_message = ["device_or_channel_changed", device_dict_list] try: self._communicator.send_message(pipe_message) except AttributeError: pass # @pyqtSlot(Channel, object) def set_value_callback(self, channel, val): """ Creates a SET message to send to server """ # values = None if channel.data_type == float: values = val * channel.scaling else: values = float(val) if self.debug: print('Set value callback was called with widget {}, ' 'type {}, and scaled value {}.'.format(channel, channel.data_type, channel.value)) _data = {'device_driver': channel.parent_device.driver, 'device_id': channel.parent_device.device_id, 'locked_by_server': False, 'channel_ids': [channel.name], 'precisions': [None], 'values': [values], 'data_types': [str(channel.data_type)]} # Create a new thread that sends the set command to the server and # waits for an answer. new_set_thread = threading.Thread(target=self.update_device_on_server, args=(_data,)) new_set_thread.start() def update_device_retry_labels(self): while self._retry_devices: tr = 30 for i in range(tr): tr -= 1 for device in self._locked_devices: device.overview_widget.set_retry_label(tr) time.sleep(1) if not self._retry_devices: return for device in self._locked_devices: device.unlock() self.device_or_channel_changed() def update_device_on_server(self, _data): """ Sends POST request to server with new device/channel info """ _url = self._server_url + "device/set" try: _data = {'data': json.dumps(_data)} _r = requests.post(_url, data=_data) if _r.status_code == 200: print("Sending set command to server successful, response was: {}".format(_r.text)) else: print("Sending set command to server unsuccessful, response-code was: {}".format(_r.status_code)) except Exception as e: if self.debug: print("Exception '{}' caught while communicating with RasPi server.".format(e)) # ---- Internal variable modifiers ---- def update_stored_values(self, device_name, channel_name, timestamp): """ Update the value deques for each channel """ ch = self._devices[device_name].channels[channel_name] # zero values will crash log scale plots if ch.value == 0: ch.value = 1e-20 if len(ch.x_values) > 0: # only append new data (i.e. if we are polling faster than this # device can respond, might receive same point twice) if ch.x_values[-1] != timestamp: ch.append_data(timestamp, ch.value) if LOG_DATA: self._data_logger.log_value(device_name, channel_name, ch.value, timestamp) else: ch.append_data(timestamp, ch.value) if LOG_DATA: self._data_logger.log_value(device_name, channel_name, ch.value, timestamp) # check basic procedures to see if we should activate them for name, procedure in self._procedures.items(): if not isinstance(procedure, BasicProcedure): continue if not self._devices[device_name] in procedure.rule_devices(): continue if procedure.should_perform_procedure(): procedure.do_actions() # # @pyqtSlot(object) def connect_device_channel_entry_form(self, obj): """ Connects the new object's save and delete signals to the control system """ try: obj.sig_entry_form_ok.disconnect() except TypeError: pass obj.sig_entry_form_ok.connect(self.on_device_channel_changed) try: obj.sig_delete.disconnect() except TypeError: pass obj.sig_delete.connect(self.on_device_channel_delete) # # @pyqtSlot(object) def on_device_channel_delete(self, obj): for procedure_name, procedure in self._procedures.items(): used_devices, used_channels = procedure.devices_channels_used() if obj in used_devices | used_channels: self.show_ErrorDialog( 'Object is part of a procedure. Delete the procedure before deleting this object.') return ignored = self.show_WarningDialog('Delete objects at your own risk!') if ignored: if isinstance(obj, Device): del self._devices[obj.name] else: dev = self._devices[obj.parent_device.name] del dev.channels[obj.name] dev.update() self.update_gui_devices() self.device_or_channel_changed() print(obj) # @pyqtSlot(object, dict) def on_device_channel_changed(self, obj, vals): """ Called when user presses Save Changes button on the settings page. Gets passed an old device/channel and new values, or a new device/channel, and no values. """ # make sure the object is not part of a procedure for procedure_name, procedure in self._procedures.items(): used_devices, used_channels = procedure.devices_channels_used() if obj in used_devices | used_channels: ignored = self.show_WarningDialog( 'Object is part of a procedure. Delete the procedure before editing this object.') if not ignored: obj.reset_entry_form() return else: # if part of multiple procedures, we only want to show this once break if isinstance(obj, Device): name_in_use = vals['name'] in self._devices.keys() id_in_use = vals['device_id'] in [x.device_id for _, x in self._devices.items()] editing_device = obj in [x for _, x in self._devices.items()] if editing_device: if name_in_use and vals['name'] != obj.name: self.show_ErrorDialog('Device name already in use.') obj.reset_entry_form() # attempting to change device name to a name in-use return if id_in_use and vals['device_id'] != obj.device_id: self.show_ErrorDialog('Device ID already in use.') obj.reset_entry_form() # attempting to change device_id to an id in-use return else: # adding a new device if name_in_use or id_in_use: self.show_ErrorDialog('Device name already in use.') obj.reset_entry_form() # attempting to add a device with name or id in-use return for attr, val in vals.items(): if attr == 'name' and editing_device: self._devices[val] = self._devices.pop(obj.name) setattr(obj, attr, val) if not editing_device: self.add_device(obj) elif isinstance(obj, Channel): editing_channel = obj in [x for _, x in obj.parent_device.channels.items()] name_in_use = vals['name'] in obj.parent_device.channels.keys() if editing_channel: if name_in_use and vals['name'] != obj.name: self.show_ErrorDialog('Channel name already in use.') obj.reset_entry_form() return else: if name_in_use: self.show_ErrorDialog('Channel name already in use.') obj.reset_entry_form() return for attr, val in vals.items(): if attr == 'name' and editing_channel: self._devices[obj.parent_device.name].channels[val] = \ self._devices[obj.parent_device.name].channels.pop(obj.name) setattr(obj, attr, val) if not editing_channel: obj.parent_device.add_channel(obj) self.add_channel(obj) self.device_or_channel_changed() self.update_gui_devices() def add_device(self, device): """ Adds a device to the control system """ if device.name in self._devices.keys(): self.show_ErrorDialog('Device with the same name already loaded.') return False device.parent = self # Add device to the list of devices in the control system self._devices[device.name] = device if device.overview_order == -1: device.overview_order = min([x.overview_order for _, x in self._devices.items()]) - 1 device.initialize() for chname, ch in device.channels.items(): self.add_channel(ch) # if the device gets disconnected and reconnected, need to send a message to the server device.sig_update_server.connect(self.device_or_channel_changed) device.reset_entry_form() """ # Add corresponding channels to the hdf5 log. for channel_name, channel in device.channels().items(): if channel.mode() == "read" or channel.mode() == "both": self._data_logger.add_channel(channel) # Add the device to the settings page tree. device_iter = self._settings_page_tree_store.insert(None, (len(self._settings_page_tree_store) - 1), [device.label(), "Device", "edit_device", device.name(), device.name()]) """ # return true if successful return True def add_channel(self, channel): if channel.parent_device is None: print('Attempt to add channel with no parent device to gui') return # channel.initialize() channel._set_signal.connect(self.set_value_callback) channel._pin_signal.connect(self.set_pinned_plot_callback) channel._settings_signal.connect(self.set_plot_settings_callback) channel.reset_entry_form() channel.parent_device.update() # ---- GUI ---- # def update_gui_devices(self): """ Main update function to be called when a device is changed """ for name, device in self._devices.items(): device.update() self._window.update_overview(self._devices) self._window.update_device_settings(self._devices) self._window.update_plots(self._devices, self._plotted_channels) self._window.update_procedures(self._procedures) # # @pyqtSlot() def on_start_pause_click(self): btn = self._window.ui.btnStartPause if btn.text() == 'Start Polling': self.setup_communication_threads() self._plot_timer.start(50) btn.setText('Pause Polling') self._window.ui.btnStop_2.setEnabled(True) elif btn.text() == 'Pause Polling': self._communicator.send_message('pause_query', ) self._keep_communicating = False self._communicator.isRunning = False self._plot_timer.stop() btn.setText('Resume Polling') else: self._communicator.send_message('pause_query', ) self._keep_communicating = True self._communicator.isRunning = True self._plot_timer.start(50) btn.setText('Pause Polling') # # @pyqtSlot() def on_stop_click(self): # First we pause the polling like in the on_start_paus_click() function self._communicator.send_message('pause_query', ) self._keep_communicating = False self._communicator.isRunning = False self._plot_timer.stop() # Then we shut it down self.shutdown_communication_threads() self._window.ui.btnStartPause.setText('Start Polling') self._window.ui.btnStop_2.setEnabled(False) for device in self._locked_devices: device.unlock() device.overview_widget.hide_error_message() self._locked_devices = [] for device_name, device in self._devices.items(): device.error_message = '' for channel_name, channel in device.channels.items(): channel.clear_data() self.update_value_displays() # self._plotted_channels = {} # self.update_gui_devices() # @pyqtSlot() def update_value_displays(self): """ This function is called by a QTimer to ensure the GUI has a chance to get input. Handles updating of 'read' values on the overview page, and redraws plots if applicable """ # update the pinned plot if self._pinned_channel is not None: self._pinned_curve.setData(self._pinned_channel.x_values, self._pinned_channel.y_values, clear=True, _callsync='off') if self._window.current_tab == 'main': # update read values on overview page for name, device in self._devices.items(): for chname, channel in device.channels.items(): if channel.read_widget is None: continue if channel.data_type in [int, float]: fmt = '{:' + channel.displayformat + '}' val = str(fmt.format(channel.value)) channel.read_widget.setText(val) # If plot tab is in window mode, we would like it to update always... if self._window.current_tab == 'plots' or self._plots_in_window: # update the plotted channels for _, _ in self._devices.items(): for channel in self._plotted_channels: # pyqtgraph cant plot log of 0 channel._plot_curve.setData(channel.x_values, channel.y_values, clear=True, _callsync='off') self._app.processEvents() def reset_pinned_plot_callback(self): if self._pinned_channel is not None: x = self._window._gbpinnedplot.layout().itemAt(0).widget() x.settings = self._pinned_channel.plot_settings def set_pinned_plot_callback(self, device, channel): """ Set the pinned plot when a user pressed the plot's pin button """ # click button emits (device, channel) self._pinned_channel = channel # update plot settings x = self._window._gbpinnedplot.layout().itemAt(0).widget() x.setLabel('left', '{} [{}]'.format(channel.label, channel.unit)) x.settings = channel.plot_settings self._window._gbpinnedplot.setTitle('{}.{}'.format(device.label, channel.label)) # @pyqtSlot(Channel) def set_plot_settings_callback(self, ch): """ Show the plot settings dialog when the user presses the plot's setting button """ rng = ch._plot_widget.view_range ch._plot_settings['x']['min'] = rng[0][0] ch._plot_settings['x']['max'] = rng[0][1] ch._plot_settings['y']['min'] = rng[1][0] ch._plot_settings['y']['max'] = rng[1][1] _plotsettingsdialog = PlotSettingsDialog(ch) _plotsettingsdialog.exec_() def send_notification(self, notification_text): # Callback function to handle sending of notifications # This is in the main GUI loop so we can centrally update slack token # and channel without communicating with the procedure threads. They # just emit a signal to trigger this function here. # :param: notification text if self._slack_channel is not None and self._slack_token is not None: _sc = SlackClient(self._slack_token) ret_data = _sc.api_call("chat.postMessage", channel="mist-1-alarms", text=notification_text) print(ret_data) if not ret_data["ok"]: self._window.status_message("A procedure was triggered, but failed to send a Slack message.") else: self._window.status_message("A procedure was triggered, but no Slack token and channel were specified.") def show_slack_dialog(self): # Open the slack dialog to get the slack token _slackdialog = SlackDialog(self._slack_token, self._slack_channel) success, ret_data = _slackdialog.exec_() # Note that SlackDialog does the checking for us, it will return # None for ret_data if it couldn't communicate with Slack # On Cancel (success==0) we do nothing. if success: if ret_data is not None: self._window.status_message("Slack token and channel were updated.") self._slack_token = ret_data["token"] self._slack_channel = ret_data["channel_id"] else: self._window.status_message("Slack token and channel were reset to None.") self._slack_token = None self._slack_channel = None print(self._slack_token) # # @pyqtSlot() def on_quit_button(self): # First we pause the polling like in the on_start_paus_click() function if self._communicator is not None: self._communicator.send_message('pause_query', ) self._keep_communicating = False self._communicator.isRunning = False self._plot_timer.stop() # Then we shut down communication threads self.shutdown_communication_threads() self._window.close() # ---- dialogs ---- # def on_save_button(self): if self._device_file_name == '': fileName, _ = QFileDialog.getSaveFileName(self._window, "Save Session as JSON", "", "Text Files (*.txt)") if fileName == '': return if fileName[-4:] != '.txt': fileName += '.txt' self._device_file_name = fileName with open(self._device_file_name, 'w') as f: devdict = {} for device_name, device in self._devices.items(): devdict[device_name] = device.get_json() procdict = {} for proc_name, procedure in self._procedures.items(): procdict[proc_name] = procedure.json winsettingsdict = self._window.current_settings() chpinname = None devpinname = None if self._pinned_channel is not None: chpinname = self._pinned_channel.name devpinname = self._pinned_channel.parent_device.name cssettingsdict = { 'pinned-channel': chpinname, 'pinned-device': devpinname, 'plotted-channels': [(x.name, x.parent_device.name) for x in self._plotted_channels]} # TODO: I don't like saving the Slack token in plain text json! -DW slacksettingsdict = {'token': self._slack_token, 'channel': self._slack_channel} output = { 'devices': devdict, 'procedures': procdict, 'window-settings': winsettingsdict, 'control-system-settings': cssettingsdict, 'slack-settings': slacksettingsdict } json.dump(output, f, sort_keys=True, indent=4, separators=(', ', ': ')) self._window.status_message('Saved session to {}.'.format(self._device_file_name)) def on_save_as_button(self): _fn, _ = QFileDialog.getSaveFileName(self._window, "Save Session as JSON", "", "Text Files (*.txt)") if _fn == '': return if _fn[-4:] != '.txt': _fn += '.txt' self._device_file_name = _fn self.on_save_button() def handle_print(self): printer = QtPrintSupport.QPrinter(QtPrintSupport.QPrinter.HighResolution) dialog = QtPrintSupport.QPrintDialog(printer, self._window) if dialog.exec_() == QtPrintSupport.QPrintDialog.Accepted: self.assemble_print_text() self._print_editor.document().print_(dialog.printer()) self._window.status_message("Sent summary document to printer {}".format(dialog.printer().printerName())) def handle_preview(self): self.assemble_print_text() dialog = QtPrintSupport.QPrintPreviewDialog() dialog.paintRequested.connect(self._print_editor.print_) if dialog.exec_() == QtPrintSupport.QPrintPreviewDialog.Accepted: self._window.status_message("Sent summary document to printer {}".format(dialog.printer().printerName())) def assemble_print_text(self): # Query Devices for information self._print_editor.setText("") self._print_editor.setFontUnderline(True) self._print_editor.setFontPointSize(14) self._print_editor.setText("{} summary from {}:" "\n".format(self._title, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) self._print_editor.moveCursor(QTextCursor.End) self._print_editor.setFontUnderline(False) self._print_editor.setFontPointSize(12) text = "" for _, _dev in self._devices.items(): text += "\nDevice {}:\n".format(_dev.label) for _, _cha in _dev.channels.items(): text += _cha.get_print_str() self._print_editor.insertPlainText(text) # # @pyqtSlot() def on_load_button(self): successes = 0 filename, _ = QFileDialog.getOpenFileName(self._window, "Load session from JSON", "", "Text Files (*.txt)") if filename == '': return # load_from_csv is a homebrewed function in FileOps.py res = load_from_csv(filename) if res is None: self._window.status_message('Unable to read JSON.') return else: devices, procedures, winsettings, cssettings, slack_settings = res self._slack_token = slack_settings["token"] self._slack_channel = slack_settings["channel"] # Add devices and procedures for _, dev in devices.items(): if self.add_device(dev): successes += 1 for _, proc in procedures.items(): self.add_procedure(proc) # Load control system settings self._window.apply_settings(winsettings) self.apply_settings(cssettings) # Test slack client if self._slack_token is not None: _sc = SlackClient(self._slack_token) test_data = _sc.api_call("api.test") if not test_data['ok']: self._window.status_message("Testing Slack token: Fail!, Error: {}. " "Token and channel deleted.".format(test_data['error'])) self._slack_token = None self._slack_channel = None else: test_data = _sc.api_call("channels.info", channel=self._slack_channel) if not test_data['ok']: self._window.status_message("Testing Slack channel '{}': Fail!, Error: {}. " "Channel deleted (token worked.)".format(self._slack_channel, test_data['error'])) self._slack_channel = None if successes > 0: self._device_file_name = filename self.update_gui_devices() self._window.status_message('Loaded {} devices from JSON.'.format(successes)) # # @pyqtSlot() def show_PlotChooseDialog(self): _plotchoosedialog = PlotChooseDialog(self._devices, self._plotted_channels) accept, chs = _plotchoosedialog.exec_() self._plotted_channels = chs self.update_gui_devices() def add_procedure(self, procedure): self._procedures[procedure.name] = procedure procedure.signal_edit.connect(self.edit_procedure) procedure.signal_delete.connect(self.delete_procedure) if isinstance(procedure, PidProcedure): procedure.set_signal.connect(self.set_value_callback) if isinstance(procedure, BasicProcedure): procedure.set_signal.connect(self.set_value_callback) procedure.send_notification_signal.connect(self.send_notification) # connect emergency stop button signal if procedure.triggertype == 'emstop': f = lambda: procedure.do_actions() self._window.ui.btnStop.clicked.connect(f) self._emergency_stop_signals[procedure.name] = f procedure.initialize() def edit_procedure(self, proc): self.show_ProcedureDialog(False, proc=proc) def delete_procedure(self, proc): # unbind PyQtSlot from emergency stop button signal if isinstance(proc, BasicProcedure): if proc.triggertype == 'emstop': self._window.ui.btnStop.clicked.disconnect(self._emergency_stop_signals[proc.name]) del self._emergency_stop_signals[proc.name] del self._procedures[proc.name] self._window.update_procedures(self._procedures) # # @pyqtSlot() # # @pyqtSlot(Procedure) def show_ProcedureDialog(self, dummy_bool, proc=None): _proceduredialog = ProcedureDialog(self._devices, self._procedures.keys(), proc) accept, rproc = _proceduredialog.exec_() if rproc is not None: if proc is not None: # if we edited a procedure delete the old version before adding the new one self.delete_procedure(proc) self.add_procedure(rproc) self._window.update_procedures(self._procedures) # @pyqtSlot() def show_ErrorDialog(self, error_message='Error'): _errordialog = ErrorDialog(error_message) _errordialog.exec_() self.update_gui_devices() def show_WarningDialog(self, warning_message='Warning'): _warningdialog = WarningDialog(warning_message) userignored = _warningdialog.exec_() return userignored # ---- Other functions ---- # def apply_settings(self, settings): if settings == {}: return if settings['pinned-device'] is not None: dev = self._devices[settings['pinned-device']] ch = dev.channels[settings['pinned-channel']] self._pinned_channel = ch self.set_pinned_plot_callback(dev, ch) for item in settings['plotted-channels']: self._plotted_channels.append(self._devices[item[1]].channels[item[0]]) def run(self): # self.setup_communication_threads() self.update_gui_devices() self._window.show()
class mainWindow(QMainWindow, Ui_MainWindow): """ The main class for the GUI window """ def __init__(self): """ The constructor and initiator. :return: """ # initial setup super(mainWindow, self).__init__() self.setupUi(self) # text, ok = QInputDialog.getText(self, 'Settings', 'Enter the host address:', QLineEdit.Normal, '140.181.97.133') # if ok: # host = str(text) # text, ok = QInputDialog.getText(self, 'Settings', 'Enter the port number:', QLineEdit.Normal, '10000') # if ok: # port = int(text) # text, ok = QInputDialog.getText(self, 'Settings', 'Enter the topic number for Dump-Pressure:', QLineEdit.Normal, '10001') # if ok: # topic = str(text) host = '140.181.97.133' port = 10000 topic = '10001' self.thread = QThread() self.zeromq_listener = ZMQListener(host, port) self.zeromq_listener.moveToThread(self.thread) self.thread.started.connect(self.zeromq_listener.loop) self.gas = Wasserstoff() # self.gasart_label.setText(self.gas.label) # Connect signals self.connect_signals() QTimer.singleShot(0, self.thread.start) self.show_message('Connected to server: {}:{}'.format(host, port)) def connect_signals(self): """ Connects signals. :return: """ # Action about and Action quit will be shown differently in OSX self.actionAbout.triggered.connect(self.show_about_dialog) self.actionQuit.triggered.connect(QCoreApplication.instance().quit) self.zeromq_listener.message.connect(self.signal_received) # combo box self.comboBox.currentTextChanged.connect(self.schaffe_passendes_gas_objekt) @staticmethod def eformat(f, prec, exp_digits): s = "%.*e" % (prec, f) mantissa, exp = s.split('e') # add 1 to digits as 1 is taken by sign +/- return "%se%+0*d" % (mantissa, exp_digits + 1, int(exp)) def signal_received(self, message): l = len(message) l = l - 1 message = message[2:l] dichte, temperatur = self.gas.rechne_dichte_aus(message) label_temperatur = 'Düsentemperatur: ' + str(round(temperatur, 2)) + 'K' self.label_temperatur.setText(label_temperatur) self.lcdNumber.setDigitCount(8) self.lcdNumber.display(dichte) def closeEvent(self, event): self.zeromq_listener.running = False self.thread.quit() self.thread.wait() def show_message(self, message): """ Implementation of an abstract method: Show text in status bar :param message: :return: """ self.statusbar.showMessage(message) def schaffe_passendes_gas_objekt(self, gasart): if gasart == 'Helium': self.gas = Helium() elif gasart == 'Neon': self.gas = Neon() elif gasart == 'Argon': self.gas = Argon() elif gasart == 'Krypton': self.gas = Krypton() elif gasart == 'Xenon': self.gas = Xenon() elif gasart == 'Wasserstoff': self.gas = Wasserstoff() elif gasart == 'Deuterium': self.gas = Deuterium() elif gasart == 'Stickstoff': self.gas = Stickstoff() def show_about_dialog(self): """ Show about dialog :return: """ about_dialog = QDialog() about_dialog.ui = Ui_AbooutDialog() about_dialog.ui.setupUi(about_dialog) about_dialog.ui.labelVersion.setText('Version: {}'.format(__version__)) about_dialog.exec_() about_dialog.show()
class WeatherStationBrowser(QWidget): """ Widget that allows the user to browse and select ECCC climate stations. """ ConsoleSignal = QSignal(str) staListSignal = QSignal(list) PROV_NAME = [x[0].title() for x in PROV_NAME_ABB] PROV_ABB = [x[1] for x in PROV_NAME_ABB] def __init__(self, parent=None): super(WeatherStationBrowser, self).__init__(parent) self.stn_finder_worker = WeatherStationFinder() self.stn_finder_worker.sig_load_database_finished.connect( self.receive_load_database) self.stn_finder_thread = QThread() self.stn_finder_worker.moveToThread(self.stn_finder_thread) self.station_table = WeatherSationView() self.waitspinnerbar = WaitSpinnerBar() self.stn_finder_worker.sig_progress_msg.connect( self.waitspinnerbar.set_label) self.__initUI__() self.start_load_database() def __initUI__(self): self.setWindowTitle('Weather Stations Browser') self.setWindowIcon(icons.get_icon('master')) self.setWindowFlags(Qt.Window) now = datetime.now() # ---- Tab Widget Search # ---- Proximity filter groupbox label_Lat = QLabel('Latitude :') label_Lat2 = QLabel('North') self.lat_spinBox = QDoubleSpinBox() self.lat_spinBox.setAlignment(Qt.AlignCenter) self.lat_spinBox.setSingleStep(0.1) self.lat_spinBox.setValue(0) self.lat_spinBox.setMinimum(0) self.lat_spinBox.setMaximum(180) self.lat_spinBox.setSuffix(u' °') self.lat_spinBox.valueChanged.connect(self.proximity_grpbox_toggled) label_Lon = QLabel('Longitude :') label_Lon2 = QLabel('West') self.lon_spinBox = QDoubleSpinBox() self.lon_spinBox.setAlignment(Qt.AlignCenter) self.lon_spinBox.setSingleStep(0.1) self.lon_spinBox.setValue(0) self.lon_spinBox.setMinimum(0) self.lon_spinBox.setMaximum(180) self.lon_spinBox.setSuffix(u' °') self.lon_spinBox.valueChanged.connect(self.proximity_grpbox_toggled) self.radius_SpinBox = QComboBox() self.radius_SpinBox.addItems(['25 km', '50 km', '100 km', '200 km']) self.radius_SpinBox.currentIndexChanged.connect( self.search_filters_changed) prox_search_grid = QGridLayout() row = 0 prox_search_grid.addWidget(label_Lat, row, 1) prox_search_grid.addWidget(self.lat_spinBox, row, 2) prox_search_grid.addWidget(label_Lat2, row, 3) row += 1 prox_search_grid.addWidget(label_Lon, row, 1) prox_search_grid.addWidget(self.lon_spinBox, row, 2) prox_search_grid.addWidget(label_Lon2, row, 3) row += 1 prox_search_grid.addWidget(QLabel('Search Radius :'), row, 1) prox_search_grid.addWidget(self.radius_SpinBox, row, 2) prox_search_grid.setColumnStretch(0, 100) prox_search_grid.setColumnStretch(4, 100) prox_search_grid.setRowStretch(row+1, 100) prox_search_grid.setHorizontalSpacing(20) prox_search_grid.setContentsMargins(10, 10, 10, 10) # (L, T, R, B) self.prox_grpbox = QGroupBox("Proximity filter :") self.prox_grpbox.setCheckable(True) self.prox_grpbox.setChecked(False) self.prox_grpbox.toggled.connect(self.proximity_grpbox_toggled) self.prox_grpbox.setLayout(prox_search_grid) # ---- Province filter prov_names = ['All'] prov_names.extend(self.PROV_NAME) self.prov_widg = QComboBox() self.prov_widg.addItems(prov_names) self.prov_widg.setCurrentIndex(0) self.prov_widg.currentIndexChanged.connect(self.search_filters_changed) layout = QGridLayout() layout.addWidget(self.prov_widg, 2, 1) layout.setColumnStretch(2, 100) layout.setVerticalSpacing(10) prov_grpbox = QGroupBox("Province filter :") prov_grpbox.setLayout(layout) # ---- Data availability filter # Number of years with data self.nbrYear = QSpinBox() self.nbrYear.setAlignment(Qt.AlignCenter) self.nbrYear.setSingleStep(1) self.nbrYear.setMinimum(0) self.nbrYear.setValue(3) self.nbrYear.valueChanged.connect(self.search_filters_changed) subgrid1 = QGridLayout() subgrid1.addWidget(self.nbrYear, 0, 0) subgrid1.addWidget(QLabel('years of data between'), 0, 1) subgrid1.setHorizontalSpacing(10) subgrid1.setContentsMargins(0, 0, 0, 0) # (L, T, R, B) subgrid1.setColumnStretch(2, 100) # Year range self.minYear = QSpinBox() self.minYear.setAlignment(Qt.AlignCenter) self.minYear.setSingleStep(1) self.minYear.setMinimum(1840) self.minYear.setMaximum(now.year) self.minYear.setValue(1840) self.minYear.valueChanged.connect(self.minYear_changed) label_and = QLabel('and') label_and.setAlignment(Qt.AlignCenter) self.maxYear = QSpinBox() self.maxYear.setAlignment(Qt.AlignCenter) self.maxYear.setSingleStep(1) self.maxYear.setMinimum(1840) self.maxYear.setMaximum(now.year) self.maxYear.setValue(now.year) self.maxYear.valueChanged.connect(self.maxYear_changed) subgrid2 = QGridLayout() subgrid2.addWidget(self.minYear, 0, 0) subgrid2.addWidget(label_and, 0, 1) subgrid2.addWidget(self.maxYear, 0, 2) subgrid2.setHorizontalSpacing(10) subgrid2.setContentsMargins(0, 0, 0, 0) # (L, T, R, B) subgrid2.setColumnStretch(4, 100) # Subgridgrid assembly grid = QGridLayout() grid.addWidget(QLabel('Search for stations with at least'), 0, 0) grid.addLayout(subgrid1, 1, 0) grid.addLayout(subgrid2, 2, 0) grid.setVerticalSpacing(5) grid.setRowStretch(0, 100) # grid.setContentsMargins(0, 0, 0, 0) # (L, T, R, B) self.year_widg = QGroupBox("Data Availability filter :") self.year_widg.setLayout(grid) # ---- Toolbar self.btn_addSta = btn_addSta = QPushButton('Add') btn_addSta.setIcon(icons.get_icon('add2list')) btn_addSta.setIconSize(icons.get_iconsize('small')) btn_addSta.setToolTip('Add selected found weather stations to the ' 'current list of weather stations.') btn_addSta.clicked.connect(self.btn_addSta_isClicked) btn_save = QPushButton('Save') btn_save.setIcon(icons.get_icon('save')) btn_save.setIconSize(icons.get_iconsize('small')) btn_save.setToolTip('Save current found stations info in a csv file.') btn_save.clicked.connect(self.btn_save_isClicked) self.btn_fetch = btn_fetch = QPushButton('Fetch') btn_fetch.setIcon(icons.get_icon('refresh')) btn_fetch.setIconSize(icons.get_iconsize('small')) btn_fetch.setToolTip("Updates the climate station database by" " fetching it again from the ECCC ftp server.") btn_fetch.clicked.connect(self.btn_fetch_isClicked) toolbar_grid = QGridLayout() toolbar_widg = QWidget() for col, btn in enumerate([btn_addSta, btn_save, btn_fetch]): toolbar_grid.addWidget(btn, 0, col+1) toolbar_grid.setColumnStretch(toolbar_grid.columnCount(), 100) toolbar_grid.setSpacing(5) toolbar_grid.setContentsMargins(0, 30, 0, 0) # (L, T, R, B) toolbar_widg.setLayout(toolbar_grid) # ---- Left Panel panel_title = QLabel('<b>Weather Station Search Criteria :</b>') left_panel = QFrame() left_panel_grid = QGridLayout() left_panel_grid.addWidget(panel_title, 0, 0) left_panel_grid.addWidget(self.prox_grpbox, 1, 0) left_panel_grid.addWidget(prov_grpbox, 2, 0) left_panel_grid.addWidget(self.year_widg, 3, 0) left_panel_grid.setRowStretch(4, 100) left_panel_grid.addWidget(toolbar_widg, 5, 0) left_panel_grid.setVerticalSpacing(20) left_panel_grid.setContentsMargins(0, 0, 0, 0) # (L, T, R, B) left_panel.setLayout(left_panel_grid) # ----- Main grid # Widgets vLine1 = QFrame() vLine1.setFrameStyle(StyleDB().VLine) # Grid main_layout = QGridLayout(self) main_layout.addWidget(left_panel, 0, 0) main_layout.addWidget(vLine1, 0, 1) main_layout.addWidget(self.station_table, 0, 2) main_layout.addWidget(self.waitspinnerbar, 0, 2) main_layout.setContentsMargins(10, 10, 10, 10) # (L,T,R,B) main_layout.setRowStretch(0, 100) main_layout.setHorizontalSpacing(15) main_layout.setVerticalSpacing(5) main_layout.setColumnStretch(col, 100) @property def stationlist(self): return self.station_table.get_stationlist() @property def search_by(self): return ['proximity', 'province'][self.tab_widg.currentIndex()] @property def prov(self): if self.prov_widg.currentIndex() == 0: return self.PROV_ABB else: return self.PROV_ABB[self.prov_widg.currentIndex()-1] @property def lat(self): return self.lat_spinBox.value() def set_lat(self, x, silent=True): if silent: self.lat_spinBox.blockSignals(True) self.lat_spinBox.setValue(x) self.lat_spinBox.blockSignals(False) self.proximity_grpbox_toggled() @property def lon(self): return self.lon_spinBox.value() def set_lon(self, x, silent=True): if silent: self.lon_spinBox.blockSignals(True) self.lon_spinBox.setValue(x) self.lon_spinBox.blockSignals(False) self.proximity_grpbox_toggled() @property def rad(self): return int(self.radius_SpinBox.currentText()[:-3]) @property def prox(self): if self.prox_grpbox.isChecked(): return (self.lat, -self.lon, self.rad) else: return None @property def year_min(self): return int(self.minYear.value()) def set_yearmin(self, x, silent=True): if silent: self.minYear.blockSignals(True) self.minYear.setValue(x) self.minYear.blockSignals(False) @property def year_max(self): return int(self.maxYear.value()) def set_yearmax(self, x, silent=True): if silent: self.maxYear.blockSignals(True) self.maxYear.setValue(x) self.maxYear.blockSignals(False) @property def nbr_of_years(self): return int(self.nbrYear.value()) def set_yearnbr(self, x, silent=True): if silent: self.nbrYear.blockSignals(True) self.nbrYear.setValue(x) self.nbrYear.blockSignals(False) # ---- Weather Station Finder Handlers def start_load_database(self, force_fetch=False): """Start the process of loading the climate station database.""" if self.stn_finder_thread.isRunning(): return self.station_table.clear() self.waitspinnerbar.show() # Start the downloading process. if force_fetch: self.stn_finder_thread.started.connect( self.stn_finder_worker.fetch_database) else: self.stn_finder_thread.started.connect( self.stn_finder_worker.load_database) self.stn_finder_thread.start() @QSlot() def receive_load_database(self): """Handles when loading the database is finished.""" # Disconnect the thread. self.stn_finder_thread.started.disconnect() # Quit the thread. self.stn_finder_thread.quit() waittime = 0 while self.stn_finder_thread.isRunning(): sleep(0.1) waittime += 0.1 if waittime > 15: # pragma: no cover print("Unable to quit the thread.") break # Force an update of the GUI. self.proximity_grpbox_toggled() if self.stn_finder_worker.data is None: self.waitspinnerbar.show_warning_icon() else: self.waitspinnerbar.hide() # ---- GUI handlers def show(self): super(WeatherStationBrowser, self).show() qr = self.frameGeometry() if self.parent(): parent = self.parent() wp = parent.frameGeometry().width() hp = parent.frameGeometry().height() cp = parent.mapToGlobal(QPoint(wp/2, hp/2)) else: cp = QDesktopWidget().availableGeometry().center() qr.moveCenter(cp) self.move(qr.topLeft()) # ------------------------------------------------------------------------- def minYear_changed(self): min_yr = min_yr = max(self.minYear.value(), 1840) now = datetime.now() max_yr = now.year self.maxYear.setRange(min_yr, max_yr) self.search_filters_changed() def maxYear_changed(self): min_yr = 1840 now = datetime.now() max_yr = min(self.maxYear.value(), now.year) self.minYear.setRange(min_yr, max_yr) self.search_filters_changed() # ---- Toolbar Buttons Handlers def btn_save_isClicked(self): ddir = os.path.join(os.getcwd(), 'weather_station_list.csv') filename, ftype = QFileDialog().getSaveFileName( self, 'Save normals', ddir, '*.csv;;*.xlsx;;*.xls') self.station_table.save_stationlist(filename) def btn_addSta_isClicked(self): rows = self.station_table.get_checked_rows() if len(rows) > 0: staList = self.station_table.get_content4rows(rows) self.staListSignal.emit(staList) print('Selected stations sent to list') else: print('No station currently selected') def btn_fetch_isClicked(self): """Handles when the button fetch is clicked.""" self.start_load_database(force_fetch=True) # ---- Search Filters Handlers def proximity_grpbox_toggled(self): """ Set the values for the reference geo coordinates that are used in the WeatherSationView to calculate the proximity values and forces a refresh of the content of the table. """ if self.prox_grpbox.isChecked(): self.station_table.set_geocoord((self.lat, -self.lon)) else: self.station_table.set_geocoord(None) self.search_filters_changed() def search_filters_changed(self): """ Search for weather stations with the current filter values and forces an update of the station table content. """ if self.stn_finder_worker.data is not None: stnlist = self.stn_finder_worker.get_stationlist( prov=self.prov, prox=self.prox, yrange=(self.year_min, self.year_max, self.nbr_of_years)) self.station_table.populate_table(stnlist)
class Debugger(QObject): """ Represents the networked debugger client. """ ETX = b'\x03' # End transmission token. def __init__(self, host, port, proc=None): """ Instantiate given a host, port and process for the debug runner. """ self.host = host self.port = port self.proc = proc self.view = None # Set after instantiation. super().__init__() def start(self): """ Start the debugger session. """ self.listener_thread = QThread(self.view.view) self.command_handler = CommandBufferHandler(self) self.command_handler.moveToThread(self.listener_thread) self.command_handler.on_command.connect(self.on_command) self.command_handler.on_fail.connect(self.on_fail) self.listener_thread.started.connect(self.command_handler.worker) self.listener_thread.start() def on_command(self, command): """ Handle a command emitted by the client thread. """ event, data = json.loads(command) if hasattr(self, 'on_{}'.format(event)): getattr(self, 'on_{}'.format(event))(**data) def on_fail(self, message): """ Handle if there's a connection failure with the debug runner. """ logger.error(message) self.view.debug_on_fail(message) def stop(self): """ Shut down the debugger session. """ self.command_handler.stopped = True self.listener_thread.quit() self.listener_thread.wait() if self.proc is not None: self.output('quit') self.socket.shutdown(socket.SHUT_WR) if self.proc is not None: # Wait for the runner process to die. self.proc.wait() def output(self, event, **data): """ Send a command to the debug runner. """ try: dumped = json.dumps((event, data)).encode('utf-8') self.socket.sendall(dumped + Debugger.ETX) except OSError as e: logger.debug('Debugger client error.') logger.debug(e) except AttributeError as e: logger.debug('Debugger client not connected to runner.') logger.debug(e) def breakpoint(self, breakpoint): """ Given a breakpoint number or (filename, line), return an object representing the referenced breakpoint. """ try: if isinstance(breakpoint, tuple): filename, line = breakpoint return self.bp_index[filename][line] else: return self.bp_list[breakpoint] except KeyError: raise UnknownBreakpoint() def breakpoints(self, filename): """ Return all the breakpoints associated with the referenced file. """ return self.bp_index.get(filename, {}) # Commands that can be passed to the debug runner. def create_breakpoint(self, filename, line, temporary=False): """ Create a new, enabled breakpoint at the specified line of the given file. """ self.output('break', filename=filename, line=line, temporary=temporary) def enable_breakpoint(self, breakpoint): """ Enable an existing breakpoint. """ self.output('enable', bpnum=breakpoint.bpnum) def disable_breakpoint(self, breakpoint): """ Disable an existing breakpoint. """ self.output('disable', bpnum=breakpoint.bpnum) def ignore_breakpoint(self, breakpoint, count): """ Ignore an existing breakpoint for "count" iterations. (N.B. Use a count of 0 to restore the breakpoint. """ self.output('ignore', bpnum=breakpoint.bpnum, count=count) def clear_breakpoint(self, breakpoint): """ Clear an existing breakpoint. """ self.output('clear', bpnum=breakpoint.bpnum) def do_run(self): """ Run the debugger until the next breakpoint. """ self.output('continue') def do_step(self): """ Step through one stack frame. """ self.output('step') def do_next(self): """ Go to the next line in the current stack frame. """ self.output('next') def do_return(self): """ Return to the previous stack frame. """ self.output('return') # Handlers for events raised by the debug runner. These generally follow # the pattern of updating state in the client object to reflect that of # the debug runner, then calling a method in the UI layer to update the # GUI to reflect the changed state. def on_bootstrap(self, breakpoints): """ The runner has finished setting up. """ self.bp_index = {} self.bp_list = list([True, ]) # Breakpoints count from 1 for bp_data in breakpoints: self.on_breakpoint_create(**bp_data) self.view.debug_on_bootstrap() def on_breakpoint_create(self, **bp_data): """ The runner has created a breakpoint. """ bp = Breakpoint(**bp_data) self.bp_index.setdefault(bp.filename, {}).setdefault(bp.line, bp) self.bp_list.append(bp) if bp.enabled: self.view.debug_on_breakpoint_enable(bp) else: self.view.debug_on_breakpoint_disable(bp) def on_breakpoint_enable(self, bpnum): """ The runner has enabled the breakpoint referenced by breakpoint number. """ bp = self.bp_list[bpnum] bp.enabled = True self.view.debug_on_breakpoint_enable(bp) def on_breakpoint_disable(self, bpnum): """ The runner has disabled a breakpoint referenced by breakpoint number. """ bp = self.bp_list[bpnum] bp.enabled = False self.view.debug_on_breakpoint_disable(bp) def on_breakpoint_ignore(self, bpnum, count): """ The runner will ignore the referenced breakpoint "count" iterations. """ bp = self.bp_list[bpnum] bp.ignore = count self.view.debug_on_breakpoint_ignore(bp, count) def on_breakpoint_clear(self, bpnum): """ The runner has cleared the referenced breakpoint. """ bp = self.bp_list[bpnum] self.view.debug_on_breakpoint_clear(bp) def on_stack(self, stack): """ The runner has sent an update to the stack. """ self.stack = stack self.view.debug_on_stack(stack) def on_restart(self): """ The runner has restarted. """ self.view.debug_on_restart() def on_finished(self): """ The debug runner has finished running the script to be debugged. """ self.view.debug_on_finished() def on_call(self, args): """ The runner has called a function with the specified arguments. """ self.view.debug_on_call(args) def on_return(self, retval): """ The runner has returned from a function with the specified return value. """ self.view.debug_on_return(retval) def on_line(self, filename, line): """ The runner has moved to the specified line in the referenced file. """ self.view.debug_on_line(filename, line) def on_exception(self, name, value): """ The runner has encountered a named exception with an associated value. """ self.view.debug_on_exception(name, value) def on_postmortem(self, *args, **kwargs): """ The runner encountered a fatal error and has died. """ self.view.debug_on_postmortem(args, kwargs) def on_info(self, message): """ The runner has sent an informative message. """ logger.info('Debug runner says: {}'.format(message)) self.view.debug_on_info(message) def on_warning(self, message): """ The runner has sent a warning message. """ logger.warning('Debug runner says: {}'.format(message)) self.view.debug_on_warning(message) def on_error(self, message): """ The runner has sent an error message. """ logger.error('Debug runner says: {}'.format(message)) self.view.debug_on_error(message)
class FileLoader: def __init__(self, main_window, tab_data_table): super(FileLoader, self).__init__() self.tab_data_table = tab_data_table self.main_window = main_window # def __del__(self): # logging.info('Destructor call check to ensure it fires after complete execution') @staticmethod def is_csv_file(file_extension): return file_extension.lower() == ".csv" @staticmethod def is_excel_file(file_extension): return file_extension.lower() == ".xlsx" def load_csv(self): """ Loads the file from file selector to a table """ loaded_file_path = QFileDialog.getOpenFileName( self.main_window, "Load File", "", 'csv or xlxs(*.csv *.xlsx);;CSV(*.csv);; XLSX(*.xlsx)') filename, self.file_extension = os.path.splitext(loaded_file_path[0]) # Proceed if and only if a valid file is selected and the file dialog is not cancelled if loaded_file_path[0]: # Get only the file name from path. eg. 'data_file.csv' filepath = os.path.normpath(loaded_file_path[0]) filename = filepath.split(os.sep) self.csv_file_name = filename[-1] self.initUI() self.worker = Worker(file_path=loaded_file_path, file_extension=self.file_extension) self.thread = QThread() self.worker.current_progress.connect(self.set_progress_value) self.worker.read_values.connect(self.update_table_values) self.worker.moveToThread(self.thread) self.worker.max_progress_value.connect(self.set_max_progress_value) self.worker.finished.connect(self.task_finished) self.thread.started.connect(self.worker.load_file) self.thread.start() QApplication.restoreOverrideCursor() def update_table_values(self, row, col, value): item = QTableWidgetItem(value) self.tab_data_table.setItem(row, col, item) def initUI(self): # # Show waiting cursor till the time file is being processed QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) self.loading_progress = QProgressDialog("Reading Rows. Please wait...", None, 0, 0, self.main_window) if self.is_csv_file(self.file_extension): self.loading_progress.setWindowTitle("Loading CSV File...") elif self.is_excel_file(self.file_extension): self.loading_progress.setWindowTitle("Loading XLSX File...") self.loading_progress.setCancelButton(None) # enable custom window hint self.loading_progress.setWindowFlags( self.loading_progress.windowFlags() | QtCore.Qt.CustomizeWindowHint) # disable (but not hide) close button self.loading_progress.setWindowFlags( self.loading_progress.windowFlags() & ~QtCore.Qt.WindowCloseButtonHint) self.loading_progress.show() def set_progress_value(self, val): self.loading_progress.setValue(val) def set_max_progress_value(self, val): # Set the maximum progress for progressbar (fetch values from signal) self.loading_progress.setMaximum(val) self.loading_progress.setValue(0) def task_finished(self): logging.info("on_task_finish_called") # Stretch to fill the column width according to content # self.tab_data_table.resizeColumnsToContents() # Change the cursor back to normal QApplication.restoreOverrideCursor() # Properly manage quiting the thread self.thread.quit() # Wait for it to cleanup self.thread.wait() # Remove pointer to the current FileLoader so it can be GCed self.tab_data_table.setProperty("file_pointer", None)
class Pireal(QMainWindow): """ Main Window class This class is responsible for installing all application services. """ __SERVICES = {} __ACTIONS = {} # The name of items is the connection text TOOLBAR_ITEMS = [ 'create_database', 'open_database', 'save_database', '', # Is a separator! 'new_query', 'open_query', 'save_query', '', 'undo_action', 'redo_action', 'cut_action', 'copy_action', 'paste_action', '', 'create_new_relation', 'remove_relation', 'edit_relation', '', 'execute_queries' ] def __init__(self): QMainWindow.__init__(self) self.setWindowTitle(self.tr("Pireal")) self.setMinimumSize(700, 500) # Load window geometry qsettings = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat) window_maximized = qsettings.value('window_max', True, type=bool) if window_maximized: self.showMaximized() else: size = qsettings.value('window_size') self.resize(size) position = qsettings.value('window_pos') self.move(position) # Toolbar self.toolbar = QToolBar(self) self.toolbar.setIconSize(QSize(22, 22)) self.toolbar.setMovable(False) self.addToolBar(self.toolbar) # Menu bar menubar = self.menuBar() self.__load_menubar(menubar) # Load notification widget after toolbar actions notification_widget = Pireal.get_service("notification") self.toolbar.addWidget(notification_widget) # Message error self._msg_error_widget = message_error.MessageError(self) # Central widget central_widget = Pireal.get_service("central") central_widget.databaseSaved.connect(notification_widget.show_text) central_widget.querySaved.connect(notification_widget.show_text) self.setCentralWidget(central_widget) central_widget.add_start_page() # Check for updates self._thread = QThread() self._updater = updater.Updater() self._updater.moveToThread(self._thread) self._thread.started.connect(self._updater.check_updates) self._updater.finished.connect(self.__on_thread_update_finished) self._thread.start() notification_widget.show_text( self.tr("Checking for updates..."), time_out=0) # Install service Pireal.load_service("pireal", self) @classmethod def get_service(cls, service): """ Return the instance of a loaded service """ return cls.__SERVICES.get(service, None) @classmethod def load_service(cls, name, instance): """ Load a service providing the service name and the instance """ cls.__SERVICES[name] = instance @classmethod def get_action(cls, name): """ Return the instance of a loaded QAction """ return cls.__ACTIONS.get(name, None) @classmethod def load_action(cls, name, action): """ Load a QAction """ cls.__ACTIONS[name] = action def __load_menubar(self, menubar): """ This method installs the menubar and toolbar, menus and QAction's, also connects to a slot each QAction. """ from src.gui import menu_actions from src import keymap # Keymap kmap = keymap.KEYMAP # Toolbar items toolbar_items = {} central = Pireal.get_service("central") # Load menu bar for item in menu_actions.MENU: menubar_item = menu_actions.MENU[item] menu_name = menubar_item['name'] items = menubar_item['items'] menu = menubar.addMenu(menu_name) for menu_item in items: if isinstance(menu_item, str): # Is a separator menu.addSeparator() else: action = menu_item['name'] obj, connection = menu_item['slot'].split(':') if obj.startswith('central'): obj = central else: obj = self qaction = menu.addAction(action) # Icon name is connection icon = QIcon(":img/%s" % connection) qaction.setIcon(icon) # Install shortcuts shortcut = kmap.get(connection, None) if shortcut is not None: qaction.setShortcut(shortcut) # Items for toolbar if connection in Pireal.TOOLBAR_ITEMS: toolbar_items[connection] = qaction # The name of QAction is the connection Pireal.load_action(connection, qaction) slot = getattr(obj, connection, None) if isinstance(slot, Callable): qaction.triggered.connect(slot) # Install toolbar self.__install_toolbar(toolbar_items) # Disable some actions self.set_enabled_db_actions(False) self.set_enabled_relation_actions(False) self.set_enabled_query_actions(False) self.set_enabled_editor_actions(False) def __install_toolbar(self, toolbar_items): for action in Pireal.TOOLBAR_ITEMS: qaction = toolbar_items.get(action, None) if qaction is not None: self.toolbar.addAction(qaction) else: self.toolbar.addSeparator() def __show_status_message(self, msg): status = Pireal.get_service("status") status.show_message(msg) def __on_thread_update_finished(self): self._thread.quit() # Clear notificator notification_widget = Pireal.get_service("notification") notification_widget.clear() msg = QMessageBox(self) if not self._updater.error: if self._updater.version: version = self._updater.version msg.setWindowTitle(self.tr("New version available!")) msg.setText(self.tr("Check the web site to " "download <b>Pireal {}</b>".format( version))) download_btn = msg.addButton(self.tr("Download!"), QMessageBox.YesRole) msg.addButton(self.tr("Cancel"), QMessageBox.RejectRole) msg.exec_() r = msg.clickedButton() if r == download_btn: webbrowser.open_new( "http://centaurialpha.github.io/pireal") self._thread.deleteLater() self._updater.deleteLater() def change_title(self, title): self.setWindowTitle("Pireal " + '[' + title + ']') def set_enabled_db_actions(self, value): """ Public method. Enables or disables db QAction """ actions = [ 'new_query', 'open_query', 'close_database', 'save_database', 'save_database_as', 'load_relation' ] for action in actions: qaction = Pireal.get_action(action) qaction.setEnabled(value) def set_enabled_relation_actions(self, value): """ Public method. Enables or disables relation's QAction """ actions = [ 'create_new_relation', 'remove_relation', 'edit_relation' ] for action in actions: qaction = Pireal.get_action(action) qaction.setEnabled(value) def set_enabled_query_actions(self, value): """ Public method. Enables or disables queries QAction """ actions = [ 'execute_queries', 'save_query' ] for action in actions: qaction = Pireal.get_action(action) qaction.setEnabled(value) def set_enabled_editor_actions(self, value): """ Public slot. Enables or disables editor actions """ actions = [ 'undo_action', 'redo_action', 'copy_action', 'cut_action', 'paste_action', 'zoom_in', 'zoom_out', 'comment', 'uncomment' ] for action in actions: qaction = Pireal.get_action(action) qaction.setEnabled(value) def about_qt(self): """ Show about qt dialog """ QMessageBox.aboutQt(self) def about_pireal(self): """ Show the bout Pireal dialog """ from src.gui.dialogs import about_dialog dialog = about_dialog.AboutDialog(self) dialog.exec_() def report_issue(self): """ Open in the browser the page to create new issue """ webbrowser.open("http://github.com/centaurialpha/pireal/issues/new") def show_hide_menubar(self): """ Change visibility of menu bar """ if self.menuBar().isVisible(): self.menuBar().hide() else: self.menuBar().show() def show_hide_toolbar(self): """ Change visibility of tool bar """ if self.toolbar.isVisible(): self.toolbar.hide() else: self.toolbar.show() def show_error_message(self, text, syntax_error=True): self._msg_error_widget.show_msg(text, syntax_error) self._msg_error_widget.show() def closeEvent(self, event): qsettings = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat) # Save window geometry if self.isMaximized(): qsettings.setValue('window_max', True) else: qsettings.setValue('window_max', False) qsettings.setValue('window_pos', self.pos()) qsettings.setValue('window_size', self.size()) central_widget = Pireal.get_service("central") # Save recent databases qsettings.setValue('recent_databases', central_widget.recent_databases) db = central_widget.get_active_db() if db is not None: # Save splitters size db.save_sizes() # Databases unsaved if db.modified: msg = QMessageBox(self) msg.setIcon(QMessageBox.Question) msg.setWindowTitle(self.tr("Some changes where not saved")) msg.setText( self.tr("Do you want to save changes to the database?")) cancel_btn = msg.addButton(self.tr("Cancel"), QMessageBox.RejectRole) msg.addButton(self.tr("No"), QMessageBox.NoRole) yes_btn = msg.addButton(self.tr("Yes"), QMessageBox.YesRole) msg.exec_() r = msg.clickedButton() if r == yes_btn: central_widget.save_database() if r == cancel_btn: event.ignore() # Query files unsaved_editors = central_widget.get_unsaved_queries() if unsaved_editors: msg = QMessageBox(self) msg.setIcon(QMessageBox.Question) msg.setWindowTitle(self.tr("Unsaved Queries")) text = '\n'.join([editor.name for editor in unsaved_editors]) msg.setText(self.tr("{files}<br><br>Do you want to " "save them?".format(files=text))) cancel_btn = msg.addButton(self.tr("Cancel"), QMessageBox.RejectRole) msg.addButton(self.tr("No"), QMessageBox.NoRole) yes_btn = msg.addButton(self.tr("Yes"), QMessageBox.YesRole) msg.exec_() if msg.clickedButton() == yes_btn: for editor in unsaved_editors: central_widget.save_query(editor) if msg.clickedButton() == cancel_btn: event.ignore()
class Preferences(QDialog): # Signal to warn that the window is closed settingsClosed = pyqtSignal() def __init__(self, parent=None): super(Preferences, self).__init__(parent) # Main container # This contains a grid main_box = QVBoxLayout(self) main_box.setContentsMargins(200, 50, 200, 100) # The grid contains two containers # left container and right container grid = QGridLayout() # Left Container left_container = QVBoxLayout() left_container.setContentsMargins(0, 0, 0, 0) # General group_gral = QGroupBox(self.tr("General")) box_gral = QVBoxLayout(group_gral) # Updates btn_updates = QPushButton(self.tr("Check for updates")) box_gral.addWidget(btn_updates) # Language group_language = QGroupBox(self.tr("Language")) box = QVBoxLayout(group_language) # Find .qm files in language path available_langs = file_manager.get_files_from_folder( settings.LANGUAGE_PATH) languages = ["English"] + available_langs self._combo_lang = QComboBox() box.addWidget(self._combo_lang) self._combo_lang.addItems(languages) self._combo_lang.currentIndexChanged[int].connect( self._change_lang) if PSetting.LANGUAGE: self._combo_lang.setCurrentText(PSetting.LANGUAGE) box.addWidget(QLabel(self.tr("(Requires restart)"))) # Add widgets left_container.addWidget(group_gral) left_container.addWidget(group_language) left_container.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Expanding)) # Right Container right_container = QVBoxLayout() right_container.setContentsMargins(0, 0, 0, 0) # Editor editor_group = QGroupBox(self.tr("Editor Configurations")) box_editor = QHBoxLayout(editor_group) # Current line self._highlight_current_line = QCheckBox( self.tr("Highlight Current Line")) self._highlight_current_line.setChecked( PSetting.HIGHLIGHT_CURRENT_LINE) self._highlight_current_line.stateChanged[int].connect( self.__current_line_value_changed) box_editor.addWidget(self._highlight_current_line) # Matching paren self._matching_paren = QCheckBox(self.tr("Matching Parenthesis")) self._matching_paren.setChecked( PSetting.MATCHING_PARENTHESIS) self._matching_paren.stateChanged[int].connect( self.__set_enabled_matching_parenthesis) box_editor.addWidget(self._matching_paren) # Font group font_group = QGroupBox(self.tr("Font")) font_grid = QGridLayout(font_group) font_grid.addWidget(QLabel(self.tr("Family")), 0, 0) self._combo_font = QFontComboBox() self._combo_font.setCurrentFont(PSetting.FONT) font_grid.addWidget(self._combo_font, 0, 1) font_grid.addWidget(QLabel(self.tr("Point Size")), 1, 0) self._combo_font_size = QComboBox() fdb = QFontDatabase() combo_sizes = fdb.pointSizes(PSetting.FONT.family()) current_size_index = combo_sizes.index( PSetting.FONT.pointSize()) self._combo_font_size.addItems([str(f) for f in combo_sizes]) self._combo_font_size.setCurrentIndex(current_size_index) font_grid.addWidget(self._combo_font_size, 1, 1) right_container.addWidget(editor_group) right_container.addWidget(font_group) right_container.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Expanding)) # Add widgets grid.addLayout(left_container, 0, 0) grid.addLayout(right_container, 0, 1) main_box.addLayout(grid) # Button close and reset hbox = QHBoxLayout() hbox.setSpacing(20) hbox.addItem(QSpacerItem(1, 0, QSizePolicy.Expanding)) btn_cancel = QPushButton(self.tr("Back")) hbox.addWidget(btn_cancel) btn_reset = QPushButton(self.tr("Reset Configurations")) hbox.addWidget(btn_reset) main_box.addLayout(hbox) # Overlay self.overlay = overlay_widget.OverlayWidget(self) self.overlay.hide() # Effect and animations self.effect = QGraphicsOpacityEffect() self.setGraphicsEffect(self.effect) duration, x = 180, 150 # Animation duration # Animation start # Opacity animation self.opacity_animation_s = QPropertyAnimation(self.effect, b"opacity") self.opacity_animation_s.setDuration(duration) self.opacity_animation_s.setStartValue(0.0) self.opacity_animation_s.setEndValue(1.0) # X animation self.x_animation_s = QPropertyAnimation(self, b"geometry") self.x_animation_s.setDuration(duration) self.x_animation_s.setStartValue(QRect(x, 0, parent.width(), parent.height())) self.x_animation_s.setEndValue(QRect(0, 0, parent.width(), parent.height())) # Animation end # Opacity animation self.opacity_animation_e = QPropertyAnimation(self.effect, b"opacity") self.opacity_animation_e.setDuration(duration) self.opacity_animation_e.setStartValue(1.0) self.opacity_animation_e.setEndValue(0.0) # X animation self.x_animation_e = QPropertyAnimation(self, b"geometry") self.x_animation_e.setDuration(duration) self.x_animation_e.setStartValue(QRect(0, 0, parent.width(), parent.height())) self.x_animation_e.setEndValue(QRect(-x, 0, parent.width(), parent.height())) # Group animation start self.group_animation_s = QParallelAnimationGroup() self.group_animation_s.addAnimation(self.opacity_animation_s) self.group_animation_s.addAnimation(self.x_animation_s) # Group animation end self.group_animation_e = QParallelAnimationGroup() self.group_animation_e.addAnimation(self.opacity_animation_e) self.group_animation_e.addAnimation(self.x_animation_e) # Connections self.group_animation_e.finished.connect( self._on_group_animation_finished) btn_cancel.clicked.connect(self.close) btn_reset.clicked.connect(self._reset_settings) btn_updates.clicked.connect(self._check_for_updates) # self.thread.finished.connect(self._on_thread_finished) self._combo_font.currentFontChanged.connect( self._change_font) self._combo_font_size.currentTextChanged.connect( self._change_font_size) def __current_line_value_changed(self, value): qs = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat) qs.setValue('highlight_current_line', value) PSetting.HIGHLIGHT_CURRENT_LINE = value def __set_enabled_matching_parenthesis(self, value): qs = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat) qs.setValue("matching_parenthesis", value) PSetting.MATCHING_PARENTHESIS = value def _change_font(self, font): # FIXME: un quilombo esto central = Pireal.get_service("central") mcontainer = central.get_active_db() if mcontainer is not None: query_widget = mcontainer.query_container.currentWidget() if query_widget is not None: weditor = query_widget.get_editor() if weditor is not None: qs = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat) weditor.set_font(font) qs.setValue("font", font) def _change_font_size(self, size): # FIXME: un quilombo esto font = self._combo_font.currentFont() font.setPointSize(int(size)) central = Pireal.get_service("central") mcontainer = central.get_active_db() if mcontainer is not None: query_widget = mcontainer.query_container.currentWidget() if query_widget is not None: weditor = query_widget.get_editor() if weditor is not None: qs = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat) weditor.set_font(font) qs.setValue("font", font) def showEvent(self, event): super(Preferences, self).showEvent(event) self.group_animation_s.start() def resizeEvent(self, event): self.overlay.resize(self.size()) event.accept() def done(self, result): self.res = result self.group_animation_e.start() def _on_group_animation_finished(self): super(Preferences, self).done(self.res) self.settingsClosed.emit() def _check_for_updates(self): # Thread self._thread = QThread() self._updater = updater.Updater() self._updater.moveToThread(self._thread) self._thread.started.connect(self._updater.check_updates) self._updater.finished.connect(self.__on_thread_update_finished) # Show overlay widget self.overlay.show() # Start thread self._thread.start() def __on_thread_update_finished(self): # Hide overlay widget self.overlay.hide() self._thread.quit() msg = QMessageBox(self) if not self._updater.error: if self._updater.version: version = self._updater.version msg.setWindowTitle(self.tr("New version available!")) msg.setText(self.tr("Check the web site to " "download <b>Pireal {}</b>".format( version))) download_btn = msg.addButton(self.tr("Download!"), QMessageBox.YesRole) msg.addButton(self.tr("Cancel"), QMessageBox.RejectRole) msg.exec_() r = msg.clickedButton() if r == download_btn: webbrowser.open_new( "http://centaurialpha.github.io/pireal") else: msg.setWindowTitle(self.tr("Information")) msg.setText(self.tr("Last version installed")) msg.addButton(self.tr("Ok"), QMessageBox.AcceptRole) msg.exec_() else: msg.critical(self, self.tr("Error"), self.tr("Connection error")) self._thread.deleteLater() self._updater.deleteLater() def _reset_settings(self): """ Remove all settings """ msg = QMessageBox(self) msg.setWindowTitle(self.tr("Reset Settings")) msg.setText(self.tr("Are you sure you want to clear all settings?")) msg.setIcon(QMessageBox.Question) msg.addButton(self.tr("No"), QMessageBox.NoRole) yes_btn = msg.addButton(self.tr("Yes"), QMessageBox.YesRole) msg.exec_() r = msg.clickedButton() if r == yes_btn: QSettings(settings.SETTINGS_PATH, QSettings.IniFormat).clear() self.close() def _change_lang(self, index): lang = self._combo_lang.itemText(index) qs = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat) qs.setValue('language', lang)
class RechgEvalWidget(QFrameLayout): sig_new_gluedf = QSignal(GLUEDataFrameBase) def __init__(self, parent): super(RechgEvalWidget, self).__init__(parent) self.setWindowTitle('Recharge Calibration Setup') self.setWindowFlags(Qt.Window) self.wxdset = None self.wldset = None self.figstack = FigureStackManager(parent=self) self.progressbar = QProgressBar() self.progressbar.setValue(0) self.progressbar.hide() self.__initUI__() # Set the worker and thread mechanics self.rechg_worker = RechgEvalWorker() self.rechg_worker.sig_glue_finished.connect(self.receive_glue_calcul) self.rechg_worker.sig_glue_progress.connect(self.progressbar.setValue) self.rechg_thread = QThread() self.rechg_worker.moveToThread(self.rechg_thread) self.rechg_thread.started.connect(self.rechg_worker.eval_recharge) def __initUI__(self): class QRowLayout(QWidget): def __init__(self, items, parent=None): super(QRowLayout, self).__init__(parent) layout = QGridLayout() for col, item in enumerate(items): layout.addWidget(item, 0, col) layout.setContentsMargins(0, 0, 0, 0) layout.setColumnStretch(0, 100) self.setLayout(layout) # ---- Parameters # Specific yield (Sy) : self.QSy_min = QDoubleSpinBox(0.05, 3) self.QSy_min.setRange(0.001, 1) self.QSy_max = QDoubleSpinBox(0.2, 3) self.QSy_max.setRange(0.001, 1) # Maximum readily available water (RASmax) : # units=' mm' self.QRAS_min = QDoubleSpinBox(5) self.QRAS_min.setRange(0, 999) self.QRAS_max = QDoubleSpinBox(40) self.QRAS_max.setRange(0, 999) # Runoff coefficient (Cro) : self.CRO_min = QDoubleSpinBox(0.1, 3) self.CRO_min.setRange(0, 1) self.CRO_max = QDoubleSpinBox(0.3, 3) self.CRO_max.setRange(0, 1) # Snowmelt parameters : # units=' °C' self._Tmelt = QDoubleSpinBox(0, 1) self._Tmelt.setRange(-25, 25) # units=' mm/°C' self._CM = QDoubleSpinBox(4, 1, 0.1, ) self._CM.setRange(0.1, 100) # units=' days' self._deltaT = QDoubleSpinBox(0, 0, ) self._deltaT.setRange(0, 999) class QLabelCentered(QLabel): def __init__(self, text): super(QLabelCentered, self).__init__(text) self.setAlignment(Qt.AlignCenter) # ---- Parameters params_group = QFrameLayout() params_group.setContentsMargins(10, 5, 10, 0) # (L, T, R, B) params_group.setObjectName("viewport") params_group.setStyleSheet("#viewport {background-color:transparent;}") row = 0 params_group.addWidget(QLabel('Sy :'), row, 0) params_group.addWidget(self.QSy_min, row, 1) params_group.addWidget(QLabelCentered('to'), row, 2) params_group.addWidget(self.QSy_max, row, 3) row += 1 params_group.addWidget(QLabel('RAS<sub>max</sub> :'), row, 0) params_group.addWidget(self.QRAS_min, row, 1) params_group.addWidget(QLabelCentered('to'), row, 2) params_group.addWidget(self.QRAS_max, row, 3) params_group.addWidget(QLabel('mm'), row, 4) row += 1 params_group.addWidget(QLabel('Cro :'), row, 0) params_group.addWidget(self.CRO_min, row, 1) params_group.addWidget(QLabelCentered('to'), row, 2) params_group.addWidget(self.CRO_max, row, 3) row += 1 params_group.setRowMinimumHeight(row, 10) row += 1 params_group.addWidget(QLabel('Tmelt :'), row, 0) params_group.addWidget(self._Tmelt, row, 1) params_group.addWidget(QLabel('°C'), row, 2, 1, 3) row += 1 params_group.addWidget(QLabel('CM :'), row, 0) params_group.addWidget(self._CM, row, 1) params_group.addWidget(QLabel('mm/°C'), row, 2, 1, 3) row += 1 params_group.addWidget(QLabel('deltaT :'), row, 0) params_group.addWidget(self._deltaT, row, 1) params_group.addWidget(QLabel('days'), row, 2, 1, 3) row += 1 params_group.setRowStretch(row, 100) params_group.setColumnStretch(5, 100) # ---- Layout ---- qtitle = QLabel('Parameter Range') qtitle.setAlignment(Qt.AlignCenter) sa = QScrollArea() sa.setWidget(params_group) sa.setWidgetResizable(True) sa.setFrameStyle(0) sa.setStyleSheet("QScrollArea {background-color:transparent;}") sa.setSizePolicy(QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Preferred)) # ---- Main Layout self.addWidget(qtitle, 0, 0) self.addWidget(HSep(), 1, 0) self.addWidget(sa, 2, 0) self.addWidget(HSep(), 3, 0) self.setRowMinimumHeight(4, 5) self.addWidget(self.setup_toolbar(), 5, 0) self.setRowStretch(2, 100) self.setVerticalSpacing(5) self.setContentsMargins(0, 0, 0, 10) # (L, T, R, B) def setup_toolbar(self): """Setup the toolbar of the widget. """ toolbar = QWidget() btn_calib = QPushButton('Compute Recharge') btn_calib.clicked.connect(self.btn_calibrate_isClicked) self.btn_show_result = QToolButtonSmall(icons.get_icon('search')) self.btn_show_result.clicked.connect(self.figstack.show) self.btn_show_result.setToolTip("Show GLUE results.") self.btn_save_glue = ExportGLUEButton(self.wxdset) layout = QGridLayout(toolbar) layout.addWidget(btn_calib, 0, 0) layout.addWidget(self.btn_show_result, 0, 1) layout.addWidget(self.btn_save_glue, 0, 2) layout.setContentsMargins(10, 0, 10, 0) # (L, T, R, B) return toolbar def set_wldset(self, wldset): """Set the namespace for the water level dataset.""" self.wldset = wldset self.setEnabled(self.wldset is not None and self.wxdset is not None) gluedf = None if wldset is None else wldset.get_glue_at(-1) self._setup_ranges_from_wldset(gluedf) self.figstack.set_gluedf(gluedf) self.btn_save_glue.set_model(gluedf) def set_wxdset(self, wxdset): """Set the namespace for the weather dataset.""" self.wxdset = wxdset self.setEnabled(self.wldset is not None and self.wxdset is not None) def _setup_ranges_from_wldset(self, gluedf): """ Set the parameter range values from the last values that were used to produce the last GLUE results saved into the project. """ if gluedf is not None: self.QSy_min.setValue(min(gluedf['ranges']['Sy'])) self.QSy_max.setValue(max(gluedf['ranges']['Sy'])) self.CRO_min.setValue(min(gluedf['ranges']['Cro'])) self.CRO_max.setValue(max(gluedf['ranges']['Cro'])) self.QRAS_min.setValue(min(gluedf['ranges']['RASmax'])) self.QRAS_max.setValue(max(gluedf['ranges']['RASmax'])) self._Tmelt.setValue(gluedf['params']['tmelt']) self._CM.setValue(gluedf['params']['CM']) self._deltaT.setValue(gluedf['params']['deltat']) def get_Range(self, name): if name == 'Sy': return [self.QSy_min.value(), self.QSy_max.value()] elif name == 'RASmax': return [self.QRAS_min.value(), self.QRAS_max.value()] elif name == 'Cro': return [self.CRO_min.value(), self.CRO_max.value()] else: raise ValueError('Name must be either Sy, Rasmax or Cro.') @property def Tmelt(self): return self._Tmelt.value() @property def CM(self): return self._CM.value() @property def deltaT(self): return self._deltaT.value() def btn_calibrate_isClicked(self): """ Handles when the button to compute recharge and its uncertainty is clicked. """ self.start_glue_calcul() def start_glue_calcul(self): """ Start the method to evaluate ground-water recharge and its uncertainty. """ # Set the parameter ranges. self.rechg_worker.Sy = self.get_Range('Sy') self.rechg_worker.Cro = self.get_Range('Cro') self.rechg_worker.RASmax = self.get_Range('RASmax') self.rechg_worker.TMELT = self.Tmelt self.rechg_worker.CM = self.CM self.rechg_worker.deltat = self.deltaT # Set the data and check for errors. error = self.rechg_worker.load_data(self.wxdset, self.wldset) if error is not None: QMessageBox.warning(self, 'Warning', error, QMessageBox.Ok) return # Start the computation of groundwater recharge. self.progressbar.show() waittime = 0 while self.rechg_thread.isRunning(): time.sleep(0.1) waittime += 0.1 if waittime > 15: print('Impossible to quit the thread.') return self.rechg_thread.start() def receive_glue_calcul(self, glue_dataframe): """ Handle the plotting of the results once ground-water recharge has been evaluated. """ self.rechg_thread.quit() self.progressbar.hide() if glue_dataframe is None: msg = ("Recharge evaluation was not possible because all" " the models produced were deemed non-behavioural." "\n\n" "This usually happens when the range of values for" " Sy, RASmax, and Cro are too restrictive or when the" " Master Recession Curve (MRC) does not represent well the" " behaviour of the observed hydrograph.") QMessageBox.warning(self, 'Warning', msg, QMessageBox.Ok) else: self.wldset.clear_glue() self.wldset.save_glue(glue_dataframe) self.sig_new_gluedf.emit(glue_dataframe) self.btn_save_glue.set_model(glue_dataframe) self.figstack.set_gluedf(glue_dataframe)
class Preferences(QDialog): settingsClosed = pyqtSignal() def __init__(self, parent=None): QDialog.__init__(self, parent) self.__need_restart = False box = QVBoxLayout(self) box.setContentsMargins(0, 0, 0, 0) view = QQuickWidget() view.setResizeMode(QQuickWidget.SizeRootObjectToView) qml = os.path.join(settings.QML_PATH, "Preferences.qml") view.setSource(QUrl.fromLocalFile(qml)) box.addWidget(view) self.__root = view.rootObject() # Lista de idiomas para el Combo qml available_langs = file_manager.get_files_from_folder( settings.LANGUAGE_PATH) langs = ["English"] + available_langs self.__root.addLangsToCombo(langs) self.__root.setCurrentLanguage(CONFIG.get("language")) font = CONFIG.get("fontFamily") size = CONFIG.get("fontSize") if font is None: font, size = CONFIG._get_font() self.__root.setFontFamily(font, size) self.__root.setInitialStates( CONFIG.get("highlightCurrentLine"), CONFIG.get("matchParenthesis")) # Conexiones self.__root.close.connect(lambda: self.settingsClosed.emit()) self.__root.resetSettings.connect(self.__reset_settings) self.__root.checkForUpdates.connect(self.__check_for_updates) self.__root.changeLanguage.connect(self.__change_language) self.__root.stateCurrentLineChanged[bool].connect( self.__on_state_current_line_changed) self.__root.stateMatchingParenChanged[bool].connect( self.__on_state_matching_parenthesis_changed) self.__root.needChangeFont.connect(self.__change_font) @pyqtSlot() def __change_font(self): font = CONFIG.get("fontFamily") size = CONFIG.get("fontSize") if font is None: font, size = CONFIG._get_font() font, ok = QFontDialog.getFont(QFont(font, size), self) if ok: CONFIG.set_value("fontFamily", font.family()) CONFIG.set_value("fontSize", font.pointSize()) central = Pireal.get_service("central") mcontainer = central.get_active_db() if mcontainer is not None: query_widget = mcontainer.query_container.currentWidget() if query_widget is not None: weditor = query_widget.get_editor() if weditor is not None: weditor.set_font(font.family(), font.pointSize()) # Cambio el texto en la interfáz QML self.__root.setFontFamily(font.family(), font.pointSize()) @pyqtSlot(bool) def __on_state_current_line_changed(self, state): CONFIG.set_value("highlightCurrentLine", state) @pyqtSlot(bool) def __on_state_matching_parenthesis_changed(self, state): CONFIG.set_value("matchParenthesis", state) @pyqtSlot('QString') def __change_language(self, lang): qsettings = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat) current_lang = qsettings.value('language', 'English') if current_lang != lang: qsettings.setValue('language', lang) self.__need_restart = True @pyqtSlot() def __check_for_updates(self): # Thread self._thread = QThread() self._updater = updater.Updater() self._updater.moveToThread(self._thread) self._thread.started.connect(self._updater.check_updates) self._updater.finished.connect(self.__on_thread_update_finished) # Start thread self._thread.start() @pyqtSlot() def __on_thread_update_finished(self): self._thread.quit() msg = QMessageBox(self) if not self._updater.error: if self._updater.version: version = self._updater.version msg.setWindowTitle(self.tr("New version available!")) msg.setText(self.tr("Check the web site to " "download <b>Pireal {}</b>".format( version))) download_btn = msg.addButton(self.tr("Download!"), QMessageBox.YesRole) msg.addButton(self.tr("Cancel"), QMessageBox.RejectRole) msg.exec_() r = msg.clickedButton() if r == download_btn: webbrowser.open_new( "http://centaurialpha.github.io/pireal") else: # Cierro BusyIndicator de qml self.__root.threadFinished() msg.setWindowTitle(self.tr("Information")) msg.setText(self.tr("Last version installed")) msg.addButton(self.tr("Ok"), QMessageBox.AcceptRole) msg.exec_() else: msg.critical(self, self.tr("Error"), self.tr("Connection error")) self._thread.deleteLater() self._updater.deleteLater() self.__root.threadFinished() @pyqtSlot() def __reset_settings(self): """ Remove all settings """ msg = QMessageBox(self) msg.setWindowTitle(self.tr("Reset Settings")) msg.setText(self.tr("Are you sure you want to clear all settings?")) msg.setIcon(QMessageBox.Question) msg.addButton(self.tr("No"), QMessageBox.NoRole) yes_btn = msg.addButton(self.tr("Yes"), QMessageBox.YesRole) msg.exec_() r = msg.clickedButton() if r == yes_btn: QSettings(settings.SETTINGS_PATH, QSettings.IniFormat).clear()
class BAONQtApplication(QApplication): BACKUP_DIALOG_CAPTION = 'Rename Plan Backup Detected' BACKUP_DIALOG_ERROR_CAPTION = 'Error' BACKUP_INTRO_TEXT = 'BAON has detected a backed up rename plan from a previous run of the '\ 'application. This suggests that the application crashed partway through executing a rename operation. The '\ 'files may have been left in an inconsistent state.' BACKUP_PROMPT_TEXT = 'What do you want to do?' REVERT_BACKUP_PROGRESS_TEXT = 'Reverting the rename operation' SUCCESS_DIALOG_CAPTION = 'Success' WARNING_DIALOG_CAPTION = 'Warning' BACKUP_DELETED_DIALOG_TEXT =\ 'The backed up plan has been deleted. Further runs of the application will proceed normally.' BACKUP_REVERTED_SUCCESS_DIALOG_TEXT = 'The rename operation has been reverted successfully. The directory state '\ 'should now have been completely restored.' BACKUP_REVERTED_WARNING_DIALOG_TEXT = 'There were inconsistencies while trying to revert the previous rename '\ 'operation. The directory state may not have been fully restored.' REVERT_BACKUP_BUTTON_TEXT = 'Revert the rename (recommended)' DELETE_BACKUP_BUTTON_TEXT = 'Delete the backup file' EXAMINE_BACKUP_BUTTON_TEXT = 'Examine the plan in a text editor' QUIT_BUTTON_TEXT = 'Quit BAON' request_revert_backup = pyqtSignal() request_delete_backup = pyqtSignal() _main_window = None _core = None _progress_dialog = None _core_thread = None def __init__(self, args): super().__init__(sys.argv) # Actually we do quit when the last window is closed, but we need to do this in a more controlled way self.setQuitOnLastWindowClosed(False) self._init_threads() self._init_main_objects(args) self._connect_main_objects() self._start_core() def event(self, evt): if isinstance(evt, QFileOpenEvent): path = evt.file() if not os.path.isdir(path): path, _ = os.path.split(path) self._main_window.set_base_path(path) return True return super().event(evt) def _init_threads(self): self._core_thread = QThread() self._core_thread.start() def _init_main_objects(self, args): self._main_window = MainWindow(args) self._core = BAONQtCore(args) self._core.moveToThread(self._core_thread) def _connect_main_objects(self): self.aboutToQuit.connect(self._on_quit) # Core vs. application self._core.request_backup_decision.connect(self.backup_decision_requested) self._core.reverted_backup.connect(self.notify_backup_reverted) self._core.revert_backup_error.connect(self.handle_backup_op_error) self._core.deleted_backup.connect(self.notify_backup_deleted) self._core.delete_backup_error.connect(self.handle_backup_op_error) self.request_revert_backup.connect(self._core.revert_backup) self.request_delete_backup.connect(self._core.delete_backup) # Core vs. main window self._core.prologue_finished.connect(self._main_window.show_first_time) self._core.status_changed.connect(self._main_window.report_status) self._core.scanned_files_updated.connect(self._main_window.update_scanned_files) self._core.renamed_files_updated.connect(self._main_window.update_renamed_files) self._core.has_shutdown.connect(self.quit) self._main_window.base_path_edited.connect(self._core.update_base_path) self._main_window.scan_recursive_changed.connect(self._core.update_scan_recursive) self._main_window.rules_text_changed.connect(self._core.update_rules_text) self._main_window.use_path_changed.connect(self._core.update_use_path) self._main_window.use_extension_changed.connect(self._core.update_use_extension) self._main_window.request_add_override.connect(self._core.add_override) self._main_window.request_remove_override.connect(self._core.remove_override) self._main_window.request_do_rename.connect(self._core.do_rename) self._main_window.request_rescan.connect(self._core.rescan) self._main_window.rejected.connect(self._core.shutdown) def _start_core(self): QMetaObject.invokeMethod(self._core, 'start', Qt.QueuedConnection) def _on_quit(self): self._core_thread.quit() self._core_thread.wait() @pyqtSlot() def backup_decision_requested(self): self._show_backup_decision() @pyqtSlot(Exception) def handle_backup_op_error(self, error): self._close_progress_dialog() self._show_backup_decision(error=error) @pyqtSlot() def notify_backup_deleted(self): QMessageBox.information( None, self.SUCCESS_DIALOG_CAPTION, self.BACKUP_DELETED_DIALOG_TEXT, ) @pyqtSlot(bool) def notify_backup_reverted(self, complete_success): self._close_progress_dialog() if complete_success: QMessageBox.information( None, self.SUCCESS_DIALOG_CAPTION, self.BACKUP_REVERTED_SUCCESS_DIALOG_TEXT, ) else: QMessageBox.warning( None, self.WARNING_DIALOG_CAPTION, self.BACKUP_REVERTED_WARNING_DIALOG_TEXT, ) def _show_backup_decision(self, error=None): text = '<p>{0}</p><p>{1}</p>'.format( self.BACKUP_INTRO_TEXT if error is None else error, self.BACKUP_PROMPT_TEXT, ) dialog = QMessageBox( QMessageBox.Question if error is None else QMessageBox.Critical, self.BACKUP_DIALOG_CAPTION if error is None else self.BACKUP_DIALOG_ERROR_CAPTION, text, ) revert_button = dialog.addButton(self.REVERT_BACKUP_BUTTON_TEXT, QMessageBox.AcceptRole) delete_button = dialog.addButton(self.DELETE_BACKUP_BUTTON_TEXT, QMessageBox.DestructiveRole) examine_button = dialog.addButton(self.EXAMINE_BACKUP_BUTTON_TEXT, QMessageBox.ActionRole) dialog.addButton(self.QUIT_BUTTON_TEXT, QMessageBox.RejectRole) dialog.exec() clicked_button = dialog.clickedButton() if clicked_button == examine_button: QMetaObject.invokeMethod(self, '_examine_backup', Qt.QueuedConnection) elif clicked_button == revert_button: self._progress_dialog = QProgressDialog(None) self._progress_dialog.setLabelText(self.REVERT_BACKUP_PROGRESS_TEXT) self._progress_dialog.setCancelButton(None) self._progress_dialog.setRange(0, 0) self._progress_dialog.forceShow() self.request_revert_backup.emit() elif clicked_button == delete_button: self.request_delete_backup.emit() else: self.quit() @pyqtSlot() def _examine_backup(self): error = None try: filename = get_rename_plan_backup_filename() QDesktopServices.openUrl(QUrl.fromLocalFile(filename)) except Exception as err: error = err finally: self._show_backup_decision(error) def _close_progress_dialog(self): if self._progress_dialog is not None: self._progress_dialog.close() self._progress_dialog = None
class MainGUI(QMainWindow): """The main GUI for azimuthal integration.""" _root_dir = osp.dirname(osp.abspath(__file__)) start_sgn = pyqtSignal() stop_sgn = pyqtSignal() quit_sgn = pyqtSignal() _db = RedisConnection() _WIDTH, _HEIGHT = config['GUI_MAIN_GUI_SIZE'] def __init__(self, pause_ev, close_ev): """Initialization.""" super().__init__() self._pause_ev = pause_ev self._close_ev = close_ev self._input_update_ev = Event() self._input = MpInQueue(self._input_update_ev, pause_ev, close_ev) self._pulse_resolved = config["PULSE_RESOLVED"] self._require_geometry = config["REQUIRE_GEOMETRY"] self._queue = deque(maxlen=1) self.setAttribute(Qt.WA_DeleteOnClose) self.title = f"EXtra-foam {__version__} ({config['DETECTOR']})" self.setWindowTitle(self.title + " - main GUI") # ************************************************************* # Central widget # ************************************************************* self._ctrl_widgets = [] # book-keeping control widgets self._cw = QSplitter() self._cw.setChildrenCollapsible(False) self.setCentralWidget(self._cw) self._left_cw_container = QScrollArea() self._left_cw_container.setFrameShape(QFrame.NoFrame) self._left_cw = QTabWidget() self._right_cw_container = QScrollArea() self._right_cw_container.setFrameShape(QFrame.NoFrame) self._right_cw = QSplitter(Qt.Vertical) self._right_cw.setChildrenCollapsible(False) self._source_cw = self.createCtrlWidget(DataSourceWidget) self._extension_cw = self.createCtrlWidget(ExtensionCtrlWidget) self._ctrl_panel_cw = QTabWidget() self._analysis_cw = QWidget() self._statistics_cw = QWidget() self._util_panel_container = QWidget() self._util_panel_cw = QTabWidget() # ************************************************************* # Tool bar # Note: the order of '_addAction` affect the unittest!!! # ************************************************************* self._tool_bar = self.addToolBar("Control") # make icon a bit larger self._tool_bar.setIconSize(2 * self._tool_bar.iconSize()) self._start_at = self._addAction("Start bridge", "start.png") self._start_at.triggered.connect(self.onStart) self._stop_at = self._addAction("Stop bridge", "stop.png") self._stop_at.triggered.connect(self.onStop) self._stop_at.setEnabled(False) self._tool_bar.addSeparator() image_tool_at = self._addAction("Image tool", "image_tool.png") image_tool_at.triggered.connect(lambda: (self._image_tool.show( ), self._image_tool.activateWindow())) open_poi_window_at = self._addAction("Pulse-of-interest", "poi.png") open_poi_window_at.triggered.connect( functools.partial(self.onOpenPlotWindow, PulseOfInterestWindow)) if not self._pulse_resolved: open_poi_window_at.setEnabled(False) pump_probe_window_at = self._addAction("Pump-probe", "pump-probe.png") pump_probe_window_at.triggered.connect( functools.partial(self.onOpenPlotWindow, PumpProbeWindow)) open_correlation_window_at = self._addAction("Correlation", "correlation.png") open_correlation_window_at.triggered.connect( functools.partial(self.onOpenPlotWindow, CorrelationWindow)) open_histogram_window_at = self._addAction("Histogram", "histogram.png") open_histogram_window_at.triggered.connect( functools.partial(self.onOpenPlotWindow, HistogramWindow)) open_bin2d_window_at = self._addAction("Binning", "binning.png") open_bin2d_window_at.triggered.connect( functools.partial(self.onOpenPlotWindow, BinningWindow)) self._tool_bar.addSeparator() open_file_stream_window_at = self._addAction("File stream", "file_stream.png") open_file_stream_window_at.triggered.connect( lambda: self.onOpenSatelliteWindow(FileStreamWindow)) open_about_at = self._addAction("About EXtra-foam", "about.png") open_about_at.triggered.connect( lambda: self.onOpenSatelliteWindow(AboutWindow)) # ************************************************************* # Miscellaneous # ************************************************************* # book-keeping opened windows self._plot_windows = WeakKeyDictionary() self._satellite_windows = WeakKeyDictionary() self._gui_logger = GuiLogger(parent=self) logger.addHandler(self._gui_logger) self._analysis_setup_manager = AnalysisSetupManager() self._thread_logger = ThreadLoggerBridge() self.quit_sgn.connect(self._thread_logger.stop) self._thread_logger_t = QThread() self._thread_logger.moveToThread(self._thread_logger_t) self._thread_logger_t.started.connect(self._thread_logger.recv) self._thread_logger.connectToMainThread(self) # For real time plot self._running = False self._plot_timer = QTimer() self._plot_timer.timeout.connect(self.updateAll) # For checking the connection to the Redis server self._redis_timer = QTimer() self._redis_timer.timeout.connect(self.pingRedisServer) self.__redis_connection_fails = 0 self._mon_proxy = MonProxy() # ************************************************************* # control widgets # ************************************************************* # analysis control widgets self.analysis_ctrl_widget = self.createCtrlWidget(AnalysisCtrlWidget) self.fom_filter_ctrl_widget = self.createCtrlWidget( FomFilterCtrlWidget) # ************************************************************* # status bar # ************************************************************* # StatusBar to display topic name self.statusBar().showMessage(f"TOPIC: {config['TOPIC']}") self.statusBar().setStyleSheet("QStatusBar{font-weight:bold;}") # ImageToolWindow is treated differently since it is the second # control window. self._image_tool = ImageToolWindow( queue=self._queue, pulse_resolved=self._pulse_resolved, require_geometry=self._require_geometry, parent=self) self.initUI() self.initConnections() self.updateMetaData() self._analysis_setup_manager.onInit() self.setMinimumSize(640, 480) self.resize(self._WIDTH, self._HEIGHT) self.show() def createCtrlWidget(self, widget_class): widget = widget_class(pulse_resolved=self._pulse_resolved, require_geometry=self._require_geometry, parent=self) self._ctrl_widgets.append(widget) return widget def initUI(self): self.initLeftUI() self.initRightUI() self._cw.addWidget(self._left_cw_container) self._cw.addWidget(self._right_cw_container) self._cw.setSizes([self._WIDTH * 0.6, self._WIDTH * 0.4]) def initLeftUI(self): self._left_cw.setTabPosition(QTabWidget.TabPosition.West) self._left_cw.addTab(self._source_cw, "Data source") self._left_cw_container.setWidget(self._left_cw) self._left_cw_container.setWidgetResizable(True) self._left_cw.addTab(self._extension_cw, "Extension") def initRightUI(self): self.initCtrlUI() self.initUtilUI() self._right_cw.addWidget(self._ctrl_panel_cw) self._right_cw.addWidget(self._util_panel_container) self._right_cw_container.setWidget(self._right_cw) self._right_cw_container.setWidgetResizable(True) def initCtrlUI(self): self.initGeneralAnalysisUI() self._ctrl_panel_cw.addTab(self._analysis_cw, "General analysis setup") def initGeneralAnalysisUI(self): layout = QVBoxLayout() layout.addWidget(self.analysis_ctrl_widget) layout.addWidget(self.fom_filter_ctrl_widget) self._analysis_cw.setLayout(layout) def initUtilUI(self): self._util_panel_cw.addTab(self._gui_logger.widget, "Logger") self._util_panel_cw.addTab(self._analysis_setup_manager, "Analysis Setup Manager") self._util_panel_cw.setTabPosition(QTabWidget.TabPosition.South) layout = QVBoxLayout() layout.addWidget(self._util_panel_cw) self._util_panel_container.setLayout(layout) def initConnections(self): self._analysis_setup_manager.load_metadata_sgn.connect( self.loadMetaData) def connect_input_to_output(self, output): self._input.connect(output) @profiler("Update Plots", process_time=True) def updateAll(self): """Update all the plots in the main and child windows.""" if not self._running: return try: processed = self._input.get() self._queue.append(processed) except Empty: return # clear the previous plots no matter what comes next # for w in self._plot_windows.keys(): # w.reset() data = self._queue[0] self._image_tool.updateWidgetsF() for w in itertools.chain(self._plot_windows): try: w.updateWidgetsF() except Exception as e: exc_type, exc_value, exc_traceback = sys.exc_info() logger.debug( repr(traceback.format_tb(exc_traceback)) + repr(e)) logger.error(f"[Update plots] {repr(e)}") logger.debug(f"Plot train with ID: {data.tid}") def pingRedisServer(self): try: self._db.ping() if self.__redis_connection_fails > 0: # Note: Indeed, we do not have mechanism to recover from # a Redis server crash. It is recommended to restart # Extra-foam if you encounter this situation. logger.info("Reconnect to the Redis server!") self.__redis_connection_fails = 0 except ConnectionError: self.__redis_connection_fails += 1 rest_attempts = config["REDIS_MAX_PING_ATTEMPTS"] - \ self.__redis_connection_fails if rest_attempts > 0: logger.warning(f"No response from the Redis server! Shut " f"down after {rest_attempts} attempts ...") else: logger.warning(f"No response from the Redis server! " f"Shutting down!") self.close() def _addAction(self, description, filename): icon = QIcon(osp.join(self._root_dir, "icons/" + filename)) action = QAction(icon, description, self) self._tool_bar.addAction(action) return action def onOpenPlotWindow(self, instance_type): """Open a plot window if it does not exist. Otherwise bring the opened window to the table top. """ if self.checkWindowExistence(instance_type, self._plot_windows): return return instance_type(self._queue, pulse_resolved=self._pulse_resolved, require_geometry=self._require_geometry, parent=self) def onOpenSatelliteWindow(self, instance_type): """Open a satellite window if it does not exist. Otherwise bring the opened window to the table top. """ if self.checkWindowExistence(instance_type, self._satellite_windows): return return instance_type(parent=self) def checkWindowExistence(self, instance_type, windows): for key in windows: if isinstance(key, instance_type): key.activateWindow() return True return False def registerWindow(self, instance): self._plot_windows[instance] = 1 def unregisterWindow(self, instance): del self._plot_windows[instance] def registerSatelliteWindow(self, instance): self._satellite_windows[instance] = 1 def unregisterSatelliteWindow(self, instance): del self._satellite_windows[instance] @property def input(self): return self._input def start(self): """Start running. ProcessWorker interface. """ self._thread_logger_t.start() self._plot_timer.start(config["GUI_PLOT_UPDATE_TIMER"]) self._redis_timer.start(config["REDIS_PING_ATTEMPT_INTERVAL"]) self._input.start() def onStart(self): if not self.updateMetaData(): return self.start_sgn.emit() self._start_at.setEnabled(False) self._stop_at.setEnabled(True) for widget in self._ctrl_widgets: widget.onStart() for win in self._plot_windows: win.onStart() self._image_tool.onStart() self._analysis_setup_manager.onStart() self._running = True # starting to update plots self._input_update_ev.set() # notify update def onStop(self): """Actions taken before the end of a 'run'.""" self._running = False self.stop_sgn.emit() # TODO: wait for some signal self._start_at.setEnabled(True) self._stop_at.setEnabled(False) for widget in self._ctrl_widgets: widget.onStop() for win in self._plot_windows: win.onStop() self._image_tool.onStop() self._analysis_setup_manager.onStop() def updateMetaData(self): """Update metadata from all the ctrl widgets. :returns bool: True if all metadata successfully parsed and emitted, otherwise False. """ for widget in self._ctrl_widgets: if not widget.updateMetaData(): return False for win in self._plot_windows: if not win.updateMetaData(): return False return self._image_tool.updateMetaData() def loadMetaData(self): """Load metadata from Redis and set child control widgets.""" for widget in self._ctrl_widgets: widget.loadMetaData() for win in self._plot_windows: win.loadMetaData() self._image_tool.loadMetaData() @pyqtSlot(str, str) def onLogMsgReceived(self, ch, msg): if ch == 'log:debug': logger.debug(msg) elif ch == 'log:info': logger.info(msg) elif ch == 'log:warning': logger.warning(msg) elif ch == 'log:error': logger.error(msg) def closeEvent(self, QCloseEvent): # prevent from logging in the GUI when it has been closed logger.removeHandler(self._gui_logger) # tell all processes to close self._close_ev.set() # clean up the logger thread self.quit_sgn.emit() self._thread_logger_t.quit() self._thread_logger_t.wait() # shutdown pipeline workers and Redis server shutdown_all() self._image_tool.close() for window in list( itertools.chain(self._plot_windows, self._satellite_windows)): # Close all open child windows to make sure their resources # (any running process etc.) are released gracefully. This # is especially necessary for the case when file stream was # still ongoing when the main GUI was closed. window.close() super().closeEvent(QCloseEvent)
def quit(self): """graceful exit when quiting the task""" self.abort = True QThread.quit(self) QThread.wait(self)
class Window(QMainWindow): """ A class that connect functionality to the GUI window. Starts a new thread with a worker when clicking on a button. """ ## Initialize different signals which can be sent to the Worker class loadingModel = pyqtSignal(str, torch.nn.Module) uploadingImage = pyqtSignal(str) predictingImage = pyqtSignal() def __init__(self, app): """ Initializes the window, initializes a (parallel) worker and connects all the functionalities to the dialog. Args: app (QApplication): A QApplication object of the GUI. Returns: None. """ ## Create a window super(Window, self).__init__() ## Load lay-out of GUI from the generated dialog file self.dialog = Ui_AFMNet() self.dialog.setupUi(self) ## Get the directory of the AFMNet folder self.dirPath = os.path.dirname( os.path.realpath(__file__)) + os.path.sep + ".." ## Set the device for Pytorch self.device = torch.device( "cuda:0" if torch.cuda.is_available() else "cpu") ## Add the model architectures (ConvNet, FineNet and FeatureNet) to the dropdown menu archs = next(os.walk(self.dirPath + os.path.sep + "models"))[1] for arch in archs: self.dialog.comboBox.addItem(arch) ## Initialize a (parallel) worker and pass the directory and device self.worker = Worker(self.dirPath, self.device) ## Connect functions to the different buttons self.dialog.modelButton.clicked.connect(self.loadModel) self.dialog.uploadButton.clicked.connect(self.uploadImage) self.dialog.predictButton.clicked.connect(self.predictImage) ## Close all threads when the window closes app.aboutToQuit.connect(self.closeWindow) def enableButtons(self, boolean=True): """ Enables or disabled all buttons. Args: boolean (bool, optional): All buttons will be enabled if 'True', they will be disabled if 'False'. Defaults to True. Returns: None. """ self.dialog.modelButton.setEnabled(boolean) self.dialog.uploadButton.setEnabled(boolean) self.dialog.predictButton.setEnabled(boolean) def startThread(self, signal, slot): """ Creates and starts a thread for the (parallel) worker. Also connects a signal to a slot of the worker. Args: signal (pyqtSignal): A signal attribute of the Window class. slot (pyqtSignal): A slot attribute of the Worker class. Returns: None. """ ## Start a QThread if it does not exist yet if not (hasattr(self, "thread") and isinstance(getattr(self, "thread"), QThread)): self.thread = QThread() ## Connect the signals of the worker to methods in the window class self.worker.progress.connect(self.setProgress) self.worker.text.connect(self.setTextLabel) self.worker.image.connect(self.setImageLabel) ## Move the worker to the thread self.worker.moveToThread(self.thread) ## Connect a sginal of the worker to a method in the window class signal.connect(slot) self.worker.finished.connect(self.finished) ## Start the thread self.thread.start() def setProgress(self, value): """ Sets the progress of the progress bar to a certain value. Args: value (int): The progress bar will be set to this (percentage) value. Returns: None. """ self.dialog.progressBar.setValue(value) def setTextLabel(self, string): """ Sets the text label of the text beneath the image. Args: string (str): String of desired text for the label. Returns: None. """ self.dialog.textlabel.setText(string) def setImageLabel(self, pixmap): """ Updates the image label with the provided pixmap. Args: pixmap (QPixmap): A pixmap of an AFM image. Returns: None. """ self.dialog.image.setUnScaledPixmap(pixmap) def finished(self): """ Called when a thread is finished. Sets the progress to 100, enables all buttons and quits the thread. Returns: None. """ self.setProgress(100) self.enableButtons() self.thread.quit() def loadModel(self): """ Method connected to the 'Load Model' button. Loads a model architecture. Returns: None. """ ## Set the progress to 0, disable all buttons and set the text label to "Loading..." self.setProgress(0) self.enableButtons(False) self.setTextLabel("Loading...") ## Start a thread which connects the loadingModel signal to the LoadModel slot of the worker self.startThread(self.loadingModel, self.worker.loadModel) ## Retrieve the selected network architecture comboText = self.dialog.comboBox.currentText() if comboText == "Select Net...": ## If no network architecture is selected, prompt the user to select a network architecture. self.setTextLabel("Select a network architecture first!") self.finished() return else: self.net_arch = comboText.split(' ', 1)[0] ## Open an explorer window in which you can select a model of architecture net_arch self.modelPath, _ = QFileDialog.getOpenFileName( self, "Load Model", self.dirPath + os.path.sep + "models" + os.path.sep + self.net_arch, "Path Files (*pt *pth)") ## Initialize the network (with a class from nets.py) if not self.net_arch.__eq__("ConvNet"): arg = eval("models." + self.modelPath.split('/')[-1].split('_')[0]) self.net = eval("nets." + self.net_arch)(arg) else: self.net = eval("nets." + self.net_arch)() ## Send a signal with the path and network to the loadModel slot of the worker self.loadingModel.emit(self.modelPath, self.net) def uploadImage(self): """ Method connected to the 'Upload Image' button. Opens an AFM image to predict. Returns: None. """ ## Set the progress to 0, disable all buttons and set the text label to "Loading..." self.setProgress(0) self.enableButtons(False) self.setTextLabel("Loading...") ## Open an explorer window in which you can open an AFM image self.fileName, _ = QFileDialog.getOpenFileName( self, "Upload Image", self.dirPath + os.path.sep + "data" + os.path.sep + "val", 'Images (*bmp *gif *jpg *jpeg *png *pbm *tiff *svg *xbm)') ## Start a thread which connects the uploadingImage signal to the uploadImage slot of the worker self.startThread(self.uploadingImage, self.worker.uploadImage) ## Send a signal with the path to the uploadImage slot of the worker self.uploadingImage.emit(self.fileName) def predictImage(self): """ Method connected to the 'Predict Image' button. Classifies an image as 'Good' or 'Bad'. Returns: None. """ ## Set the progress to 0, disable all buttons and set the text label to "Busy..." self.setProgress(0) self.enableButtons(False) self.setTextLabel("Busy...") ## Start a thread which connects the predictingImage signal to the predictImage slot of the worker self.startThread(self.predictingImage, self.worker.predictImage) ## Send a signal to the predictImage slot of the worker self.predictingImage.emit() def closeWindow(self): """ Closes the remaining threads. Returns: None. """ if hasattr(self, "thread") and isinstance(getattr(self, "thread"), QThread): self.thread.quit()
class TrainingWidget(QWidget): data_dir_selected = pyqtSignal(str) save_dir_selected = pyqtSignal(str) test_count = pyqtSignal(str) train_count = pyqtSignal(str) training = pyqtSignal(bool) def __init__(self): super().__init__() self.data_dir = None self.save_dir = None self.train_button = None self.text_edit = None self.is_training = False self.runner_thread = None self.worker = None self.training_info_box = {} self.init_ui() def init_ui(self): vbox = QVBoxLayout() size = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) ## Data Box data_hbox = QHBoxLayout() data_label = QLabel("Data directory: ") data_label.setSizePolicy(size) data_label_dir = QLabel("") data_label_dir.setSizePolicy(size) self.data_dir_selected.connect(data_label_dir.setText) data_button = QPushButton("Choose Folder") data_button.setSizePolicy(size) data_button.pressed.connect(self.data_button_pressed) data_hbox.addWidget(data_label) data_hbox.addWidget(data_label_dir) data_hbox.addStretch(1) data_hbox.addWidget(data_button) data_hbox.addStretch(2) # train box train_hbox = QHBoxLayout() train_count_label = QLabel("Training images: ") train_count_label.setSizePolicy(size) train_count_value_label = QLabel("") train_count_value_label.setSizePolicy(size) self.train_count.connect(train_count_value_label.setText) train_hbox.addWidget(train_count_label) train_hbox.addWidget(train_count_value_label) train_hbox.addStretch(1) # test box test_hbox = QHBoxLayout() test_count_label = QLabel("Testing images: ") test_count_value_label = QLabel("") test_count_label.setSizePolicy(size) test_count_value_label.setSizePolicy(size) self.test_count.connect(test_count_value_label.setText) test_hbox.addWidget(test_count_label) test_hbox.addWidget(test_count_value_label) test_hbox.addStretch(1) ## Save box save_hbox = QHBoxLayout() save_label = QLabel("Save directory: ") save_label.setSizePolicy(size) save_label_dir = QLabel("") save_label_dir.setSizePolicy(size) self.save_dir_selected.connect(save_label_dir.setText) save_button = QPushButton("Choose Folder") save_button.setSizePolicy(size) save_button.pressed.connect(self.save_button_pressed) save_hbox.addWidget(save_label) save_hbox.addWidget(save_label_dir) save_hbox.addStretch(1) save_hbox.addWidget(save_button) save_hbox.addStretch(2) ## Train button self.train_button = QPushButton("Strat Training") self.train_button.setMaximumWidth(300) self.train_button.setEnabled(False) self.train_button.pressed.connect(self.start_training) self.training.connect(self.train_button.setDisabled) train_info_layout = self.create_training_info() vbox.setSpacing(20) vbox.addLayout(data_hbox) vbox.addLayout(train_hbox) vbox.addLayout(test_hbox) vbox.addLayout(save_hbox) vbox.addSpacing(40) vbox.addWidget(self.train_button) vbox.addLayout(train_info_layout) vbox.addStretch(1) self.setLayout(vbox) self.setSizePolicy(size) self.setObjectName("training") def set_training_visible(self, visible): self.training_info_box["text_edit_label"].setVisible(visible) self.training_info_box["text_edit"].setVisible(visible) self.training_info_box["bar"].setVisible(visible) self.training_info_box["figure"].set_visible(visible) plt.draw() def create_training_info(self): self.training_info_box["layout"] = QVBoxLayout() size = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) ##QTextEdit self.training_info_box["text_edit_label"] = QLabel("Info: ") self.training_info_box["text_edit_label"].setSizePolicy(size) self.training_info_box["text_edit"] = QTextEdit() self.training_info_box["text_edit"].setFixedHeight(100) self.training_info_box["text_edit"].setReadOnly(True) ## Progress bar self.training_info_box["bar"] = QProgressBar() ## matplotlib self.training_info_box["figure"] = plt.figure() self.training_info_box["canvas"] = FigureCanvas( self.training_info_box["figure"]) self.training_info_box["layout"].addWidget( self.training_info_box["text_edit_label"]) self.training_info_box["layout"].addWidget( self.training_info_box["text_edit"]) self.training_info_box["layout"].addWidget( self.training_info_box["bar"]) self.training_info_box["layout"].addWidget( self.training_info_box["canvas"]) self.set_training_visible(False) return self.training_info_box["layout"] def change_train_button_state(self): try: if self.data_dir is not None and self.save_dir is not None: self.train_button.setEnabled(True) else: self.train_button.setEnabled(False) except Exception as a: print(a) @pyqtSlot() def start_training(self): self.training.emit(True) self.runner_thread = QThread() self.worker = TrainRunner(self.data_dir, self.save_dir) self.worker.moveToThread(self.runner_thread) self.worker.msg_from_job.connect(self.handleRunner) self.runner_thread.started.connect(self.worker.run) self.runner_thread.start() @pyqtSlot(object) def handleRunner(self, obj): if obj.type == "command" and obj.data == "end": self.text_edit.append("Done.\n") self.runner_thread.quit() self.training.emit(False) elif obj.type == "total_epochs": self.set_training_visible(True) axis = self.training_info_box["figure"].gca() axis.y_limit([0, 1]) axis.x_limit([0, obj.data]) self.training_info_box["bar"].setMinimum(0) self.training_info_box["bar"].setMaximum(obj.data) elif obj.type == "epoch_logs": self.text_edit.append("accuracy: " + str(obj.data["acc"]) + ", validation_accuracy: " + str(obj.data["val_acc"])) elif obj.type == "text": self.text_edit.append(obj.data + "\n") @pyqtSlot() def data_button_pressed(self): data_dir = QFileDialog.getExistingDirectory(self, "Choose data folder") if len(data_dir): try: if not os.path.isdir(os.path.join(data_dir, "training")) or \ not os.path.isdir(os.path.join(data_dir, "validation")): raise Exception len_train = 0 for root, dir, files in os.walk( os.path.join(data_dir, "training")): len_train += len(files) len_test = 0 for root, dir, files in os.walk( os.path.join(data_dir, "validation")): len_test += len(files) self.data_dir = data_dir self.data_dir_selected.emit(data_dir) self.test_count.emit(str(len_test)) self.train_count.emit(str(len_train)) except Exception as e: print(e) showError("Directory doesn't have the correct structure.") self.data_dir_selected.emit("") self.test_count.emit("") self.train_count.emit("") self.data_dir = None finally: self.change_train_button_state() @pyqtSlot() def save_button_pressed(self): save_dir = QFileDialog.getExistingDirectory(self, "Choose save folder") if len(save_dir): self.save_dir = save_dir self.save_dir_selected.emit(save_dir) self.change_train_button_state()
class TVLinker(QWidget): def __init__(self, settings: QSettings, parent=None): super(TVLinker, self).__init__(parent) self.firstrun = True self.rows, self.cols = 0, 0 self.parent = parent self.settings = settings self.taskbar = TaskbarProgress(self) self.init_styles() self.init_settings() self.init_icons() if sys.platform.startswith('linux'): notify.init(qApp.applicationName()) layout = QVBoxLayout() layout.setSpacing(0) layout.setContentsMargins(15, 15, 15, 0) form_groupbox = QGroupBox(self, objectName='mainForm') form_groupbox.setLayout(self.init_form()) self.table = TVLinkerTable(0, 4, self) self.table.doubleClicked.connect(self.show_hosters) layout.addWidget(form_groupbox) layout.addWidget(self.table) layout.addLayout(self.init_metabar()) self.setLayout(layout) qApp.setWindowIcon(self.icon_app) self.resize(FixedSettings.windowSize) self.show() self.start_scraping() self.firstrun = False class ProcError(Enum): FAILED_TO_START = 0 CRASHED = 1 TIMED_OUT = 2 READ_ERROR = 3 WRITE_ERROR = 4 UNKNOWN_ERROR = 5 class NotifyIcon(Enum): SUCCESS = ':assets/images/tvlinker.png' DEFAULT = ':assets/images/tvlinker.png' def init_threads(self, threadtype: str = 'scrape') -> None: if threadtype == 'scrape': if hasattr(self, 'scrapeThread'): if not sip.isdeleted( self.scrapeThread) and self.scrapeThread.isRunning(): self.scrapeThread.terminate() del self.scrapeWorker del self.scrapeThread self.scrapeThread = QThread(self) self.scrapeWorker = ScrapeWorker(self.source_url, self.user_agent, self.dl_pagecount) self.scrapeThread.started.connect(self.show_progress) self.scrapeThread.started.connect(self.scrapeWorker.begin) self.scrapeWorker.moveToThread(self.scrapeThread) self.scrapeWorker.addRow.connect(self.add_row) self.scrapeWorker.workFinished.connect(self.scrape_finished) self.scrapeWorker.workFinished.connect( self.scrapeWorker.deleteLater, Qt.DirectConnection) self.scrapeWorker.workFinished.connect(self.scrapeThread.quit, Qt.DirectConnection) self.scrapeThread.finished.connect(self.scrapeThread.deleteLater, Qt.DirectConnection) elif threadtype == 'unrestrict': pass @staticmethod def load_stylesheet(qssfile: str) -> None: if QFileInfo(qssfile).exists(): qss = QFile(qssfile) qss.open(QFile.ReadOnly | QFile.Text) qApp.setStyleSheet(QTextStream(qss).readAll()) def init_styles(self) -> None: if sys.platform == 'darwin': qss_stylesheet = self.get_path('%s_osx.qss' % qApp.applicationName().lower()) else: qss_stylesheet = self.get_path('%s.qss' % qApp.applicationName().lower()) TVLinker.load_stylesheet(qss_stylesheet) QFontDatabase.addApplicationFont(':assets/fonts/opensans.ttf') QFontDatabase.addApplicationFont(':assets/fonts/opensans-bold.ttf') QFontDatabase.addApplicationFont(':assets/fonts/opensans-semibold.ttf') qApp.setFont(QFont('Open Sans', 12 if sys.platform == 'darwin' else 10)) def init_icons(self) -> None: self.icon_app = QIcon( self.get_path('images/%s.png' % qApp.applicationName().lower())) self.icon_faves_off = QIcon(':assets/images/star_off.png') self.icon_faves_on = QIcon(':assets/images/star_on.png') self.icon_refresh = QIcon(':assets/images/refresh.png') self.icon_menu = QIcon(':assets/images/menu.png') self.icon_settings = QIcon(':assets/images/cog.png') self.icon_updates = QIcon(':assets/images/cloud.png') def init_settings(self) -> None: self.provider = 'Scene-RLS' self.select_provider(0) self.user_agent = self.settings.value('user_agent') self.dl_pagecount = self.settings.value('dl_pagecount', 20, int) self.dl_pagelinks = FixedSettings.linksPerPage self.realdebrid_api_token = self.settings.value('realdebrid_apitoken') self.realdebrid_api_proxy = self.settings.value('realdebrid_apiproxy') self.download_manager = self.settings.value('download_manager') self.persepolis_cmd = self.settings.value('persepolis_cmd') self.pyload_host = self.settings.value('pyload_host') self.pyload_username = self.settings.value('pyload_username') self.pyload_password = self.settings.value('pyload_password') self.idm_exe_path = self.settings.value('idm_exe_path') self.kget_cmd = self.settings.value('kget_cmd') self.favorites = self.settings.value('favorites') def init_form(self) -> QHBoxLayout: self.search_field = QLineEdit(self, clearButtonEnabled=True, placeholderText='Enter search criteria') self.search_field.setObjectName('searchInput') self.search_field.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.search_field.setFocus() self.search_field.textChanged.connect(self.clear_filters) self.search_field.returnPressed.connect( lambda: self.filter_table(self.search_field.text())) self.favorites_button = QPushButton(parent=self, flat=True, cursor=Qt.PointingHandCursor, objectName='favesButton', toolTip='Favorites', checkable=True, toggled=self.filter_faves, checked=self.settings.value( 'faves_filter', False, bool)) self.refresh_button = QPushButton(parent=self, flat=True, cursor=Qt.PointingHandCursor, objectName='refreshButton', toolTip='Refresh', clicked=self.start_scraping) self.dlpages_field = QComboBox(self, toolTip='Pages', editable=False, cursor=Qt.PointingHandCursor) self.dlpages_field.addItems( ('10', '20', '30', '40', '50', '60', '70', '80')) self.dlpages_field.setCurrentIndex( self.dlpages_field.findText(str(self.dl_pagecount), Qt.MatchFixedString)) self.dlpages_field.currentIndexChanged.connect(self.update_pagecount) self.settings_button = QPushButton(parent=self, flat=True, toolTip='Menu', objectName='menuButton', cursor=Qt.PointingHandCursor) self.settings_button.setMenu(self.settings_menu()) layout = QHBoxLayout(spacing=10) # providerCombo = QComboBox(self, toolTip='Provider', editable=False, cursor=Qt.PointingHandCursor) # providerCombo.setObjectName('providercombo') # providerCombo.addItem(QIcon(':assets/images/provider-scenerls.png'), '') # providerCombo.addItem(QIcon(':assets/images/provider-tvrelease.png'), '') # providerCombo.setIconSize(QSize(146, 36)) # providerCombo.setMinimumSize(QSize(160, 40)) # providerCombo.setStyleSheet(''' # QComboBox, QComboBox::drop-down { background-color: transparent; border: none; margin: 5px; } # QComboBox::down-arrow { image: url(:assets/images/down_arrow.png); } # QComboBox QAbstractItemView { selection-background-color: #DDDDE4; } # ''') # providerCombo.currentIndexChanged.connect(self.select_provider) layout.addWidget( QLabel(pixmap=QPixmap(':assets/images/provider-scenerls.png'))) layout.addWidget(self.search_field) layout.addWidget(self.favorites_button) layout.addWidget(self.refresh_button) layout.addWidget(QLabel('Pages:')) layout.addWidget(self.dlpages_field) layout.addWidget(self.settings_button) return layout @pyqtSlot(int) def select_provider(self, index: int): if index == 0: self.provider = 'Scene-RLS' self.source_url = 'http://scene-rls.net/releases/index.php?p={0}&cat=TV%20Shows' elif index == 1: self.provider = 'TV-Release' self.source_url = 'http://tv-release.pw/?cat=TV' self.setWindowTitle('%s :: %s' % (qApp.applicationName(), self.provider)) def settings_menu(self) -> QMenu: settings_action = QAction(self.icon_settings, 'Settings', self, triggered=self.show_settings) updates_action = QAction(self.icon_updates, 'Check for updates', self, triggered=self.check_update) aboutqt_action = QAction('About Qt', self, triggered=qApp.aboutQt) about_action = QAction('About %s' % qApp.applicationName(), self, triggered=self.about_app) menu = QMenu() menu.addAction(settings_action) menu.addAction(updates_action) menu.addSeparator() menu.addAction(aboutqt_action) menu.addAction(about_action) return menu def init_metabar(self) -> QHBoxLayout: self.meta_template = 'Total number of links retrieved: <b>%i</b> / <b>%i</b>' self.progress = QProgressBar(parent=self, minimum=0, maximum=(self.dl_pagecount * self.dl_pagelinks), visible=False) self.taskbar.setProgress(0.0, True) if sys.platform == 'win32': self.win_taskbar_button = QWinTaskbarButton(self) self.meta_label = QLabel(textFormat=Qt.RichText, alignment=Qt.AlignRight, objectName='totals') self.update_metabar() layout = QHBoxLayout() layout.setContentsMargins(10, 5, 10, 10) layout.addWidget(self.progress, Qt.AlignLeft) layout.addWidget(self.meta_label, Qt.AlignRight) return layout @pyqtSlot() def check_update(self) -> None: QDesktopServices.openUrl(QUrl(FixedSettings.latest_release_url)) @pyqtSlot() def show_settings(self) -> None: settings_win = Settings(self, self.settings) settings_win.exec_() def update_metabar(self) -> bool: rowcount = self.table.rowCount() self.meta_label.setText( self.meta_template % (rowcount, self.dl_pagecount * self.dl_pagelinks)) self.progress.setValue(rowcount) self.taskbar.setProgress(rowcount / self.progress.maximum()) if sys.platform == 'win32': self.win_taskbar_button.progress().setValue(self.progress.value()) return True def start_scraping(self) -> None: self.init_threads('scrape') self.rows = 0 self.table.clearContents() self.table.setRowCount(0) self.table.setSortingEnabled(False) self.update_metabar() self.scrapeThread.start() @pyqtSlot() def about_app(self) -> None: about_html = '''<style> a { color:#441d4e; text-decoration:none; font-weight:bold; } a:hover { text-decoration:underline; } </style> <p style="font-size:24pt; font-weight:bold; color:#6A687D;">%s</p> <p> <span style="font-size:13pt;"><b>Version: %s</b></span> <span style="font-size:10pt;position:relative;left:5px;">( %s )</span> </p> <p style="font-size:13px;"> Copyright © %s <a href="mailto:[email protected]">Pete Alexandrou</a> <br/> Web: <a href="%s">%s</a> </p> <p style="font-size:11px;"> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. </p>''' % (qApp.applicationName(), qApp.applicationVersion(), platform.architecture()[0], datetime.now().year, qApp.organizationDomain(), qApp.organizationDomain()) QMessageBox.about(self, 'About %s' % qApp.applicationName(), about_html) @pyqtSlot(int) def update_pagecount(self, index: int) -> None: self.dl_pagecount = int(self.dlpages_field.itemText(index)) self.scrapeWorker.maxpages = self.dl_pagecount self.progress.setMaximum(self.dl_pagecount * self.dl_pagelinks) self.settings.setValue('dl_pagecount', self.dl_pagecount) if sys.platform == 'win32': self.win_taskbar_button.progress().setMaximum(self.dl_pagecount * self.dl_pagelinks) if self.scrapeThread.isRunning(): self.scrapeThread.requestInterruption() self.start_scraping() @pyqtSlot() def show_progress(self): self.progress.show() self.taskbar.setProgress(0.0, True) if sys.platform == 'win32': self.win_taskbar_button.setWindow(self.windowHandle()) self.win_taskbar_button.progress().setRange( 0, self.dl_pagecount * self.dl_pagelinks) self.win_taskbar_button.progress().setVisible(True) self.win_taskbar_button.progress().setValue(self.progress.value()) @pyqtSlot() def scrape_finished(self) -> None: self.progress.hide() self.taskbar.setProgress(0.0, False) if sys.platform == 'win32': self.win_taskbar_button.progress().setVisible(False) self.table.setSortingEnabled(True) self.filter_table(text='') @pyqtSlot(list) def add_row(self, row: list) -> None: if self.scrapeThread.isInterruptionRequested(): self.scrapeThread.terminate() else: self.cols = 0 self.table.setRowCount(self.rows + 1) if self.table.cursor() != Qt.PointingHandCursor: self.table.setCursor(Qt.PointingHandCursor) for item in row: table_item = QTableWidgetItem(item) table_item.setToolTip( '%s\n\nDouble-click to view hoster links.' % row[1]) table_item.setFont(QFont('Open Sans', weight=QFont.Normal)) if self.cols == 2: if sys.platform == 'win32': table_item.setFont( QFont('Open Sans Semibold', pointSize=10)) elif sys.platform == 'darwin': table_item.setFont( QFont('Open Sans Bold', weight=QFont.Bold)) else: table_item.setFont( QFont('Open Sans', weight=QFont.DemiBold, pointSize=10)) table_item.setText(' ' + table_item.text()) elif self.cols in (0, 3): table_item.setTextAlignment(Qt.AlignCenter) self.table.setItem(self.rows, self.cols, table_item) self.update_metabar() self.cols += 1 self.rows += 1 @pyqtSlot(list) def add_hosters(self, links: list) -> None: self.hosters_win.show_hosters(links) @pyqtSlot(QModelIndex) def show_hosters(self, index: QModelIndex) -> None: qApp.setOverrideCursor(Qt.BusyCursor) self.hosters_win = HosterLinks(self) self.hosters_win.downloadLink.connect(self.download_link) self.hosters_win.copyLink.connect(self.copy_download_link) self.links = HostersThread( self.table.item(self.table.currentRow(), 1).text(), self.user_agent) self.links.setHosters.connect(self.add_hosters) self.links.noLinks.connect(self.no_links) self.links.start() @pyqtSlot() def no_links(self) -> None: self.hosters_win.loading_progress.cancel() self.hosters_win.close() QMessageBox.warning( self, 'No Links Available', 'No links are available yet for the chosen TV show. ' + 'This is most likely due to the files still being uploaded. This is normal if the ' + 'link was published 30-45 mins ago.\n\nPlease check back again in 10-15 minutes.' ) @pyqtSlot(bool) def filter_faves(self, checked: bool) -> None: self.settings.setValue('faves_filter', checked) # if hasattr(self, 'scrapeWorker') and (sip.isdeleted(self.scrapeWorker) or self.scrapeWorker.complete): if not self.firstrun: self.filter_table() @pyqtSlot(str) @pyqtSlot() def filter_table(self, text: str = '') -> None: filters = [] if self.favorites_button.isChecked(): filters = self.favorites self.table.sortItems(2, Qt.AscendingOrder) else: self.table.sortItems(0, Qt.DescendingOrder) if len(text): filters.append(text) if not len(filters) or not hasattr(self, 'valid_rows'): self.valid_rows = [] for search_term in filters: for item in self.table.findItems(search_term, Qt.MatchContains): self.valid_rows.append(item.row()) for row in range(0, self.table.rowCount()): if not len(filters): self.table.showRow(row) else: if row not in self.valid_rows: self.table.hideRow(row) else: self.table.showRow(row) @pyqtSlot() def clear_filters(self): if not len(self.search_field.text()): self.filter_table('') @pyqtSlot(bool) def aria2_confirmation(self, success: bool) -> None: qApp.restoreOverrideCursor() if success: if sys.platform.startswith('linux'): self.notify( title=qApp.applicationName(), msg='Your download link has been unrestricted and now ' + 'queued in Aria2 RPC Daemon', icon=self.NotifyIcon.SUCCESS) else: QMessageBox.information( self, qApp.applicationName(), 'Download link has been queued in Aria2.', QMessageBox.Ok) else: QMessageBox.critical( self, 'Aria2 RPC Daemon', 'Could not connect to Aria2 RPC Daemon. ' + 'Check your %s settings and try again.' % qApp.applicationName(), QMessageBox.Ok) @pyqtSlot(str) def download_link(self, link: str) -> None: if len(self.realdebrid_api_token) > 0 and 'real-debrid.com' not in link \ and 'rdeb.io' not in link: qApp.setOverrideCursor(Qt.BusyCursor) self.unrestrict_link(link, True) else: if self.download_manager == 'aria2': self.aria2 = Aria2Thread(settings=self.settings, link_url=link) self.aria2.aria2Confirmation.connect(self.aria2_confirmation) self.aria2.start() self.hosters_win.close() elif self.download_manager == 'pyload': self.pyload_conn = PyloadConnection(self.pyload_host, self.pyload_username, self.pyload_password) pid = self.pyload_conn.addPackage(name='TVLinker', links=[link]) qApp.restoreOverrideCursor() self.hosters_win.close() if sys.platform.startswith('linux'): self.notify(title='Download added to %s' % self.download_manager, icon=self.NotifyIcon.SUCCESS) else: QMessageBox.information( self, self.download_manager, 'Your link has been queued in %s.' % self.download_manager, QMessageBox.Ok) # open_pyload = msgbox.addButton('Open pyLoad', QMessageBox.AcceptRole) # open_pyload.clicked.connect(self.open_pyload) elif self.download_manager in ('kget', 'persepolis'): provider = self.kget_cmd if self.download_manager == 'kget' else self.persepolis_cmd cmd = '{0} "{1}"'.format(provider, link) if self.cmdexec(cmd): qApp.restoreOverrideCursor() self.hosters_win.close() if sys.platform.startswith('linux'): self.notify(title='Download added to %s' % self.download_manager, icon=self.NotifyIcon.SUCCESS) else: QMessageBox.information( self, self.download_manager, 'Your link has been queued in %s.' % self.download_manager, QMessageBox.Ok) elif self.download_manager == 'idm': cmd = '"%s" /n /d "%s"' % (self.idm_exe_path, link) if self.cmdexec(cmd): qApp.restoreOverrideCursor() self.hosters_win.close() QMessageBox.information( self, 'Internet Download Manager', 'Your link has been queued in IDM.') else: print('IDM QProcess error = %s' % self.ProcError(self.idm.error()).name) qApp.restoreOverrideCursor() self.hosters_win.close() QMessageBox.critical( self, 'Internet Download Manager', '<p>Could not connect to your local IDM application instance. ' + 'Please check your settings and ensure the IDM executable path is correct ' + 'according to your installation.</p><p>Error Code: %s</p>' % self.ProcError(self.idm.error()).name, QMessageBox.Ok) else: dlpath, _ = QFileDialog.getSaveFileName( self, 'Save File', link.split('/')[-1]) if dlpath != '': self.directdl_win = DirectDownload(parent=self) self.directdl = DownloadThread(link_url=link, dl_path=dlpath) self.directdl.dlComplete.connect( self.directdl_win.download_complete) if sys.platform.startswith('linux'): self.directdl.dlComplete.connect( lambda: self.notify(qApp.applicationName( ), 'Download complete', self.NotifyIcon.SUCCESS)) else: self.directdl.dlComplete.connect( lambda: QMessageBox.information( self, qApp.applicationName(), 'Download complete', QMessageBox.Ok)) self.directdl.dlProgressTxt.connect( self.directdl_win.update_progress_label) self.directdl.dlProgress.connect( self.directdl_win.update_progress) self.directdl_win.cancelDownload.connect( self.cancel_download) self.directdl.start() self.hosters_win.close() def _init_notification_icons(self): for icon in self.NotifyIcon: icon_file = QPixmap(icon.value, 'PNG') icon_file.save( os.path.join(FixedSettings.config_path, os.path.basename(icon.value)), 'PNG', 100) def notify(self, title: str, msg: str = '', icon: Enum = None, urgency: int = 1) -> bool: icon_path = icon.value if icon is not None else self.NotifyIcon.DEFAULT.value icon_path = os.path.join(FixedSettings.config_path, os.path.basename(icon_path)) if not os.path.exists(icon_path): self._init_notification_icons() notification = notify.Notification(title, msg, icon_path) notification.set_urgency(urgency) return notification.show() def cmdexec(self, cmd: str) -> bool: self.proc = QProcess() self.proc.setProcessChannelMode(QProcess.MergedChannels) if hasattr(self.proc, 'errorOccurred'): self.proc.errorOccurred.connect(lambda error: print( 'Process error = %s' % self.ProcError(error).name)) if self.proc.state() == QProcess.NotRunning: self.proc.start(cmd) self.proc.waitForFinished(-1) rc = self.proc.exitStatus( ) == QProcess.NormalExit and self.proc.exitCode() == 0 self.proc.deleteLater() return rc return False @pyqtSlot() def cancel_download(self) -> None: self.directdl.cancel_download = True self.directdl.quit() self.directdl.deleteLater() def open_pyload(self) -> None: QDesktopServices.openUrl(QUrl(self.pyload_config.host)) @pyqtSlot(str) def copy_download_link(self, link: str) -> None: if len(self.realdebrid_api_token) > 0 and 'real-debrid.com' not in link \ and 'rdeb.io' not in link: qApp.setOverrideCursor(Qt.BusyCursor) self.unrestrict_link(link, False) else: clip = qApp.clipboard() clip.setText(link) self.hosters_win.close() qApp.restoreOverrideCursor() def unrestrict_link(self, link: str, download: bool = True) -> None: caller = inspect.stack()[1].function self.realdebrid = RealDebridThread( settings=self.settings, api_url=FixedSettings.realdebrid_api_url, link_url=link, action=RealDebridThread.RealDebridAction.UNRESTRICT_LINK) self.realdebrid.errorMsg.connect(self.error_handler) if download: self.realdebrid.unrestrictedLink.connect(self.download_link) else: self.realdebrid.unrestrictedLink.connect(self.copy_download_link) self.realdebrid.start() def closeEvent(self, event: QCloseEvent) -> None: if hasattr(self, 'scrapeThread'): if not sip.isdeleted( self.scrapeThread) and self.scrapeThread.isRunning(): self.scrapeThread.requestInterruption() self.scrapeThread.quit() qApp.quit() def error_handler(self, props: list) -> None: qApp.restoreOverrideCursor() QMessageBox.critical(self, props[0], props[1], QMessageBox.Ok) @staticmethod def get_path(path: str = None, override: bool = False) -> str: if override: if getattr(sys, 'frozen', False): return os.path.join(sys._MEIPASS, path) return os.path.join(QFileInfo(__file__).absolutePath(), path) return ':assets/%s' % path @staticmethod def get_version(filename: str = '__init__.py') -> str: with open(TVLinker.get_path(filename, override=True), 'r') as initfile: for line in initfile.readlines(): m = re.match('__version__ *= *[\'](.*)[\']', line) if m: return m.group(1)
class Paprika: """QGIS Plugin Implementation.""" def __init__(self, iface): """Constructor. :param iface: An interface instance that will be passed to this class which provides the hook by which you can manipulate the QGIS application at run time. :type iface: QgsInterface """ # Save reference to the QGIS interface self.iface = iface # initialize plugin directory self.plugin_dir = os.path.dirname(__file__) # initialize locale locale = QSettings().value('locale/userLocale')[0:2] locale_path = os.path.join( self.plugin_dir, 'i18n', 'Paprika_{}.qm'.format(locale)) if os.path.exists(locale_path): self.translator = QTranslator() self.translator.load(locale_path) if qVersion() > '4.3.3': QCoreApplication.installTranslator(self.translator) # Declare instance attributes self.actions = [] self.menu = self.tr(u'&Paprika') self.toolbar = self.iface.addToolBar(u'Paprika') self.toolbar.setObjectName(u'Paprika') self.pluginIsActive = False self.dockwidget = None self.raster_info = {} self.extent_view = None # noinspection PyMethodMayBeStatic def tr(self, message): """Get the translation for a string using Qt translation API. We implement this ourselves since we do not inherit QObject. :param message: String for translation. :type message: str, QString :returns: Translated version of message. :rtype: QString """ # noinspection PyTypeChecker,PyArgumentList,PyCallByClass return QCoreApplication.translate('Paprika', message) def add_action( self, icon_path, text, callback, enabled_flag=True, add_to_menu=True, add_to_toolbar=True, status_tip=None, whats_this=None, parent=None): """Add a toolbar icon to the toolbar. :param icon_path: Path to the icon for this action. Can be a resource path (e.g. ':/plugins/foo/bar.png') or a normal file system path. :type icon_path: str :param text: Text that should be shown in menu items for this action. :type text: str :param callback: Function to be called when the action is triggered. :type callback: function :param enabled_flag: A flag indicating if the action should be enabled by default. Defaults to True. :type enabled_flag: bool :param add_to_menu: Flag indicating whether the action should also be added to the menu. Defaults to True. :type add_to_menu: bool :param add_to_toolbar: Flag indicating whether the action should also be added to the toolbar. Defaults to True. :type add_to_toolbar: bool :param status_tip: Optional text to show in a popup when mouse pointer hovers over the action. :type status_tip: str :param parent: Parent widget for the new action. Defaults None. :type parent: QWidget :param whats_this: Optional text to show in the status bar when the mouse pointer hovers over the action. :returns: The action that was created. Note that the action is also added to self.actions list. :rtype: QAction """ icon = QIcon(icon_path) action = QAction(icon, text, parent) action.triggered.connect(callback) action.setEnabled(enabled_flag) if status_tip is not None: action.setStatusTip(status_tip) if whats_this is not None: action.setWhatsThis(whats_this) if add_to_toolbar: self.toolbar.addAction(action) if add_to_menu: self.iface.addPluginToMenu( self.menu, action) self.actions.append(action) return action def initGui(self): """Create the menu entries and toolbar icons inside the QGIS GUI.""" icon_path = os.path.join(BASE_DIR, 'icon.png') self.add_action( icon_path, text=self.tr('Paprika Toolbox'), callback=self.run, parent=self.iface.mainWindow()) def onClosePlugin(self): """Cleanup necessary items here when plugin dockwidget is closed""" self.dockwidget.closingPlugin.disconnect(self.onClosePlugin) self.dockwidget.mMapLayerComboBox_IMPLUVIUM.layerChanged.disconnect(self.update_raster_info) if self.extent_view is not None: self.iface.mapCanvas().scene().removeItem(self.extent_view) self.extent_view = None self.pluginIsActive = False def unload(self): """Removes the plugin menu item and icon from QGIS GUI.""" if self.extent_view is not None: self.iface.mapCanvas().scene().removeItem(self.extent_view) self.extent_view = None for action in self.actions: self.iface.removePluginMenu( self.tr('&Paprika'), action) self.iface.removeToolBarIcon(action) del self.toolbar def run(self): """Run method that loads and starts the plugin""" if not self.pluginIsActive: self.pluginIsActive = True if self.dockwidget is None: self.dockwidget = Ui_PaprikaDockWidgetBase() self.dockwidget.pushButton_Aide.clicked.connect(self.open_help) self.dockwidget.pushButton_methodo.clicked.connect(self.download_methodo) self.dockwidget.pushButton_lancerCarteP.clicked.connect(self.carte_p) self.dockwidget.pushButton_lancerCarteR.clicked.connect(self.carte_r) self.dockwidget.pushButton_lancerCarteI.clicked.connect(self.carte_i) self.dockwidget.pushButton_lancerCarteKa.clicked.connect(self.carte_ka) self.dockwidget.pushButton_lancerCarteFinale.clicked.connect(self.carte_finale) self.dockwidget.toolButton_dossier_travail.clicked.connect(self.open_directory) self.dockwidget.lineEdit_dossier_travail.setText(QSettings().value('Paprika_toolbox/current_directory', '')) self.dockwidget.pushButton_Apropos.clicked.connect(self.open_a_propos) # connect to provide cleanup on closing of dockwidget self.dockwidget.closingPlugin.connect(self.onClosePlugin) self.iface.addDockWidget(Qt.LeftDockWidgetArea, self.dockwidget) self.dockwidget.show() #filtrage et peuplement des ComboBox des layers self.dockwidget.mMapLayerComboBox_IMPLUVIUM.setFilters(QgsMapLayerProxyModel.VectorLayer) self.dockwidget.mMapLayerComboBox_SOL.setFilters(QgsMapLayerProxyModel.VectorLayer) self.dockwidget.mMapLayerComboBox_EPIKARST.setFilters(QgsMapLayerProxyModel.VectorLayer) self.dockwidget.mMapLayerComboBox_SINKING_CATCHMENT.setFilters(QgsMapLayerProxyModel.VectorLayer) self.dockwidget.mMapLayerComboBox_ROCHE.setFilters(QgsMapLayerProxyModel.VectorLayer) self.dockwidget.mMapLayerComboBox_STRUCTURE.setFilters(QgsMapLayerProxyModel.VectorLayer) self.dockwidget.mMapLayerComboBox_OBJETS_EXOKARSTIQUES.setFilters(QgsMapLayerProxyModel.VectorLayer) self.dockwidget.mMapLayerComboBox_KARST_FEATURES.setFilters(QgsMapLayerProxyModel.VectorLayer) self.dockwidget.mMapLayerComboBox_ZNS.setFilters(QgsMapLayerProxyModel.RasterLayer) self.dockwidget.mMapLayerComboBox_PENTE.setFilters(QgsMapLayerProxyModel.RasterLayer) self.dockwidget.mMapLayerComboBox_CartePF.setFilters(QgsMapLayerProxyModel.RasterLayer) self.dockwidget.mMapLayerComboBox_CarteRF.setFilters(QgsMapLayerProxyModel.RasterLayer) self.dockwidget.mMapLayerComboBox_CarteIF.setFilters(QgsMapLayerProxyModel.RasterLayer) self.dockwidget.mMapLayerComboBox_CarteKaF.setFilters(QgsMapLayerProxyModel.RasterLayer) # peuplement de la comboBox du critere de Mangin self.dockwidget.comboBox_MANGIN.clear() self.dockwidget.comboBox_MANGIN.addItems(['1', '2', '3', '4']) # peuplement des ComboBox des champs et gestion des criteres optionnels self.dockwidget.mMapLayerComboBox_IMPLUVIUM.layerChanged.connect(self.update_raster_info) self.dockwidget.cb_show_extent.toggled.connect(self.on_show_extent) #SOL self.dockwidget.mFieldComboBox_SOL.setFilters(QgsFieldProxyModel.Numeric) self.dockwidget.mFieldComboBox_SOL.setLayer(self.dockwidget.mMapLayerComboBox_SOL.currentLayer()) self.dockwidget.mMapLayerComboBox_SOL.layerChanged.connect(self.dockwidget.mFieldComboBox_SOL.setLayer) #EPIKARST self.dockwidget.mFieldComboBox_EPIKARST.setFilters(QgsFieldProxyModel.Numeric) self.dockwidget.mFieldComboBox_EPIKARST.setLayer(self.dockwidget.mMapLayerComboBox_EPIKARST.currentLayer()) self.dockwidget.mMapLayerComboBox_EPIKARST.layerChanged.connect(self.dockwidget.mFieldComboBox_EPIKARST.setLayer) #SINKING STREAM CATCHMENT self.dockwidget.mFieldComboBox_SINKING_CATCHMENT.setFilters(QgsFieldProxyModel.Numeric) self.dockwidget.mFieldComboBox_SINKING_CATCHMENT.setLayer(self.dockwidget.mMapLayerComboBox_SINKING_CATCHMENT.currentLayer()) self.dockwidget.mMapLayerComboBox_SINKING_CATCHMENT.layerChanged.connect(self.dockwidget.mFieldComboBox_SINKING_CATCHMENT.setLayer) #LITHOLOGY self.dockwidget.mFieldComboBox_ROCHE.setFilters(QgsFieldProxyModel.Numeric) self.dockwidget.mFieldComboBox_ROCHE.setLayer(self.dockwidget.mMapLayerComboBox_ROCHE.currentLayer()) self.dockwidget.mMapLayerComboBox_ROCHE.layerChanged.connect(self.dockwidget.mFieldComboBox_ROCHE.setLayer) #KARST FEATURES I self.dockwidget.mFieldComboBox_OBJETS_EXOKARSTIQUES.setFilters(QgsFieldProxyModel.Numeric) self.dockwidget.mFieldComboBox_OBJETS_EXOKARSTIQUES.setLayer(self.dockwidget.mMapLayerComboBox_OBJETS_EXOKARSTIQUES.currentLayer()) self.dockwidget.mMapLayerComboBox_OBJETS_EXOKARSTIQUES.layerChanged.connect(self.dockwidget.mFieldComboBox_OBJETS_EXOKARSTIQUES.setLayer) # mise a jour du total de la ponderation (Final Map) self.dockwidget.spinBox_PondP.setValue(int(QSettings().value('Paprika_toolbox/pondP', 25))) self.dockwidget.spinBox_PondR.setValue(int(QSettings().value('Paprika_toolbox/pondR', 20))) self.dockwidget.spinBox_PondI.setValue(int(QSettings().value('Paprika_toolbox/pondI', 30))) self.dockwidget.spinBox_PondKa.setValue(int(QSettings().value('Paprika_toolbox/pondKa', 25))) self.dockwidget.spinBox_PondP.valueChanged.connect(self.calcul_somme_pond) self.dockwidget.spinBox_PondR.valueChanged.connect(self.calcul_somme_pond) self.dockwidget.spinBox_PondI.valueChanged.connect(self.calcul_somme_pond) self.dockwidget.spinBox_PondKa.valueChanged.connect(self.calcul_somme_pond) self.calcul_somme_pond() self.update_raster_info() def calcul_somme_pond(self): P = self.dockwidget.spinBox_PondP.value() R = self.dockwidget.spinBox_PondR.value() I = self.dockwidget.spinBox_PondI.value() Ka = self.dockwidget.spinBox_PondKa.value() new_value = P + R + I + Ka self.dockwidget.label_sum_ponderation.setText(str(new_value) + ' %') if new_value == 100: self.dockwidget.pushButton_lancerCarteFinale.setEnabled(True) else: self.dockwidget.pushButton_lancerCarteFinale.setEnabled(False) def open_directory(self): """choose a directory to save the generated files""" directory = QFileDialog.getExistingDirectory(self.dockwidget.toolButton_dossier_travail, "Choose a directory to work with", QgsProject.instance().fileName(), QFileDialog.ShowDirsOnly) self.dockwidget.lineEdit_dossier_travail.setText(str(QtCore.QDir.toNativeSeparators(directory))) QSettings().setValue('Paprika_toolbox/current_directory', str(QtCore.QDir.toNativeSeparators(directory))) def update_raster_info(self): """Get raster extent and resolution from impluvium user choice and save it in the instance for use""" self.raster_info['resolution_x'] = self.dockwidget.spb_resolution.value() self.raster_info['resolution_y'] = self.dockwidget.spb_resolution.value() layer_impluvium = self.dockwidget.mMapLayerComboBox_IMPLUVIUM.currentLayer() if layer_impluvium and layer_impluvium.isValid(): self.raster_info['projection_wkt'] = layer_impluvium.crs().toWkt() feature_impluvium = next(layer_impluvium.getFeatures()) geom = feature_impluvium.geometry().buffer(1000, 5).boundingBox() Xmin = geom.xMinimum() Ymin = geom.yMinimum() Xmax = geom.xMaximum() Ymax = geom.yMaximum() self.raster_info['extent'] = {} self.raster_info['extent']['Xmin'] = Xmin self.raster_info['extent']['Ymin'] = Ymin self.raster_info['extent']['Xmax'] = Xmax self.raster_info['extent']['Ymax'] = Ymax self.raster_info['extent']['str_extent'] = ', '.join([str(Xmin), str(Xmax), str(Ymax), str(Ymin)]) self.raster_info['size_x'] = int(abs(Xmax - Xmin)/self.raster_info['resolution_x']) self.raster_info['size_y'] = int(abs(Ymax - Ymin)/self.raster_info['resolution_y']) if self.dockwidget.cb_show_extent.isChecked(): if self.extent_view is not None: self.iface.mapCanvas().scene().removeItem(self.extent_view) self.extent_view = QgsRubberBand(self.iface.mapCanvas()) self.extent_view.addGeometry(QgsGeometry.fromRect(QgsRectangle(Xmin, Ymin, Xmax, Ymax))) self.extent_view.setColor(QColor('#A43C27')) else: self.extent_view = QgsRubberBand(self.iface.mapCanvas()) self.extent_view.addGeometry(QgsGeometry.fromRect(QgsRectangle(Xmin, Ymin, Xmax, Ymax))) self.extent_view.setColor(QColor('#A43C27')) def on_show_extent(self): if self.dockwidget.cb_show_extent.isChecked(): if 'extent' in self.raster_info: corner = self.raster_info['extent'] self.extent_view = QgsRubberBand(self.iface.mapCanvas()) self.extent_view.addGeometry(QgsGeometry.fromRect(QgsRectangle(corner['Xmin'], corner['Ymin'], corner['Xmax'], corner['Ymax']))) self.extent_view.setColor(QColor('#A43C27')) else: if self.extent_view is not None: self.iface.mapCanvas().scene().removeItem(self.extent_view) self.extent_view = None def carte_p(self): """test les parametres et lance la generation de la carte P""" if self.dockwidget.mFieldComboBox_SOL.currentField() == u'': return self.showdialog('The index Field of Soil Protection Layer is not set...', 'Field issue...') # controle la validite des occurences du champ index value_sol = [feature.attribute(self.dockwidget.mFieldComboBox_SOL.currentField()) for feature in self.dockwidget.mMapLayerComboBox_SOL.currentLayer().getFeatures()] if min(value_sol) < 0 or max(value_sol) > 4 or len(value_sol) == 0 : return self.showdialog('The index Field of Soil Cover Layer has wrong value... (not between 0 and 4 or null)', 'Index issue...') if self.dockwidget.checkBox_Epikarst.isChecked(): # controle que la comboBox du champ Epikarst est bien remplie if self.dockwidget.mFieldComboBox_EPIKARST.currentField() == u'': return self.showdialog('The index Field of Epikarst Layer is not set...', 'Field issue...') # controle la validite des occurences du champ index value_epikarst = [feature.attribute(self.dockwidget.mFieldComboBox_EPIKARST.currentField()) for feature in self.dockwidget.mMapLayerComboBox_EPIKARST.currentLayer().getFeatures()] if min(value_epikarst) < 0 or max(value_epikarst) > 4 or len(value_epikarst) == 0 : return self.showdialog('The index Field of Epikarst Layer has wrong value... (not between 0 and 4 or null)', 'Index issue...') if self.dockwidget.checkBox_Sinking.isChecked(): # controle que la comboBox du champ Sinking Stream Catchment est bien remplie if self.dockwidget.mFieldComboBox_SINKING_CATCHMENT.currentField() == u'': return self.showdialog('The index Field of Sinking Stream Catchment Layer is not set...', 'Field issue...') # controle la validite des occurences des champs index value_sinking = [feature.attribute(self.dockwidget.mFieldComboBox_SINKING_CATCHMENT.currentField()) for feature in self.dockwidget.mMapLayerComboBox_SINKING_CATCHMENT.currentLayer().getFeatures()] if min(value_sinking) < 0 or max(value_sinking) > 4 or len(value_sinking) == 0 : return self.showdialog('The index''s field of Sinking catchment Layer has wrong value... (not between 0 and 4 or null)', 'Index issue...') sol = self.dockwidget.mMapLayerComboBox_SOL.currentLayer() field_sol = self.dockwidget.mFieldComboBox_SOL.currentField() #EPIKARST if self.dockwidget.checkBox_Epikarst.isChecked(): epikarst = self.dockwidget.mMapLayerComboBox_EPIKARST.currentLayer() field_epikarst = self.dockwidget.mFieldComboBox_EPIKARST.currentField() else : epikarst = None field_epikarst = None #SINKING CATCHMENT if self.dockwidget.checkBox_Sinking.isChecked(): sinking = self.dockwidget.mMapLayerComboBox_SINKING_CATCHMENT.currentLayer() field_sinking = self.dockwidget.mFieldComboBox_SINKING_CATCHMENT.currentField() else : sinking = None field_sinking = None #lance la generation de la carte P for lyr in QgsProject.instance().mapLayers().values(): if lyr.name() == "P factor": QgsProject.instance().removeMapLayers( [lyr.id()]) self.carte_p_worker = WorkerCarteP(self.raster_info, self.dockwidget.lineEdit_dossier_travail.text(), self.dockwidget.mMapLayerComboBox_ZNS.currentLayer(), sol, field_sol, epikarst, field_epikarst, sinking, field_sinking) self.carte_p_thread = QThread() self.carte_p_worker.results.connect(self.on_carte_p_results) self.carte_p_worker.progress.connect(self.on_progress) self.carte_p_worker.error.connect(self.on_error) self.carte_p_worker.finished.connect(self.on_carte_p_finished) self.carte_p_worker.moveToThread(self.carte_p_thread) self.carte_p_thread.started.connect(self.carte_p_worker.run) self.carte_p_thread.start() def on_carte_p_results(self): #genere le style et charge le tif dans QGIS avec un message lay_carteP = QgsRasterLayer(str(self.dockwidget.lineEdit_dossier_travail.text())+'/P_factor.tif','P factor') self.set_raster_style(lay_carteP) QgsProject.instance().addMapLayer(lay_carteP) self.showdialog('P factor map created wih success!', 'Well done!') def on_carte_p_finished(self): self.carte_p_thread.quit() self.carte_p_thread.wait() def carte_r(self): """teste les parametres et lance la generation de la carte R""" if self.dockwidget.mFieldComboBox_ROCHE.currentField() == u'': return self.showdialog('The index Field of Lithology Layer is not set...', 'Field issue...') value_lithology = [feature.attribute(self.dockwidget.mFieldComboBox_ROCHE.currentField()) for feature in self.dockwidget.mMapLayerComboBox_ROCHE.currentLayer().getFeatures()] if min(value_lithology) < 0 or max(value_lithology) > 4 or len(value_lithology) == 0 : return self.showdialog('The index Field of Lithology Layer has wrong value... (not between 0 and 4 or null)', 'Index issue...') lithology = self.dockwidget.mMapLayerComboBox_ROCHE.currentLayer() field_lithology = self.dockwidget.mFieldComboBox_ROCHE.currentField() if self.dockwidget.checkBox_STRUCTURE.isChecked(): structure = self.dockwidget.mMapLayerComboBox_STRUCTURE.currentLayer() else: structure = None for lyr in QgsProject.instance().mapLayers().values(): if lyr.name() == "R factor": QgsProject.instance().removeMapLayers( [lyr.id()] ) self.carte_r_worker = WorkerCarteR(self.dockwidget.lineEdit_dossier_travail.text(), self.raster_info, lithology, field_lithology, structure) self.carte_r_thread = QThread() self.carte_r_worker.results.connect(self.on_carte_r_results) self.carte_r_worker.progress.connect(self.on_progress) self.carte_r_worker.error.connect(self.on_error) self.carte_r_worker.finished.connect(self.on_carte_r_finished) self.carte_r_worker.moveToThread(self.carte_r_thread) self.carte_r_thread.started.connect(self.carte_r_worker.run) self.carte_r_thread.start() def on_carte_r_results(self): lay_carteR = QgsRasterLayer(str(self.dockwidget.lineEdit_dossier_travail.text()) + '/R_factor.tif', 'R factor') self.set_raster_style(lay_carteR) QgsProject.instance().addMapLayer(lay_carteR) self.showdialog('R factor map created wih success!', 'Well done!') def on_carte_r_finished(self): self.carte_p_thread.quit() self.carte_p_thread.wait() def carte_i(self): """teste les parametres et lance la generation de la carte I""" if self.dockwidget.checkBox_OBJETS_EXOKARSTIQUES.isChecked(): if self.dockwidget.mFieldComboBox_OBJETS_EXOKARSTIQUES.currentField() == '': return self.showdialog('The index Field of Karst features Layer is not set...', 'Field issue...') value_objets_exokarstiques = [feature.attribute(self.dockwidget.mFieldComboBox_OBJETS_EXOKARSTIQUES.currentField()) for feature in self.dockwidget.mMapLayerComboBox_OBJETS_EXOKARSTIQUES.currentLayer().getFeatures()] if min(value_objets_exokarstiques) < 0 or max(value_objets_exokarstiques) > 4 : return self.showdialog('The index Field of Karst features Layer has wrong value... (not between 0 and 4 or null)', 'Index issue...') rules = self.generate_reclass_rules_slope(self.dockwidget.spinBox_first_threshold.value(),self.dockwidget.spinBox_second_threshold.value(),self.dockwidget.spinBox_third_threshold.value()) pente = self.dockwidget.mMapLayerComboBox_PENTE.currentLayer() if self.dockwidget.checkBox_OBJETS_EXOKARSTIQUES.isChecked(): exokarst = self.dockwidget.mMapLayerComboBox_OBJETS_EXOKARSTIQUES.currentLayer() field_exokarst = self.dockwidget.mFieldComboBox_OBJETS_EXOKARSTIQUES.currentField() else: exokarst = None field_exokarst = None for lyr in QgsProject.instance().mapLayers().values(): if lyr.name() == "I Factor": QgsProject.instance().removeMapLayers( [lyr.id()] ) self.carte_i_worker = WorkerCarteI(self.dockwidget.lineEdit_dossier_travail.text(), self.raster_info, pente, rules, exokarst, field_exokarst) self.carte_i_thread = QThread() self.carte_i_worker.results.connect(self.on_carte_i_results) self.carte_i_worker.progress.connect(self.on_progress) self.carte_i_worker.error.connect(self.on_error) self.carte_i_worker.finished.connect(self.on_carte_i_finished) self.carte_i_worker.moveToThread(self.carte_i_thread) self.carte_i_thread.started.connect(self.carte_i_worker.run) self.carte_i_thread.start() def on_carte_i_results(self): lay_carteI = QgsRasterLayer(str(self.dockwidget.lineEdit_dossier_travail.text())+'/I_factor.tif', 'I factor') self.set_raster_style(lay_carteI) QgsProject.instance().addMapLayer(lay_carteI) self.showdialog('I factor map created wih success!', 'Well done!') def on_carte_i_finished(self): self.carte_i_thread.quit() self.carte_i_thread.wait() def carte_ka(self): """teste les parametres et lance la generation de la carte Ka""" if self.dockwidget.checkBox_KARST_FEATURES.isChecked(): karst_features = self.dockwidget.mMapLayerComboBox_KARST_FEATURES.currentLayer() else: karst_features = None #genere le tif for lyr in QgsProject.instance().mapLayers().values(): if lyr.name() == "Ka factor": QgsProject.instance().removeMapLayers([lyr.id()]) self.carte_ka_worker = WorkerCarteKa(self.dockwidget.lineEdit_dossier_travail.text(), self.raster_info, int(self.dockwidget.comboBox_MANGIN.currentText()), karst_features) self.carte_ka_thread = QThread() self.carte_ka_worker.results.connect(self.on_carte_ka_results) self.carte_ka_worker.progress.connect(self.on_progress) self.carte_ka_worker.error.connect(self.on_error) self.carte_ka_worker.finished.connect(self.on_carte_ka_finished) self.carte_ka_worker.moveToThread(self.carte_ka_thread) self.carte_ka_thread.started.connect(self.carte_ka_worker.run) self.carte_ka_thread.start() def on_carte_ka_results(self): lay_carteKa = QgsRasterLayer(str(self.dockwidget.lineEdit_dossier_travail.text())+'/Ka_factor.tif','Ka factor') self.set_raster_style(lay_carteKa) QgsProject.instance().addMapLayer(lay_carteKa) self.showdialog('Ka factor map created wih success!', 'Well done!') def on_carte_ka_finished(self): self.carte_ka_thread.quit() self.carte_ka_thread.wait() def carte_finale(self): pP=self.dockwidget.spinBox_PondP.value() pR=self.dockwidget.spinBox_PondR.value() pI=self.dockwidget.spinBox_PondI.value() pKa=self.dockwidget.spinBox_PondKa.value() QSettings().setValue('Paprika_toolbox/pondP', pP) QSettings().setValue('Paprika_toolbox/pondR', pR) QSettings().setValue('Paprika_toolbox/pondI', pI) QSettings().setValue('Paprika_toolbox/pondKa', pKa) if pI + pKa + pP + pR != 100: return self.showdialog('weight sum must be egal at 100%!', 'invalid weight...') #supprime la couche si elle est deja chargee et genere le tif for lyr in QgsProject.instance().mapLayers().values(): if lyr.name() == "Vulnerability Map": QgsProject.instance().removeMapLayers( [lyr.id()] ) self.carte_finale_worker = WorkerCarteFinale(self.dockwidget.lineEdit_dossier_travail.text(), self.raster_info, self.dockwidget.spinBox_PondP.value(), self.dockwidget.spinBox_PondR.value(), self.dockwidget.spinBox_PondI.value(), self.dockwidget.spinBox_PondKa.value(), self.dockwidget.mMapLayerComboBox_CartePF.currentLayer(), self.dockwidget.mMapLayerComboBox_CarteRF.currentLayer(), self.dockwidget.mMapLayerComboBox_CarteIF.currentLayer(), self.dockwidget.mMapLayerComboBox_CarteKaF.currentLayer()) self.carte_finale_thread = QThread() self.carte_finale_worker.results.connect(self.on_carte_finale_results) self.carte_finale_worker.progress.connect(self.on_progress) self.carte_finale_worker.error.connect(self.on_error) self.carte_finale_worker.finished.connect(self.on_carte_finale_finished) self.carte_finale_worker.moveToThread(self.carte_finale_thread) self.carte_finale_thread.started.connect(self.carte_finale_worker.run) self.carte_finale_thread.start() def on_carte_finale_results(self): lay_carteFinale = QgsRasterLayer(str(self.dockwidget.lineEdit_dossier_travail.text())+'/Vulnerability_Map.tif','Vulnerability Map') self.set_raster_style(lay_carteFinale) QgsProject.instance().addMapLayer(lay_carteFinale) return self.showdialog('Final map created wih success!', 'Well done!') def on_carte_finale_finished(self): self.carte_finale_thread.quit() self.carte_finale_thread.wait() def on_error(self, e): """If something wrong happen in the thread, show what...""" self.showdialog(str(e), 'Oups!') def on_progress(self, value, tot): """Update the progress bar from thread""" self.dockwidget.progress.setMaximum(100) self.dockwidget.progress.setValue((value*100/tot) + 1) def showdialog (self, text, title): """Just a wrapper to show message popup to user""" msg = QMessageBox() msg.setIcon(QMessageBox.Warning) msg.setText(text) msg.setWindowTitle(title) msg.setStandardButtons(QMessageBox.Ok) msg.exec_() def set_raster_style(self, raster_layer): """Set the style for output""" s = QgsRasterShader() c = QgsColorRampShader() c.setColorRampType(QgsColorRampShader.Exact) i = [] i.append(QgsColorRampShader.ColorRampItem(0, QColor('#FFFFFF'), '0')) i.append(QgsColorRampShader.ColorRampItem(1, QColor('#0040FF'), '1')) i.append(QgsColorRampShader.ColorRampItem(2, QColor('#A8D990'), '2')) i.append(QgsColorRampShader.ColorRampItem(3, QColor('#F6F085'), '3')) i.append(QgsColorRampShader.ColorRampItem(4, QColor('#E6A55B'), '4')) i.append(QgsColorRampShader.ColorRampItem(5, QColor('#A43C27'), '5')) c.setColorRampItemList(i) s.setRasterShaderFunction(c) ps = QgsSingleBandPseudoColorRenderer(raster_layer.dataProvider(), 1, s) raster_layer.setRenderer(ps) def open_a_propos(self): """Open About dialog""" a_propos = Ui_A_propos() a_propos.exec() def download_methodo(self): """Open webbrowser on official methodology page""" webbrowser.open_new('http://infoterre.brgm.fr/rapports/RP-57527-FR.pdf') webbrowser.open_new_tab('http://link.springer.com/article/10.1007/s10040-010-0688-8') def open_help(self): """Open pdf docs. Platform dependent""" if os.name == 'nt': os.startfile(os.path.dirname(os.path.abspath(__file__))+'/doc/Paprika_Toolbox_User_guide.pdf') elif os.name == 'posix': subprocess.call(["xdg-open", os.path.dirname(os.path.abspath(__file__))+'/doc/Paprika_Toolbox_User_guide.pdf']) def generate_reclass_rules_slope(self,first,second,third): """Generate the list that contains rules for slope reclassify""" return [-1, first, 4, first, second, 3, second, third, 2, third, 99999999, 1]
class MainWindow(QMainWindow): """ Main window class. Includes the main window itself as well as handling of it's signals """ upload_pictures = pyqtSignal(list) def __init__(self, **kwargs): super().__init__(**kwargs) load_ui('MainWindow.ui', self) self.setWindowTitle('Picup - {}'.format(__version__)) apikey = get_api_key() if not apikey: apikey = self.request_api_key() self.legal_resize = True self.upload_in_progress = False self.upload_thread = QThread(parent=self) self.upload = Upload(apikey=apikey) self.upload_thread.start() self.upload.moveToThread(self.upload_thread) self.list_view_files_model = FileListModel(parent=self) self.list_view_files.setModel(self.list_view_files_model) self.pushButton_close.clicked.connect(self.shutdown) self.pushButton_add_picture.clicked.connect(self.add_file) self.pushButton_add_links.clicked.connect(self.add_url) self.pushButton_upload.clicked.connect(self.start_upload) self.pushButton_clear_list.clicked.connect( self.list_view_files_model.clear_list) self.pushButton_remove_selected.clicked.connect(self.remove_selected) self.upload.upload_finished.connect(self.upload_finished) self.upload.upload_error.connect(self.handle_error) self.upload_pictures.connect(self.upload.upload_multiple) self.dialog = QFileDialog(parent=self) self.dialog.setFileMode(QFileDialog.ExistingFiles) self.dialog.setNameFilters(SUPPORTED_FILE_TYPES) self.resize_container.hide() self.resize_container_percentual.hide() self.check_box_resize.clicked.connect( self.set_resize_box_visibility ) self.radio_button_absolute.toggled.connect( self.set_absolute_resize_box_visibility ) self.radio_button_percentual.toggled.connect( self.set_percentual_resize_box_visibility ) self.spin_box_width.valueChanged.connect(self.update_resize) self.spin_box_higth.valueChanged.connect(self.update_resize) self.spin_box_percentual.valueChanged.connect(self.update_resize) self.comboBox_rotate_options.activated['QString'].connect( self.upload.change_default_rotation ) self.checkBox_delete_exif.toggled.connect( self.upload.change_default_exif ) self.comboBox_rotate_options.addItems(ALLOWED_ROTATION) def request_api_key(self,): """ requests and stores an api key from the user, if non is stores yet. If none is given a default one is used. """ window = KeyRequest(parent=self) if window.exec_(): apikey = window.lineEdit_apikey.text() if apikey: set_api_key(apikey) return apikey return DEFAULT_API_KEY sys.exit(0) @pyqtSlot() def add_file(self): """ add file(s) to the upload list. using Qts file dialog. """ if self.dialog.exec_(): files = self.dialog.selectedFiles() files = [(file_, 'file') for file_ in files] self.list_view_files_model.add_files(files) @pyqtSlot() def add_url(self,): """ add url(s) to the upload list. using a text box. """ url_input = UrlInput() code = url_input.exec_() urls = url_input.text() new_entrys = [] not_added = [] if code and urls != '': for url in urls.split('\n'): # skip empty lines if url == '': continue parsed_url = urlparse(url, scheme='http') scheme = parsed_url.scheme.lower() if scheme in ['http', 'https', 'ftp']: new_entrys.append((urlunparse(parsed_url), 'url')) else: not_added.append(url) if not_added: message = QMessageBox(QMessageBox.Warning, 'Fehler', ('Ein oder mehrere link(s) konnten ' 'nicht hinzugefügt werden.'), buttons=QMessageBox.Ok, parent=self) message.setDetailedText('\n'.join(not_added)) self.list_view_files_model.add_files(new_entrys) @pyqtSlot() def start_upload(self,): """ starts the upload and does some setup for the status/result dialog. As well as some cleanup afterwards. It locks the application for any further uploads until this one is \ finished. """ if (len(self.list_view_files_model.files) and not self.upload_in_progress and self.legal_resize): self.upload_in_progress = True files = self.list_view_files_model.files.copy() link_dialog = ShowLinks(self.upload, len(files), parent=self) link_dialog.readd_pictures.connect( self.list_view_files_model.add_files ) link_dialog.show() LOGGER.debug('emitting upload signal with arguments: %s', files) self.upload_pictures.emit(files) LOGGER.debug('cleanup main window') self.list_view_files_model.clear_list() elif self.upload_in_progress: LOGGER.debug('Upload already in progress.') QMessageBox.warning(self, 'Upload Läuft', 'Es läuft bereits ein Upload Prozess.') elif not self.legal_resize: LOGGER.debug('illegal resize string will not upload.') # pylint: disable=line-too-long # would harm readability QMessageBox.warning(self, 'Auflösung ungültig', ('Die für die Skalierung angegebene Auflösung ist ungültig. ' 'Bitte gib diese im folgendem format an: breite x höhe') ) else: LOGGER.info('There is nothing to upload.') QMessageBox.information(self, 'Nüx da', ('Es wurden keine bilder zum hochladen ' 'hinzugefügt')) @pyqtSlot() def upload_finished(self,): """ called through a signal after upload is finished to release the lock. """ self.upload_in_progress = False @pyqtSlot(type, tuple) def handle_error(self, exception_type, args): """ displays informations about an exception. """ message = QMessageBox(QMessageBox.Warning, 'Fehler', 'Fehler beim upload.', buttons=QMessageBox.Ok, parent=self) message.setDetailedText(repr(exception_type) + '\n' + repr(args)) message.exec_() @pyqtSlot() def update_resize(self,): if (self.check_box_resize.isChecked() and self.radio_button_absolute.isChecked()): width = self.spin_box_width.value() higth = self.spin_box_higth.value() self.upload.change_default_resize("{}x{}".format(width, higth)) elif (self.check_box_resize.isChecked() and self.radio_button_percentual.isChecked()): percentage = self.spin_box_percentual.value() self.upload.change_default_resize("{}%".format(percentage)) else: self.upload.change_default_resize(None) @pyqtSlot(bool) def set_resize_box_visibility(self, visible): if visible: LOGGER.debug('show resize box') self.update_resize() else: LOGGER.debug('hide resize box') self.update_resize() self.resize_container.setVisible(visible) @pyqtSlot(bool) def set_absolute_resize_box_visibility(self, visible): if visible: LOGGER.debug('show absolute resize box') self.update_resize() else: LOGGER.debug('hide absolute resize box') self.resize_container_absolute.setVisible(visible) @pyqtSlot(bool) def set_percentual_resize_box_visibility(self, visible): if visible: LOGGER.debug('show percentual resize box') self.update_resize() else: LOGGER.debug('hide percentual resize box') self.resize_container_percentual.setVisible(visible) @pyqtSlot() def remove_selected(self,): """ remove selected files from the upload list. """ for item in self.list_view_files.selectedIndexes(): self.list_view_files_model.remove_element(item.row(), item.row()) @pyqtSlot() def display_about_qt(self,): """ displays the about qt dialog """ QMessageBox.aboutQt(self,) @pyqtSlot() def shutdown(self,): """shut down Qapp""" self.thread_cleanup() QCoreApplication.instance().quit() def thread_cleanup(self): """ shuts down the upload thread at exit. """ LOGGER.debug('begin cleanup threads') try: self.upload_thread.quit() self.upload_thread.wait() # pylint: disable=bare-except # I do want to catch them all here, to be able to log them. except: LOGGER.exception('Exception while cleanup') LOGGER.debug('thread cleanup finished')
class mainWindow(QMainWindow, Ui_MainWindow): """ The main class for the GUI window """ def __init__(self): """ The constructor and initiator. :return: """ # initial setup super(mainWindow, self).__init__() self.setupUi(self) self.thread = QThread() self.connected = False # Connect signals self.connect_signals() def on_push_button_clicked(self): if self.pushButton.isChecked(): try: host = str(ip_address(self.lineEdit_host.text())) port = self.lineEdit_port.text() if not port.isdigit(): raise ValueError except(ValueError): self.pushButton.setChecked(False) self.show_message('Please enter valid numeric IP address and port number.') return self.zeromq_listener_10001 = ZMQListener(host, port, '10001') self.zeromq_listener_10001.moveToThread(self.thread) self.zeromq_listener_10001.message.connect(self.signal_received_10001) self.zeromq_listener_10001.err_msg.connect(self.show_message) self.thread.started.connect(self.zeromq_listener_10001.loop) self.thread.start() self.pushButton.setText('Stop') self.show_message('Connected to server: {}:{}'.format(host, port)) else: self.zeromq_listener_10001.running = False self.thread.terminate() self.pushButton.setText('Start') self.show_message('Disconnected.') def connect_signals(self): """ Connects signals. :return: """ # Action about and Action quit will be shown differently in OSX self.actionAbout.triggered.connect(self.show_about_dialog) self.actionQuit.triggered.connect(self.shutdown) self.pushButton.clicked.connect(self.on_push_button_clicked) def shutdown(self): self.zeromq_listener_10001.running = False self.thread.terminate() self.thread.quit() self.thread.wait() QCoreApplication.instance().quit() def signal_received_10001(self, message): # get the message and split it topic, time, stat_bits, value_str = message.split() current_range = int(stat_bits[-3:], 2) range_str = RANGE_DIC[current_range] self.label_time_stamp.setText(time) self.label_status.setText(stat_bits) self.label_range.setText(range_str) # do the calibration value_float = float(value_str) * CAL_SLOPE + CAL_ITCPT # convert binary to float value value = value_float * RAIL_VOLTAGE / ADC_QUANTIZATION # set to 2 decimal points value = int(value * 100) / 100 # in case more digits are needed # self.lcdNumber.setDigitCount(8) if self.zeromq_listener_10001.running: self.lcdNumber.display(value) else: self.lcdNumber.display(0) def closeEvent(self, event): self.zeromq_listener_10001.running = False self.thread.terminate() self.thread.quit() self.thread.wait() def show_message(self, message): """ Implementation of an abstract method: Show text in status bar :param message: :return: """ self.statusbar.showMessage(message) def show_about_dialog(self): """ Show about dialog :return: """ about_dialog = QDialog() about_dialog.ui = Ui_AbooutDialog() about_dialog.ui.setupUi(about_dialog) about_dialog.ui.labelVersion.setText('Version: {}'.format(__version__)) about_dialog.exec_() about_dialog.show()
class XAnoS_Reducer(QWidget): """ This widget is developed to reduce on the fly 2D SAXS data to azimuthally averaged 1D SAXS data """ def __init__(self,poniFile=None,dataFile=None, darkFile=None, maskFile=None,extractedFolder='/tmp', npt=1000, azimuthalRange=(-180.0,180.0), parent=None): """ poniFile is the calibration file obtained after Q-calibration """ QWidget.__init__(self,parent) self.setup_dict=json.load(open('./SetupData/reducer_setup.txt','r')) if poniFile is not None: self.poniFile=poniFile else: self.poniFile=self.setup_dict['poniFile'] if maskFile is not None: self.maskFile=maskFile else: self.maskFile=self.setup_dict['maskFile'] self.dataFile=dataFile if darkFile is None: self.dark_corrected=False self.darkFile='' else: self.darkFile=darkFile self.dark_corrected=True self.curDir=os.getcwd() self.extractedBaseFolder=extractedFolder self.npt=npt self.set_externally=False #ai=AIWidget() #self.layout.addWidget(ai) self.azimuthalRange=azimuthalRange self.create_UI() if os.path.exists(self.poniFile): self.openPoniFile(file=self.poniFile) if os.path.exists(self.maskFile): self.openMaskFile(file=self.maskFile) self.clientRunning=False def create_UI(self): """ Creates the widget user interface """ loadUi('UI_Forms/Data_Reduction_Client.ui',self) self.poniFileLineEdit.setText(str(self.poniFile)) self.maskFileLineEdit.setText(str(self.maskFile)) self.darkFileLineEdit.setText(str(self.darkFile)) self.extractedBaseFolderLineEdit.setText(self.extractedBaseFolder) self.radialPointsLineEdit.setText(str(self.npt)) self.openDataPushButton.clicked.connect(self.openDataFiles) self.reducePushButton.clicked.connect(self.reduce_multiple) self.openDarkPushButton.clicked.connect(self.openDarkFile) self.openPoniPushButton.clicked.connect(lambda x: self.openPoniFile(file=None)) self.calibratePushButton.clicked.connect(self.calibrate) self.maskFileLineEdit.returnPressed.connect(self.maskFileChanged) self.openMaskPushButton.clicked.connect(lambda x: self.openMaskFile(file=None)) self.createMaskPushButton.clicked.connect(self.createMask) self.extractedFolderPushButton.clicked.connect(self.openFolder) self.extractedFolderLineEdit.textChanged.connect(self.extractedFolderChanged) self.polCorrComboBox.currentIndexChanged.connect(self.polarizationChanged) self.polarizationChanged() self.radialPointsLineEdit.returnPressed.connect(self.nptChanged) self.azimuthalRangeLineEdit.returnPressed.connect(self.azimuthalRangeChanged) self.azimuthalRangeChanged() #self.statusLabel.setStyleSheet("color:rgba(0,1,0,0)") self.imageWidget=Image_Widget(zeros((100,100))) self.cakedImageWidget=Image_Widget(zeros((100,100))) imgNumberLabel=QLabel('Image number') self.imgNumberSpinBox=QSpinBox() self.imgNumberSpinBox.setSingleStep(1) self.imageWidget.imageLayout.addWidget(imgNumberLabel,row=2,col=1) self.imageWidget.imageLayout.addWidget(self.imgNumberSpinBox,row=2,col=2) self.imageView=self.imageWidget.imageView.getView() self.plotWidget=PlotWidget() self.plotWidget.setXLabel('Q, Å<sup>-1</sup>',fontsize=5) self.plotWidget.setYLabel('Intensity',fontsize=5) self.tabWidget.addTab(self.plotWidget,'Reduced 1D-data') self.tabWidget.addTab(self.imageWidget,'Masked 2D-data') self.tabWidget.addTab(self.cakedImageWidget,'Reduced Caked Data') self.serverAddress=self.serverAddressLineEdit.text() self.startClientPushButton.clicked.connect(self.startClient) self.stopClientPushButton.clicked.connect(self.stopClient) self.serverAddressLineEdit.returnPressed.connect(self.serverAddressChanged) self.startServerPushButton.clicked.connect(self.startServer) self.stopServerPushButton.clicked.connect(self.stopServer) def startServer(self): serverAddr=self.serverAddressLineEdit.text() dataDir=QFileDialog.getExistingDirectory(self,'Select data folder',options=QFileDialog.ShowDirsOnly) self.serverStatusLabel.setText('<font color="Red">Transmitting</font>') QApplication.processEvents() self.serverThread=QThread() self.zeromq_server=ZeroMQ_Server(serverAddr,dataDir) self.zeromq_server.moveToThread(self.serverThread) self.serverThread.started.connect(self.zeromq_server.loop) self.zeromq_server.messageEmitted.connect(self.updateServerMessage) self.zeromq_server.folderFinished.connect(self.serverDone) QTimer.singleShot(0,self.serverThread.start) def updateServerMessage(self,mesg): #self.serverStatusLabel.setText('<font color="Red">Transmitting</font>') self.serverMessageLabel.setText('Server sends: %s'%mesg) QApplication.processEvents() def serverDone(self): self.serverStatusLabel.setText('<font color="Green">Idle</font>') self.zeromq_server.socket.unbind(self.zeromq_server.socket.last_endpoint) self.serverThread.quit() self.serverThread.wait() self.serverThread.deleteLater() self.zeromq_server.deleteLater() def stopServer(self): try: self.zeromq_server.running=False self.serverStatusLabel.setText('<font color="Green">Idle</font>') self.zeromq_server.socket.unbind(self.zeromq_server.socket.last_endpoint) self.serverThread.quit() self.serverThread.wait() self.serverThread.deleteLater() self.zeromq_server.deleteLater() except: QMessageBox.warning(self,'Server Error','Start the server before stopping it') def enableClient(self,enable=True): self.startClientPushButton.setEnabled(enable) self.stopClientPushButton.setEnabled(enable) def enableServer(self,enable=True): self.startServerPushButton.setEnabled(enable) self.stopServerPushButton.setEnabled(enable) def startClient(self): if self.clientRunning: self.stopClient() else: self.clientFree=True self.clientRunning=True self.files=[] self.listenerThread = QThread() addr=self.clientAddressLineEdit.text() self.zeromq_listener = ZeroMQ_Listener(addr) self.zeromq_listener.moveToThread(self.listenerThread) self.listenerThread.started.connect(self.zeromq_listener.loop) self.zeromq_listener.messageReceived.connect(self.signal_received) QTimer.singleShot(0, self.listenerThread.start) QTimer.singleShot(0,self.clientReduce) self.clientStatusLabel.setText('<font color="red">Connected</font>') def stopClient(self): try: self.clientRunning=False self.clientFree=False self.zeromq_listener.messageReceived.disconnect() self.zeromq_listener.running=False self.listenerThread.quit() self.listenerThread.wait() self.listenerThread.deleteLater() self.zeromq_listener.deleteLater() self.clientStatusLabel.setText('<font color="green">Idle</font>') except: QMessageBox.warning(self,'Client Error', 'Please start the client first before closing.',QMessageBox.Ok) def serverAddressChanged(self): if self.clientRunning: self.startClient() def signal_received(self, message): self.clientMessageLabel.setText('Client receives: %s'%message) if 'dark.edf' not in message: self.files.append(message) def clientReduce(self): while self.clientFree: QApplication.processEvents() if len(self.files)>0: message=self.files[0] self.dataFiles=[message] self.dataFileLineEdit.setText(str(self.dataFiles)) self.extractedBaseFolder=os.path.dirname(message) self.extractedFolder=os.path.join(self.extractedBaseFolder,self.extractedFolderLineEdit.text()) if not os.path.exists(self.extractedFolder): os.makedirs(self.extractedFolder) self.extractedBaseFolderLineEdit.setText(self.extractedBaseFolder) self.set_externally=True self.reduce_multiple() self.set_externally=False self.files.pop(0) def closeEvent(self, event): if self.clientRunning: self.stopClient() event.accept() def polarizationChanged(self): if self.polCorrComboBox.currentText()=='Horizontal': self.polarization_factor=1 elif self.polCorrComboBox.currentText()=='Vertical': self.polarization_factor=-1 elif self.polCorrComboBox.currentText()=='Circular': self.polarization_factor=0 else: self.polarization_factor=None def createMask(self): """ Opens a mask-widget to create mask file """ fname=str(QFileDialog.getOpenFileName(self,'Select an image file', directory=self.curDir,filter='Image file (*.edf *.tif)')[0]) if fname is not None or fname!='': img=fb.open(fname).data self.maskWidget=MaskWidget(img) self.maskWidget.saveMaskPushButton.clicked.disconnect() self.maskWidget.saveMaskPushButton.clicked.connect(self.save_mask) self.maskWidget.show() else: QMessageBox.warning(self,'File error','Please import a data file first for creating the mask',QMessageBox.Ok) def maskFileChanged(self): """ Changes the mask file """ maskFile=str(self.maskFileLineEdit.text()) if str(maskFile)=='': self.maskFile=None elif os.path.exists(maskFile): self.maskFile=maskFile else: self.maskFile=None def save_mask(self): """ Saves the entire mask combining all the shape ROIs """ fname=str(QFileDialog.getSaveFileName(filter='Mask Files (*.msk)')[0]) name,extn=os.path.splitext(fname) if extn=='': fname=name+'.msk' elif extn!='.msk': QMessageBox.warning(self,'File extension error','Please donot provide file extension other than ".msk". Thank you!') return else: tmpfile=fb.edfimage.EdfImage(data=self.maskWidget.full_mask_data.T,header=None) tmpfile.save(fname) self.maskFile=fname self.maskFileLineEdit.setText(self.maskFile) def calibrate(self): """ Opens a calibartion widget to create calibration file """ fname=str(QFileDialog.getOpenFileName(self,'Select calibration image',directory=self.curDir, filter='Calibration image (*.edf *.tif)')[0]) if fname is not None: img=fb.open(fname).data if self.maskFile is not None: try: mask=fb.open(self.maskFile).data except: QMessageBox.warning(self,'Mask File Error','Cannot open %s.\n No masking will be done.'%self.maskFile) mask=None else: mask=None pixel1=79.0 pixel2=79.0 self.calWidget=CalibrationWidget(img,pixel1,pixel2,mask=mask) self.calWidget.saveCalibrationPushButton.clicked.disconnect() self.calWidget.saveCalibrationPushButton.clicked.connect(self.save_calibration) self.calWidget.show() else: QMessageBox.warning(self,'File error','Please import a data file first for creating the calibration file',QMessageBox.Ok) def save_calibration(self): fname=str(QFileDialog.getSaveFileName(self,'Calibration file',directory=self.curDir,filter='Clibration files (*.poni)')[0]) tfname=os.path.splitext(fname)[0]+'.poni' self.calWidget.applyPyFAI() self.calWidget.geo.save(tfname) self.poniFile=tfname self.poniFileLineEdit.setText(self.poniFile) self.openPoniFile(file=self.poniFile) def openPoniFile(self,file=None): """ Select and imports the calibration file """ if file is None: self.poniFile=QFileDialog.getOpenFileName(self,'Select calibration file',directory=self.curDir,filter='Calibration file (*.poni)')[0] self.poniFileLineEdit.setText(self.poniFile) else: self.poniFile=file if os.path.exists(self.poniFile): self.setup_dict['poniFile']=self.poniFile json.dump(self.setup_dict,open('./SetupData/reducer_setup.txt','w')) fh=open(self.poniFile,'r') lines=fh.readlines() self.calib_data={} for line in lines: if line[0]!='#': key,val=line.split(': ') self.calib_data[key]=float(val) self.dist=self.calib_data['Distance'] self.pixel1=self.calib_data['PixelSize1'] self.pixel2=self.calib_data['PixelSize2'] self.poni1=self.calib_data['Poni1'] self.poni2=self.calib_data['Poni2'] self.rot1=self.calib_data['Rot1'] self.rot2=self.calib_data['Rot2'] self.rot3=self.calib_data['Rot3'] self.wavelength=self.calib_data['Wavelength'] self.ai=AzimuthalIntegrator(dist=self.dist,poni1=self.poni1,poni2=self.poni2,pixel1=self.pixel1,pixel2=self.pixel2,rot1=self.rot1,rot2=self.rot2,rot3=self.rot3,wavelength=self.wavelength) #pos=[self.poni2/self.pixel2,self.poni1/self.pixel1] #self.roi=cake(pos,movable=False) #self.roi.sigRegionChangeStarted.connect(self.endAngleChanged) #self.imageView.addItem(self.roi) else: QMessageBox.warning(self,'File error','The calibration file '+self.poniFile+' doesnot exists.',QMessageBox.Ok) def endAngleChanged(self,evt): print(evt.pos()) def nptChanged(self): """ Changes the number of radial points """ try: self.npt=int(self.radialPointsLineEdit.text()) except: QMessageBox.warning(self,'Value error', 'Please input positive integers only.',QMessageBox.Ok) def azimuthalRangeChanged(self): """ Changes the azimuth angular range """ try: self.azimuthalRange=tuple(map(float, self.azimuthalRangeLineEdit.text().split(':'))) except: QMessageBox.warning(self,'Value error','Please input min:max angles in floating point numbers',QMessageBox.Ok) def openDataFile(self): """ Select and imports one data file """ dataFile=QFileDialog.getOpenFileName(self,'Select data file',directory=self.curDir,filter='Data file (*.edf *.tif)')[0] if dataFile!='': self.dataFile=dataFile self.curDir=os.path.dirname(self.dataFile) self.dataFileLineEdit.setText(self.dataFile) self.data2d=fb.open(self.dataFile).data if self.darkFile is not None: self.applyDark() if self.maskFile is not None: self.applyMask() self.imageWidget.setImage(self.data2d,transpose=True) self.tabWidget.setCurrentWidget(self.imageWidget) if not self.set_externally: self.extractedFolder=os.path.join(self.curDir,self.extractedFolderLineEdit.text()) if not os.path.exists(self.extractedFolder): os.makedirs(self.extractedFolder) def openDataFiles(self): """ Selects and imports multiple data files """ self.dataFiles=QFileDialog.getOpenFileNames(self,'Select data files', directory=self.curDir,filter='Data files (*.edf *.tif)')[0] if len(self.dataFiles)!=0: self.imgNumberSpinBox.valueChanged.connect(self.imageChanged) self.imgNumberSpinBox.setMinimum(0) self.imgNumberSpinBox.setMaximum(len(self.dataFiles)-1) self.dataFileLineEdit.setText(str(self.dataFiles)) self.curDir=os.path.dirname(self.dataFiles[0]) self.extractedBaseFolder=self.curDir self.extractedFolder=os.path.abspath(os.path.join(self.extractedBaseFolder,self.extractedFolderLineEdit.text())) if not os.path.exists(self.extractedFolder): os.makedirs(self.extractedFolder) self.extractedBaseFolderLineEdit.setText(self.extractedBaseFolder) self.imgNumberSpinBox.setValue(0) self.imageChanged() def imageChanged(self): self.data2d=fb.open(self.dataFiles[self.imgNumberSpinBox.value()]).data if self.darkFile is not None: self.applyDark() if self.maskFile is not None: self.applyMask() self.imageWidget.setImage(self.data2d,transpose=True) def applyDark(self): if not self.dark_corrected and self.darkFile!='': self.dark2d=fb.open(self.darkFile).data self.data2d=self.data2d-self.dark2d self.dark_corrected=True def applyMask(self): self.mask2d=fb.open(self.maskFile).data self.data2d=self.data2d*(1+self.mask2d)/2.0 self.mask_applied=True def openDarkFile(self): """ Select and imports the dark file """ self.darkFile=QFileDialog.getOpenFileName(self,'Select dark file',directory=self.curDir,filter='Dark file (*.edf)')[0] if self.darkFile!='': self.dark_corrected=False self.darkFileLineEdit.setText(self.darkFile) if self.dataFile is not None: self.data2d=fb.open(self.dataFile).data self.applyDark() def openMaskFile(self,file=None): """ Select and imports the Mask file """ if file is None: self.maskFile=QFileDialog.getOpenFileName(self,'Select mask file',directory=self.curDir,filter='Mask file (*.msk)')[0] else: self.maskFile=file if self.maskFile!='': self.mask_applied=False if os.path.exists(self.maskFile): self.curDir=os.path.dirname(self.maskFile) self.maskFileLineEdit.setText(self.maskFile) self.setup_dict['maskFile']=self.maskFile self.setup_dict['poniFile']=self.poniFile json.dump(self.setup_dict,open('./SetupData/reducer_setup.txt','w')) else: self.openMaskFile(file=None) if self.dataFile is not None: self.applyMask() else: self.maskFile=None self.maskFileLineEdit.clear() def openFolder(self): """ Select the folder to save the reduce data """ oldfolder=self.extractedBaseFolder.text() folder=QFileDialog.getExistingDirectory(self,'Select extracted directory',directory=self.curDir) if folder!='': self.extractedBaseFolder=folder self.extractedBaseFolderLineEdit.setText(folder) self.extractedFolder=os.path.join(folder,self.extractedFolderLineEdit.text()) self.set_externally=True else: self.extractedBaseFolder=oldfolder self.extractedBaseFolderLineEdit.setText(oldfolder) self.extractedFolder = os.path.join(oldfolder, self.extractedFolderLineEdit.text()) self.set_externally = True def extractedFolderChanged(self,txt): self.extractedFolder=os.path.join(self.extractedBaseFolder,txt) self.set_externally=True def reduceData(self): """ Reduces the 2d data to 1d data """ if (self.dataFile is not None) and (os.path.exists(self.dataFile)): if (self.poniFile is not None) and (os.path.exists(self.poniFile)): # self.statusLabel.setText('Busy') # self.progressBar.setRange(0, 0) imageData=fb.open(self.dataFile) #self.data2d=imageData.data #if self.maskFile is not None: # self.applyMask() #self.imageWidget.setImage(self.data2d,transpose=True) #self.tabWidget.setCurrentWidget(self.imageWidget) self.header=imageData.header try: self.ai.set_wavelength(float(self.header['Wavelength'])*1e-10) except: self.ai.set_wavelength(self.wavelength) #print(self.darkFile) if os.path.exists(self.dataFile.split('.')[0]+'_dark.edf') and self.darkCheckBox.isChecked(): self.darkFile=self.dataFile.split('.')[0]+'_dark.edf' dark=fb.open(self.darkFile) self.darkFileLineEdit.setText(self.darkFile) imageDark=dark.data self.header['BSDiode_corr']=max([1.0,(float(imageData.header['BSDiode'])-float(dark.header['BSDiode']))]) self.header['Monitor_corr']=max([1.0,(float(imageData.header['Monitor'])-float(dark.header['Monitor']))]) print("Dark File read from existing dark files") elif self.darkFile is not None and self.darkFile!='' and self.darkCheckBox.isChecked(): dark=fb.open(self.darkFile) imageDark=dark.data self.header['BSDiode_corr']=max([1.0,(float(imageData.header['BSDiode'])-float(dark.header['BSDiode']))]) self.header['Monitor_corr']=max([1.0,(float(imageData.header['Monitor'])-float(dark.header['Monitor']))]) print("Dark File from memory subtracted") else: imageDark=None try: self.header['BSDiode_corr']=float(imageData.header['BSDiode']) self.header['Monitor_corr']=float(imageData.header['Monitor']) self.header['Transmission'] = float(imageData.header['Transmission']) except: self.normComboBox.setCurrentText('None') print("No dark correction done") if str(self.normComboBox.currentText())=='BSDiode': norm_factor=self.header['BSDiode_corr']#/self.header['Monitor_corr']#float(self.header[ # 'count_time']) elif str(self.normComboBox.currentText())=='TransDiode': norm_factor=self.header['Transmission']*self.header['Monitor_corr'] elif str(self.normComboBox.currentText())=='Monitor': norm_factor=self.header['Monitor_corr'] elif str(self.normComboBox.currentText())=='Image Sum': norm_factor=sum(imageData.data) else: norm_factor=1.0 if self.maskFile is not None: imageMask=fb.open(self.maskFile).data else: imageMask=None # QApplication.processEvents() #print(self.azimuthalRange) self.q,self.I,self.Ierr=self.ai.integrate1d(imageData.data,self.npt,error_model='poisson',mask=imageMask,dark=imageDark,unit='q_A^-1',normalization_factor=norm_factor,azimuth_range=self.azimuthalRange,polarization_factor=self.polarization_factor) self.plotWidget.add_data(self.q,self.I,yerr=self.Ierr,name='Reduced data') if not self.set_externally: cakedI,qr,phir=self.ai.integrate2d(imageData.data,self.npt,mask=imageMask,dark=imageDark,unit='q_A^-1',normalization_factor=norm_factor,polarization_factor=self.polarization_factor) self.cakedImageWidget.setImage(cakedI,xmin=qr[0],xmax=qr[-1],ymin=phir[0],ymax=phir[-1],transpose=True,xlabel='Q ', ylabel='phi ',unit=['Å<sup>-1</sup>','degree']) self.cakedImageWidget.imageView.view.setAspectLocked(False) try: self.azimuthalRegion.setRegion(self.azimuthalRange) except: self.azimuthalRegion=pg.LinearRegionItem(values=self.azimuthalRange,orientation=pg.LinearRegionItem.Horizontal,movable=True,bounds=[-180,180]) self.cakedImageWidget.imageView.getView().addItem(self.azimuthalRegion) self.azimuthalRegion.sigRegionChanged.connect(self.azimuthalRegionChanged) self.plotWidget.setTitle(self.dataFile,fontsize=3) # self.progressBar.setRange(0,100) # self.progressBar.setValue(100) # self.statusLabel.setText('Idle') # QApplication.processEvents() self.saveData() #self.tabWidget.setCurrentWidget(self.plotWidget) else: QMessageBox.warning(self,'Calibration File Error','Data reduction failed because either no calibration file provided or the provided file or path do not exists',QMessageBox.Ok) else: QMessageBox.warning(self,'Data File Error','No data file provided', QMessageBox.Ok) def azimuthalRegionChanged(self): minp,maxp=self.azimuthalRegion.getRegion() self.azimuthalRangeLineEdit.setText('%.1f:%.1f'%(minp,maxp)) self.azimuthalRange=[minp,maxp] self.set_externally=True def reduce_multiple(self): """ Reduce multiple files """ try: i=0 self.progressBar.setRange(0,len(self.dataFiles)) self.progressBar.setValue(i) self.statusLabel.setText('<font color="red">Busy</font>') for file in self.dataFiles: self.dataFile=file QApplication.processEvents() self.reduceData() i=i+1 self.progressBar.setValue(i) QApplication.processEvents() self.statusLabel.setText('<font color="green">Idle</font>') self.progressBar.setValue(0) except: QMessageBox.warning(self,'File error','No data files to reduce',QMessageBox.Ok) def saveData(self): """ saves the extracted data into a file """ if not os.path.exists(self.extractedFolder): os.makedirs(self.extractedFolder) filename=os.path.join(self.extractedFolder,os.path.splitext(os.path.basename(self.dataFile))[0]+'.txt') headers='File extracted on '+time.asctime()+'\n' headers='Files used for extraction are:\n' headers+='Data file: '+self.dataFile+'\n' if self.darkFile is not None: headers+='Dark file: '+self.darkFile+'\n' else: headers+='Dark file: None\n' headers+='Poni file: '+self.poniFile+'\n' if self.maskFile is not None: headers+='mask file: '+self.maskFile+'\n' else: headers+='mask file: None\n' for key in self.header.keys(): headers+=key+'='+str(self.header[key])+'\n' headers+="col_names=['Q (inv Angs)','Int','Int_err']\n" headers+='Q (inv Angs)\tInt\tInt_err' data=vstack((self.q,self.I,self.Ierr)).T savetxt(filename,data,header=headers,comments='#')
class HeapTrace(object): def __init__(self, mainWindow): self.mainWindow = mainWindow self.reader = None self.thread = None self.proc = None self.blocks = [] def run(self): self.log = [] self.bits = int(settings.get('new trace', 'BITS')) self.mainWindow.textLog.clear() invocation = PopenAndCall(getcmd(self.bits), shell=False) invocation.finished.connect(self.on_proc_finished) invocation.started.connect(self.on_proc_started) invocation.start(self) def kill(self): if self.thread: self.thread.quit() if self.proc: self.proc.terminate() try: os.kill(self.pin_proc, signal.SIGKILL) except Exception: pass def find_block_by_addr(self, addr): for i, block in enumerate(self.heapView.layoutHeapView.children()): print block if block.base_addr == addr: return i, block return None, None def on_got_heap_op(self, packet): if packet.code == heap_op_type_t.PKT_FREE: i, freed = self.find_block_by_addr(packet.args[0]) if freed == None: print "Hacking is not nice." else: freed.new_packet(packet) else: i, old = self.find_block_by_addr(packet.return_value) if not old: block = Block(packet) self.heapView.push_new_block(block) self.blocks.append(block) else: if old.packet.chunk.size != packet.chunk.size: self.heapView.layoutHeapView.removeWidget(old) else: old.new_packet(packet) self.log.append(packet) self.mainWindow.textLog.append(packet.text_dump() + "\n") def on_proc_started(self): self.mainWindow.status("Process started") self.events = Queue(maxsize=0) self.reader = PinCommunication('localhost', 12345, self.bits, self.events) self.thread = QThread() self.reader.moveToThread(self.thread) self.reader.got_heap_op.connect(self.on_got_heap_op) self.reader.pin_PID.connect(self.on_pin_pid_received) self.thread.started.connect(self.reader.event_loop) self.reader.finished.connect(self.on_reader_finished) self.thread.finished.connect(self.on_thread_finished) self.thread.start() def on_pin_pid_received(self, pid): self.pin_proc = pid self.heapView = HeapWindow(self.mainWindow) self.heapView.show() def on_proc_finished(self): if self.thread: self.thread.quit() self.proc = None self.kill() self.mainWindow.status("Process finished ({} lines)".format(len(self.log))) def on_reader_finished(self): self.kill() self.reader.deleteLater() def on_thread_finished(self): self.thread.deleteLater() self.thread = None
def terminal(self): Worm.stop(self) QThread.quit(self) # Stop the thread self.is_listening = False
class EditorGUI(QWidget): thread_invoker = pyqtSignal() def __init__(self, settings, parent = None, filename=None, params=None): super(EditorGUI, self).__init__(parent) # options #self.skip = 0 logger = logging.getLogger() self.perspective = [0,0,1000,1000] self.angle_degree = 0 self.focus = [333,666,333,666] self.croptop = 0 self.cropbottom = 1000 self.slitpos = 250 if params is not None: logger.debug("EditorGUI params {0}".format(params)) self.angle_degree = params["rotate"] if params["perspective"] is not None: self.perspective = params["perspective"] self.focus = params["focus"] self.slitpos = params["slitpos"] self.croptop, self.cropbottom = params["crop"] #self.skip = params["skip"] #no use filename = params["filename"] #private self.preview_size = 500 self.frame = 0 self.settings = settings #make the threaded loader self.thread = QThread() self.thread.start() self.lastupdatethumbs = 0 #from epoch self.asyncimageloader = AsyncImageLoader(filename=filename, size=self.preview_size) self.asyncimageloader.moveToThread(self.thread) self.thread_invoker.connect(self.asyncimageloader.task) self.thread_invoker.emit() #self.destroyed.connect(self.stop_thread) #does not work self.asyncimageloader.frameIncreased.connect(self.updateTimeLine) #close on quit #http://stackoverflow.com/questions/27420338/how-to-clear-child-window-reference-stored-in-parent-application-when-child-wind #self.setAttribute(Qt.WA_DeleteOnClose) layout = self.make_layout() self.imageselector2 = ImageSelector2() self.imageselector2.slider.startValueChanged.connect(self.frameChanged) self.imageselector2.slider.endValueChanged.connect(self.frameChanged) imageselector_layout = QHBoxLayout() imageselector_layout.addWidget(self.imageselector2) imageselector_gbox = QGroupBox(self.tr('1. Seek the first video frame')) imageselector_gbox.setLayout(imageselector_layout) glayout = QVBoxLayout() glayout.addWidget(imageselector_gbox) glayout.addLayout(layout) self.setLayout(glayout) self.setWindowTitle("Editor") self.show_snapshots() def thumbtransformer(self, cv2image): rotated,warped,cropped = self.transform.process_image(cv2image) h,w = cropped.shape[0:2] thumbh = 100 thumbw = w*thumbh//h thumb = cv2.resize(cropped,(thumbw,thumbh),interpolation = cv2.INTER_CUBIC) return self.cv2toQImage(thumb) def updateTimeLine(self, cv2thumbs): #count time and limit update now = time.time() if now - self.lastupdatethumbs < 0.2: return #transformation filter self.imageselector2.imagebar.setTransformer(self.thumbtransformer) self.imageselector2.setThumbs(cv2thumbs) self.lastupdatethumbs = time.time() def make_layout(self): # layout layout = QHBoxLayout() #second left panel for image rotation rotation_layout = QHBoxLayout() self.btn = QPushButton(self.tr("-90")) self.btn.clicked.connect(self.angle_sub90) rotation_layout.addWidget(self.btn) self.btn = QPushButton(self.tr("-1")) self.btn.clicked.connect(self.angle_dec) rotation_layout.addWidget(self.btn) rotation_layout.addWidget(QLabel(self.tr('rotation'))) self.angle_label = QLabel("0 "+self.tr("degrees")) rotation_layout.addWidget(self.angle_label) self.btn = QPushButton(self.tr("+1")) self.btn.clicked.connect(self.angle_inc) rotation_layout.addWidget(self.btn) self.btn = QPushButton(self.tr("+90")) self.btn.clicked.connect(self.angle_add90) rotation_layout.addWidget(self.btn) # crop_layout = QVBoxLayout() self.crop_slider = rs.QRangeSlider(splitterWidth=10, vertical=True) # スライダの向き self.crop_slider.setFixedWidth(15) self.crop_slider.setStyleSheet(cropCSS) self.crop_slider.setDrawValues(False) self.crop_slider.startValueChanged.connect(self.croptop_slider_on_draw) self.crop_slider.endValueChanged.connect(self.cropbottom_slider_on_draw) self.crop_slider.setMinimumHeight(500) crop_layout.addWidget(self.crop_slider) self.sliderL = rs.QRangeSlider(splitterWidth=10, vertical=True) # スライダの向き self.sliderL.setFixedWidth(15) self.sliderL.setStyleSheet(perspectiveCSS) self.sliderL.setDrawValues(False) self.sliderL.startValueChanged.connect(self.sliderTL_on_draw) self.sliderL.endValueChanged.connect(self.sliderBL_on_draw) self.sliderL.setMinimumHeight(500) self.sliderR = rs.QRangeSlider(splitterWidth=10, vertical=True) # スライダの向き self.sliderR.setFixedWidth(15) self.sliderR.setStyleSheet(perspectiveCSS) self.sliderR.setDrawValues(False) self.sliderR.startValueChanged.connect(self.sliderTR_on_draw) self.sliderR.endValueChanged.connect(self.sliderBR_on_draw) self.sliderR.setMinimumHeight(500) raw_image_layout = QVBoxLayout() self.raw_image_pane = DrawableLabel() self.raw_image_pane.setAlignment(Qt.AlignCenter) self.raw_image_pane.setFixedWidth(self.preview_size) self.raw_image_pane.setFixedHeight(self.preview_size) #raw_image_layout.setAlignment(self.raw_image_pane, Qt.AlignCenter) raw_image_layout.addWidget(self.raw_image_pane) raw_image_layout.setAlignment(self.raw_image_pane, Qt.AlignHCenter) raw_image_layout.setAlignment(self.raw_image_pane, Qt.AlignTop) processed_edit_gbox_layout = QVBoxLayout() processed_edit_gbox = QGroupBox(self.tr('3. Motion Detection and Slit')) box = QVBoxLayout() processed_image_layout = QVBoxLayout() self.processed_pane = MyLabel(func=self.show_snapshots) self.processed_pane.setAlignment(Qt.AlignCenter) self.processed_pane.setFixedWidth(self.preview_size) self.processed_pane.setFixedHeight(self.preview_size) processed_image_layout.addWidget(self.processed_pane) processed_image_layout.setAlignment(self.processed_pane, Qt.AlignTop) hbox = QHBoxLayout() hbox.addLayout(processed_image_layout) hbox.addLayout(crop_layout) box.addLayout(hbox) processed_edit_gbox.setLayout(box) processed_edit_gbox_layout.addWidget(processed_edit_gbox) slit_slider_label = QLabel(self.tr('Slit position')) self.slit_slider = QSlider(Qt.Horizontal) # スライダの向き self.slit_slider.setRange(-500, 500) # スライダの範囲 #スライダの目盛りを両方に出す self.slit_slider.setTickPosition(QSlider.TicksBelow) self.slit_slider.valueChanged.connect(self.slit_slider_on_draw) slit_slider_layout = QHBoxLayout() slit_slider_layout.addWidget(slit_slider_label) slit_slider_layout.addWidget(self.slit_slider) box.addLayout(slit_slider_layout) box.setAlignment(slit_slider_layout, Qt.AlignTop) #combine panels topleft_layout = QHBoxLayout() topleft_layout.addWidget(self.sliderL) topleft_layout.addLayout(raw_image_layout) topleft_layout.addWidget(self.sliderR) left_layout = QVBoxLayout() left_layout.addLayout(topleft_layout) left_layout.addLayout(rotation_layout) left_layout.setAlignment(rotation_layout, Qt.AlignTop) raw_edit_gbox = QGroupBox(self.tr('2. Repair deformation')) raw_edit_gbox.setLayout(left_layout) raw_edit_gbox_layout = QVBoxLayout() raw_edit_gbox_layout.addWidget(raw_edit_gbox) layout.addLayout(raw_edit_gbox_layout) layout.addLayout(processed_edit_gbox_layout) return layout def stop_thread(self): self.asyncimageloader.stop() self.thread.quit() self.thread.wait() def angle_inc(self): self.angle_degree += 1 self.angle_degree %= 360 self.angle_label.setText("{0} ".format(self.angle_degree)+self.tr("degrees")) self.updateTimeLine(self.asyncimageloader.snapshots) self.show_snapshots() def angle_dec(self): self.angle_degree -= 1 self.angle_degree %= 360 self.angle_label.setText("{0} ".format(self.angle_degree)+self.tr("degrees")) self.updateTimeLine(self.asyncimageloader.snapshots) self.show_snapshots() def angle_add90(self): self.angle_degree += 90 self.angle_degree %= 360 self.angle_label.setText("{0} ".format(self.angle_degree)+self.tr("degrees")) self.updateTimeLine(self.asyncimageloader.snapshots) self.show_snapshots() def angle_sub90(self): self.angle_degree -= 90 self.angle_degree %= 360 self.angle_label.setText("{0} ".format(self.angle_degree)+self.tr("degrees")) self.updateTimeLine(self.asyncimageloader.snapshots) self.show_snapshots() def frameChanged(self, value): self.frame = value self.show_snapshots() def sliderTL_on_draw(self): self.perspective[0] = self.sliderL.start() self.updateTimeLine(self.asyncimageloader.snapshots) self.show_snapshots() def sliderBL_on_draw(self): self.perspective[2] = self.sliderL.end() self.updateTimeLine(self.asyncimageloader.snapshots) self.show_snapshots() def sliderTR_on_draw(self): self.perspective[1] = self.sliderR.start() self.updateTimeLine(self.asyncimageloader.snapshots) self.show_snapshots() def sliderBR_on_draw(self): self.perspective[3] = self.sliderR.end() self.updateTimeLine(self.asyncimageloader.snapshots) self.show_snapshots() def cv2toQImage(self,cv2image): height,width = cv2image.shape[:2] return QImage(cv2image[:,:,::-1].copy().data, width, height, width*3, QImage.Format_RGB888) def show_snapshots(self, region=None): """ put the snapshots in the preview panes """ if self.frame < 0: return logger = logging.getLogger() image = self.asyncimageloader.snapshots[self.frame] self.transform = trainscanner.transformation(self.angle_degree, self.perspective, [self.croptop, self.cropbottom]) rotated, warped, cropped = self.transform.process_first_image(image) self.put_cv2_image(rotated, self.raw_image_pane) if region is not None: logger.debug("show_snapshot region {0}".format(region)) #assume the QLabel size is square preview_size x preview_size top, left, bottom, right = region.top(), region.left(), region.bottom(), region.right() if top < 0: top = 0 if left < 0: left = 0 if right > self.preview_size: right = self.preview_size if bottom > self.preview_size: bottom = self.preview_size #and also assume that the cropped image is centered and sometimes shrinked. top -= self.preview_size//2 bottom -= self.preview_size//2 left -= self.preview_size//2 right -= self.preview_size//2 #expected image size in the window height, width = cropped.shape[0:2] if height > width: if height > self.preview_size: width = width * self.preview_size // height height = self.preview_size else: if width > self.preview_size: height = height * self.preview_size // width width = self.preview_size #indicate the region size relative to the image size top = top * 1000 // height + 500 bottom = bottom * 1000 // height + 500 left = left * 1000 // width + 500 right = right * 1000 // width + 500 if top < 0: top = 0 if top > 1000: top = 1000 if bottom < 0: bottom = 0 if bottom > 1000: bottom = 1000 if left < 0: left = 0 if left > 1000: left = 1000 if right < 0: right = 0 if right > 1000: right = 1000 self.focus = left,right,top,bottom self.put_cv2_image(cropped, self.processed_pane) def put_cv2_image(self, image, widget): height, width = image.shape[0:2] qImg = self.cv2toQImage(image) pixmap = QPixmap(qImg) if height > width: if height > self.preview_size: pixmap = pixmap.scaledToHeight(self.preview_size) else: if width > self.preview_size: pixmap = pixmap.scaledToWidth(self.preview_size) widget.setPixmap(pixmap) #give hints to DrawableLabel() and MyLabel() widget.perspective = self.perspective widget.focus = self.focus widget.slitpos = self.slitpos w = pixmap.width() h = pixmap.height() x = ( self.preview_size - w ) // 2 y = ( self.preview_size - h ) // 2 widget.geometry = x,y,w,h def slit_slider_on_draw(self): self.slitpos = self.slit_slider.value() self.show_snapshots() def croptop_slider_on_draw(self): self.croptop = self.crop_slider.start() self.updateTimeLine(self.asyncimageloader.snapshots) self.show_snapshots() def cropbottom_slider_on_draw(self): self.cropbottom = self.crop_slider.end() self.updateTimeLine(self.asyncimageloader.snapshots) self.show_snapshots() def closeEvent(self,event): self.settings.reset_input() self.stop_thread()
class CodeCompletionWidget(QFrame): def __init__(self, neditor): super().__init__(None, Qt.FramelessWindowHint | Qt.ToolTip) self._neditor = neditor # Code completion worker self._cc_worker = QThread() self._cc = code_completion.CodeCompletion() self._cc.moveToThread(self._cc_worker) box = QVBoxLayout(self) box.setContentsMargins(0, 0, 0, 0) self._completion_list = QListWidget() self._completion_list.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) delegate = completion_delegate.CompletionDelegate() # self._completion_list.setItemDelegate(delegate) box.addWidget(self._completion_list) self._icons = { 'class': ':img/class', 'function': ':img/function', 'attr': ':img/attr', 'instance': ':img/instance', 'statement': ':img/statement' } # Key operations self._key_operations = { Qt.Key_Down: self.next_item, Qt.Key_Up: self.previous_item, Qt.Key_Escape: self.hide_completer, Qt.Key_Tab: self.insert_completion, Qt.Key_Return: self.insert_completion } self.__desktop = QApplication.instance().desktop() # Connections self._neditor.post_key_press.connect(self.process_key_event) self._cc_worker.started.connect(self._cc.collect_completions) self._cc.completionsReady.connect(self.__show_completions) def __show_completions(self, completions): self._add_proposals(completions) self.set_geometry() self.show() def _add_proposals(self, proposals): self._completion_list.clear() for proposal in proposals: item = QListWidgetItem() item.setText(proposal['name']) item.setIcon(QIcon(self._icons[proposal['type']])) # item.setData(Qt.DisplayRole, proposal['name']) # item.setData(Qt.UserRole + 1, proposal['desc']) # item.setData(Qt.DecorationRole, self._icons.get(proposal['type'])) self._completion_list.addItem(item) self._completion_list.setCurrentRow(0) def set_geometry(self): cursor_rect = self._neditor.cursorRect() desktop_geo = self.__desktop.availableGeometry(self._neditor) point = self._neditor.mapToGlobal(cursor_rect.topLeft()) cursor_rect.moveTopLeft(point) width = 450 # Calculate the height max_visible_items = 10 visible_items = min(self._completion_list.model().rowCount(), max_visible_items) opt = self._completion_list.viewOptions() model = self._completion_list.model() height = self._completion_list.itemDelegate().sizeHint( opt, model.index(0)).height() height *= visible_items orientation = (point.y() + height) < desktop_geo.height() if orientation: cursor_rect.moveTop(cursor_rect.bottom()) cursor_rect.setWidth(width) cursor_rect.setHeight(height) self.setFixedHeight(height) if not orientation: cursor_rect.moveBottom(cursor_rect.top()) xpos = desktop_geo.width() - (point.x() + width) if xpos < 0: cursor_rect.moveLeft(cursor_rect.left() + xpos) # else: # cursor_rect.moveRight(cursor_rect.right() + 50) self.setGeometry(cursor_rect) def process_pre_key_event(self, event): if not self.isVisible(): return key = event.key() operation = self._key_operations.get(key, lambda: False)() if operation is None: return True return False def process_key_event(self, event): if self.isVisible(): # Collect new proposals based on prefix self.__prefix = self._neditor._text_under_cursor() if self.__prefix is None: return proposals = [] for completion in self._cc.proposals: name = completion['name'] len_prefix = len(self.__prefix) if name[:len_prefix] == self.__prefix: proposals.append(completion) if proposals: self._add_proposals(proposals) self.set_geometry() else: self.hide_completer() key = event.key() if key == Qt.Key_Period: self.complete() def insert_completion(self): to_insert = self._completion_list.currentItem().text() if self.__prefix: to_insert = to_insert[len(self.__prefix):] self._neditor.textCursor().insertText(to_insert) self.hide_completer() def complete(self): self._cc.clean_up() # Get data from editor source = self._neditor.text lineno, offset = self._neditor.cursor_position self.__prefix = self._neditor._text_under_cursor() # Prepare the worker and run thread self._cc.prepare(source, lineno, offset) self._cc_worker.start() def hide_completer(self): self.hide() # Stop thread self.__prefix = '' self._cc_worker.quit() self._cc_worker.wait(10) def previous_item(self): new_row = self._completion_list.currentRow() - 1 if new_row >= 0: self._completion_list.setCurrentRow(new_row) else: self._completion_list.setCurrentRow(self._completion_list.count() - 1) def next_item(self): new_row = self._completion_list.currentRow() + 1 if new_row < self._completion_list.count(): self._completion_list.setCurrentRow(new_row) else: self._completion_list.setCurrentRow(0)
class _POSIXUserscriptRunner(_BaseUserscriptRunner): """Userscript runner to be used on POSIX. Uses _BlockingFIFOReader. The OS must have support for named pipes and select(). Commands are executed immediately when they arrive in the FIFO. Attributes: _reader: The _BlockingFIFOReader instance. _thread: The QThread where reader runs. """ def __init__(self, parent=None): super().__init__(parent) self._reader = None self._thread = None def run(self, cmd, *args, env=None): rundir = utils.get_standard_dir(QStandardPaths.RuntimeLocation) # tempfile.mktemp is deprecated and discouraged, but we use it here to # create a FIFO since the only other alternative would be to create a # directory and place the FIFO there, which sucks. Since os.kfifo will # raise an exception anyways when the path doesn't exist, it shouldn't # be a big issue. self._filepath = tempfile.mktemp(prefix='userscript-', dir=rundir) os.mkfifo(self._filepath) # pylint: disable=no-member self._reader = _BlockingFIFOReader(self._filepath) self._thread = QThread(self) self._reader.moveToThread(self._thread) self._reader.got_line.connect(self.got_cmd) self._thread.started.connect(self._reader.read) self._reader.finished.connect(self.on_reader_finished) self._thread.finished.connect(self.on_thread_finished) self._run_process(cmd, *args, env=env) self._thread.start() def on_proc_finished(self): """Interrupt the reader when the process finished.""" log.procs.debug("proc finished") self._thread.requestInterruption() def on_proc_error(self, error): """Interrupt the reader when the process had an error.""" super().on_proc_error(error) self._thread.requestInterruption() def on_reader_finished(self): """Quit the thread and clean up when the reader finished.""" log.procs.debug("reader finished") self._thread.quit() self._reader.fifo.close() self._reader.deleteLater() super()._cleanup() self.finished.emit() def on_thread_finished(self): """Clean up the QThread object when the thread finished.""" log.procs.debug("thread finished") self._thread.deleteLater()
class Serial(QObject): ''' Simple serial communications class. NOTICE! RXD and TXD are the only pins used. Usage: import sys from PyQt5.QtCore import QCoreApplication from qserial.serial import Serial from qserial.io import IOException application = QCoreApplication(sys.argv) def onRead(data): print("Read {} byte(s): {}".format(len(data), data)) serial = Serial() serial.port = "/dev/ttyUSB0" serial.baudRate = 19200 serial.byteSize = Serial.DATABITS_SEVEN serial.parity = Serial.PARITY_EVEN serial.stopBits = Serial.STOPBITS_ONE serial.onRead = onRead try: serial.open() except IOException as exception: print(exception) serial.close() else: data = b"The quick brown fox jumps over the lazy dog" print("Write {} byte(s):{}".format(len(data), data)) serial.write(data) application.exec_() ''' PARITY_NONE, PARITY_EVEN, PARITY_ODD, PARITY_MARK, PARITY_SPACE = ( IO.PARITY_NONE, IO.PARITY_EVEN, IO.PARITY_ODD, IO.PARITY_MARK, IO.PARITY_SPACE) STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO = ( IO.STOPBITS_ONE, IO.STOPBITS_ONE_POINT_FIVE, IO.STOPBITS_TWO) DATABITS_FIVE, DATABITS_SIX, DATABITS_SEVEN, DATABITS_EIGHT = ( IO.DATABITS_FIVE, IO.DATABITS_SIX, IO.DATABITS_SEVEN, IO.DATABITS_EIGHT) send = pyqtSignal(bytes) quit = pyqtSignal() def __init__(self, parent=None): QObject.__init__(self, parent) self.__io = IO() class Reader(QObject): ''' Synchronous data reader. ''' received = pyqtSignal(bytes) done = pyqtSignal() def __init__(self, io): QObject.__init__(self) self.io = io self.proceedToExit = None pyqtSlot() def read(self): ''' Infinitely wait for incoming data in an infinite loop. All the incoming data is passed outside with the signal. :return data: bytes, incoming data ''' self.proceedToExit = False while not self.proceedToExit: data = self.io.read() QCoreApplication.processEvents( ) # need to process events, and delivered signals if not data: continue self.received.emit(data) self.io.close() self.done.emit() @pyqtSlot() def quit(self): self.proceedToExit = True class Writer(QObject): ''' Synchronous data writer. ''' done = pyqtSignal() def __init__(self, io): QObject.__init__(self) self.io = io self.proceedToExit = False self.dataToWrite = [] @pyqtSlot(bytes) def write(self, data): ''' Write all the characters of <data> one by one. :param data: bytes, outgoing data :return: None ''' self.proceedToExit = False self.dataToWrite.append(data) while len(self.dataToWrite): chunk = self.dataToWrite.pop(0) self.io.write(chunk) if self.proceedToExit: self.done.emit() @pyqtSlot() def quit(self): self.proceedToExit = True self.__on_read = None # on-read callback self.__readingThread = QThread(self) self.__writingThread = QThread(self) self.__reader = Reader(self.__io) self.__writer = Writer(self.__io) self.__reader.moveToThread(self.__readingThread) self.__writer.moveToThread(self.__writingThread) # TOUCH THE READER ONLY WITH SIGNALS !! self.__reader.received.connect(self.__onReadyRead) # self.__reader.done.connect(self.__readingThread.quit) self.__reader.done.connect(self.deleteLater) self.quit.connect(self.__reader.quit) self.__readingThread.started.connect(self.__reader.read) # self.__readingThread.finished.connect(self.deleteLater) #TOUCH THE WRITER ONLY WITH SIGNALS !! # self.__writer.done.connect(self.__writingThread.quit) self.__writer.done.connect(self.deleteLater) self.send.connect(self.__writer.write) self.quit.connect(self.__writer.quit) # self.__writingThread.finished.connect(self.deleteLater) def open(self): self.__io.open() self.__readingThread.start() self.__writingThread.start() def close(self): self.quit.emit() self.__writingThread.wait(2) self.__readingThread.wait(2) self.__writingThread.quit() self.__readingThread.quit() @pyqtSlot(bytes) def __onReadyRead(self, data): if self.__on_read: self.__on_read(data) def write(self, data): self.send.emit(data) @property def port(self): return self.__io.port @port.setter def port(self, port): self.__io.port = port @property def baudRate(self): return self.__io.baudRate @baudRate.setter def baudRate(self, baudRate): self.__io.baudRate = baudRate @property def byteSize(self): return self.__io.byteSize @byteSize.setter def byteSize(self, byteSize): self.__io.byteSize = byteSize @property def parity(self): return self.__io.parity @parity.setter def parity(self, parity): self.__io.parity = parity @property def stopBits(self): return self.__io.stopBits @stopBits.setter def stopBits(self, stopBits): self.__io.stopBits = stopBits @property def onRead(self): return self.__on_read @onRead.setter def onRead(self, callback): self.__on_read = callback @property def isOpen(self): return self.__io.isOpen
class MpvTemplatePyQt(QObject, AbstractTemplate, mpv.Mpv): """Bases: :obj:`QObject <PyQt5.QtCore.QObject>`, :obj:`AbstractTemplate<mpv.templates.AbstractTemplate>`, :obj:`Mpv <mpv.Mpv>`. A Template that can be subclassed for a PyQt5 application. It uses a :obj:`PyQt5.QtCore.QThread` for the event loop. see ``demo/pyqt5.py`` for an example. Args: options (:obj:`dict`, optional): dictionary of options to set with mpv_set_option(). observe (:obj:`list` of :obj:`str`): a list of properties to be observed. log_level (:obj:`mpv.LogLevel`): the log level for mpv to use. log_handler (:obj:`callable`): a function that will be called with the log message as its only argument. parent (:obj:`QObject <PyQt5.QtCore.QObject>`): the Qt parent. **kwargs (optional): options to set with mpv_set_option(). Raises: mpv.ApiVersionError: if the loaded libmpv doesn't meet the minimum requirement. Attributes: shutdown (:obj:`pyqtSignal <PyQt5.QtCore.pyqtSignal>`): Emitted when mpv has finished shutting down after :obj:`quit() <mpv.templates.MpvTemplatePyQt.quit>` has been called. """ _wakeup = pyqtSignal(mpv.Mpv) shutdown = pyqtSignal() def __init__(self, options=None, observe=None, log_level=mpv.LogLevel.INFO, log_handler=None, parent=None, **kwargs): QObject.__init__(self, parent) AbstractTemplate.__init__(self) mpv.Mpv.__init__(self, options=options, **kwargs) if observe is not None: for prop in observe: self.observe_property(prop) if log_handler is not None: self.request_log_messages(log_level) self.log_handler = log_handler self._event_thread = QThread(self) self._event_worker = EventWorker() self._event_worker.moveToThread(self._event_thread) self._event_worker.mpv_event.connect(self._handle_event) self._event_worker.finished.connect(self._event_worker.deleteLater) self._event_thread.finished.connect(self._event_thread.deleteLater) self._wakeup.connect(self._event_worker.wait_event) self.before_initialize() self.initialize() self._event_thread.start() self._wakeup.emit(self) def quit(self): """Make mpv quit. """ if self.handle: self.command('quit') # trigger a SHUTDOWN event. self._event_thread.quit() # end the event thread self._event_thread.wait() self.terminate_destroy() # destroy mpv self.shutdown.emit() @pyqtSlot(int) def seek_absolute(self, ms): """ Args: ms (int): seek to the absolute position in milliseconds. """ if not self.handle: return self.seek(ms / 1000.0, 'absolute+exact') @pyqtSlot(int) def seek_relative(self, ms): """ Args: ms (int): seek relative to the current position in milliseconds. """ if not self.handle: return self.seek(ms / 1000.0, 'relative+exact') @pyqtSlot(float) def set_volume(self, val): """ Args: val (float) [0, 100]: volume percentage as a float. """ if not self.handle: return if val < 0 or val > 100: raise ValueError('Must be in range [0, 100]') self.volume = val
class Ui(QWidget): # 开启的线程数量 signal_thread_number = pyqtSignal(int) signal_draw_model = pyqtSignal(bool) signal_reset_draw_base_data = pyqtSignal(bool) def __init__(self): """ 1.载入UI 1.1 显示内存情况 1.2给工具栏添加日志文本框 2.配置界面基本信息 2.1 导入项目名称 2.2 初始化导入数据 2.3 初始化选择项目 2.4 初始化模式 2.5 绘图数据 2.6 配置进度条初始值 2.7 初始化解压界面 2.8 是否绘图按钮 3.绑定事件 3.1 绑定左击事件 3.1.1 绑定导入数据事件 3.1.2 绑定选择项目事件 3.1.3 绑定运行按钮 3.1.4 绑定解压事件 3.1.5 绑定添加项目事件 3.1.6 绑定修改项目事件 3.1.7 绑定帮助事件 3.1.8 绑定关于事件 3.1.9 绑定列表与tab对应事件 3.1.10 绑定是否绘图事件 3.2绑定右击事件 3.3 退出事件 4.自定义信号反馈 4.1 gearbox区 4.1.1 创建gearbox区的绘图 4.2 generator区 4.1 pitch区 4.2 converter区 4.1 hydraulic区 4.1 sensor区 5.创建线程 5.1 定义新线程 5.2 定义需要创建线程数量的信号 """ super(Ui, self).__init__() # from PyQt5.QtWidgets import QGraphicsView # QGraphicsView.update() # 1 ----------------------------------------载入UI self.window = uic.loadUi("UI/main.ui") # # 1.1 显示内存情况 self.window.memory_label = QLabel(self.window) self.window.memory_label.setStyleSheet("color:blue") self.window.memory_label.setText("") self.window.toolBar.addWidget(self.window.memory_label) self.timer = QTimer() self.refresh_ui() # # >>> 1.2 工具栏 """ 创建log窗口 默认情况不显示 绑定工具栏action """ self.log = log_window.UI() self.show_log = False self.window.Log.triggered.connect(self.open_log) # # <<<工具栏结束 # 2 ----------------------------------------载入界面基础数据开始 self.base_setting = base_setting.AutoSelectTickets(os.getcwd() + r"/config/tickets.json") self.window.select_project_comboBox.addItems(self.base_setting.tickets_data_project_name) # # 2.1 设置默认情况下不选择项目,不选择模式 self.window.select_project_comboBox.setCurrentIndex(-1) self.window.mode_comboBox.setCurrentIndex(-1) # # 2.2 导入数据初始化 self.need_deal_data = None # # 2.3 选择项目 self.select_project_index = None # # 2.4 初始化模式 self.mode = 0 # # 2.5 配置进度条初始值 self.gb_process = 0 self.ge_process = 0 self.pitch_process = 0 self.co_process = 0 self.hy_process = 0 self.se_process = 0 # # 2.7 初始化解压界面 # #2.8 self.is_plot = False self.unpack_ui = seven_zip_window.UI() # # 2.9 定义结束线程变量 self.over = 0 # 3----------------------------------------绑定事件 # # >>>3.1 左击事件 # # # 3.1.1 绑定导入数据事件 self.window.import_data_action.triggered.connect(self.load_data) # # # 3.1.2 绑定选择项目事件,选择模式事件 self.window.select_project_comboBox.currentIndexChanged.connect(self.select_project) self.window.mode_comboBox.currentIndexChanged.connect(self.select_mode) # # # 3.1.3 绑定运行按钮 self.window.run_pushButton.clicked.connect(self.run) # # # >>> 3.1.4 绑定解压 self.window.unpack_action.triggered.connect(self.load_unpack) # # # >>> 3.1.5 绑定添加项目事件 self.window.add_project_action.triggered.connect(self.add_project) # # # >>> 3.1.6 绑定修改项目事件 self.window.refactor_project_action.triggered.connect(self.refactor_project) # # # >>> 3.1.7 绑定帮助事件 self.window.help_action.triggered.connect(self.help) # # # >>> 3.1.8 绑定关于事件 self.window.about_action.triggered.connect(self.about) # # # >>> 3.1.9 绑定左击列表与页列表对应事件 self.window.gb_listWidget.itemClicked.connect(self.select_gb_tabWidget_items) self.window.ge_listWidget.itemClicked.connect(self.select_ge_tabWidget_items) self.window.pitch_listWidget.itemClicked.connect(self.select_pitch_tabWidget_items) self.window.co_listWidget.itemClicked.connect(self.select_co_tabWidget_items) self.window.hy_listWidget.itemClicked.connect(self.select_hy_tabWidget_items) self.window.se_listWidget.itemClicked.connect(self.select_se_tabWidget_items) # # # >>> 3.1.10是否绘图 self.window.plot_comboBox.currentIndexChanged.connect(self.is_draw) self.window.plot_comboBox.setCurrentIndex(-1) # # <<< 左击事件结束 # # 3.2 >>>绑定右击事件 self.window.gb_listWidget.setContextMenuPolicy(3) # 设置菜单 # # 3.3 >>> 绑定退出事件 self.window.closeEvent = self.closeEvent # self.window.gb_listWidget.customContextMenuRequested[QPoint].connect(self.change_gearbox_list_color) # 设置菜单 # 4 ----------------------------------------自定义信号区 # # 模型管理 self.model_manager = model_manager.Manager() # # 4.1 gearbox 区 # # # 4.2.1 gearbox绘图区 self.gb_graphicsViews = None # # # 4.1.2 >>>初始化gearbox线程 """ 1. 初始化空gearbox 2.绑定gearbox模块的信号:修改列表框颜色,更新进度 """ self.gearbox = grGearBox.GearBox1() self.gearbox.signal_gb_progress.connect(self.progress) self.gearbox.signal_gb_color.connect(self.change_gearbox_list_color) self.gearbox.signal_gb_write_log.connect(self.write_log) self.gearbox.signal_gb_over.connect(self.over_threading) # # 4.2 generator 区 # # # 4.2.1 generator绘图区 self.ge_graphicsViews = None # # # 4.2.2 >>>初始化generator线程 self.generator = grGenerator.Generator1() self.generator.signal_ge_progress.connect(self.progress) self.generator.signal_ge_color.connect(self.change_generator_list_color) self.generator.signal_ge_write_log.connect(self.write_log) self.generator.signal_ge_over.connect(self.over_threading) # # 4.3 pitch 区 # # # 4.3.1 pitch绘图区 self.pitch_graphicsViews = None # # # 4.3.2 >>>初始化pitch线程 self.pitch = grPitch.Pitch1() self.pitch.signal_pitch_progress.connect(self.progress) self.pitch.signal_pitch_color.connect(self.change_pitch_list_color) self.pitch.signal_pitch_write_log.connect(self.write_log) self.pitch.signal_pitch_over.connect(self.over_threading) # # 4.4 converter 区 # # # 4.4.1 converter绘图区 self.co_graphicsViews = None # # # 4.4.2 >>>初始化converter线程 self.converter = grConverter.Converter1() self.converter.signal_co_progress.connect(self.progress) self.converter.signal_co_color.connect(self.change_converter_list_color) self.converter.signal_co_write_log.connect(self.write_log) self.converter.signal_co_over.connect(self.over_threading) # # 4.5 hydraulic 区 # # # 4.5.1 hydraulic绘图区 self.hy_graphicsViews = None # # # 4.5.2 >>>初始化hydraulic线程 self.hydraulic = grHydraulic.Hydraulic1() self.hydraulic.signal_hy_progress.connect(self.progress) self.hydraulic.signal_hy_color.connect(self.change_hydraulic_list_color) self.hydraulic.signal_hy_write_log.connect(self.write_log) self.hydraulic.signal_hy_over.connect(self.over_threading) # # 4.6 sensor 区 # # # 4.6.1 sensor绘图区 self.se_graphicsViews = None # # # 4.6.2 >>>初始化sensor线程 self.sensor = grSensor.Sensor1() self.sensor.signal_se_progress.connect(self.progress) self.sensor.signal_se_color.connect(self.change_sensor_list_color) self.sensor.signal_se_write_log.connect(self.write_log) self.sensor.signal_se_over.connect(self.over_threading) self.load_draw_timer = QTimer() # # # <<<初始化结束 # # ------------------------------>>>测试区域 # self.window.run_pushButton.setEnabled(True) # self.window.run_pushButton.setEnabled(True) self.window.statusBar.showMessage("主界面加载完成") # self.load_draw_timer.singleShot(1000, self.load_plot) self.window.statusBar.showMessage("请等待绘图界面加载!") # # ------------------------------>>>测试区域结束 # 5 创建线程 # # 5.1 定义新线程 # *********************************************************************************### # self.gearbox_thread = self.generator_thread = self.pitch_thread = \ # # self.converter_thread = self.hydraulic_thread = self.sensor_thread = QThread() # # 此处不能用这种创建方式,不然创建的是同一个进程!!!只是引用了多次 # # *********************************************************************************### self.gearbox_thread = QThread() self.generator_thread = QThread() self.pitch_thread = QThread() self.converter_thread = QThread() self.hydraulic_thread = QThread() self.sensor_thread = QThread() # # 5.2 定义需要创建线程数量的信号 self.signal_thread_number.connect(self.start_thread) # # 测试区 # self.window.test_pushButton.clicked.connect(self.closeEvent) # self.gb_graphicsViews = list(self.window.gb_tabWidget.findChildren((pg.GraphicsLayoutWidget,))) # self.gb_graphicsViews.reverse() # self.drawing(self.gb_graphicsViews[0], # canvas_setting={"title": "ss"}, # data=[ # {"x": [1, 2, 3, 4, 5], "y": [6, 7, 8, 9, 10], 'pen': 'g', "name": "o"}, # {"x": [1, 2, 3, 4, 5], "y": [2, 4, 9, 16, 25]} # ]) def is_draw(self): self.window.run_pushButton.setEnabled(True) if self.window.plot_comboBox.currentIndex() == 0: self.is_plot = True else: self.is_plot = False def load_plot(self): """ 1.分模块获取绘图对象 2.为绘图对象添加画布 :return: """ self.gb_graphicsViews = list(self.window.gb_tabWidget.findChildren((pg.GraphicsLayoutWidget,))) self.gb_graphicsViews.reverse() self.ge_graphicsViews = list(self.window.ge_tabWidget.findChildren((pg.GraphicsLayoutWidget,))) self.ge_graphicsViews.reverse() self.pitch_graphicsViews = list(self.window.pitch_tabWidget.findChildren((pg.GraphicsLayoutWidget,))) self.pitch_graphicsViews.reverse() self.co_graphicsViews = list(self.window.co_tabWidget.findChildren((pg.GraphicsLayoutWidget,))) self.co_graphicsViews.reverse() self.hy_graphicsViews = list(self.window.hy_tabWidget.findChildren((pg.GraphicsLayoutWidget,))) self.hy_graphicsViews.reverse() self.se_graphicsViews = list(self.window.se_tabWidget.findChildren((pg.GraphicsLayoutWidget,))) self.se_graphicsViews.reverse() self.window.statusBar.showMessage("绘图模块加载完成!!!") # 载入解压界面 def load_unpack(self): self.unpack_ui.window.show() def mouse_moved(self, evt): # gearbox if self.window.tabWidget.currentIndex() == 0: i = self.window.gb_tabWidget.currentIndex() + 1 # 得到当前页的序号+1 用于绑定十字 mousePoint = self.vb.mapSceneToView(evt) print(self.vLine) self.vLine.setPos(mousePoint.x()) self.hLine.setPos(mousePoint.y()) # generator elif self.window.tabWidget.currentIndex() == 1: i = self.window.ge_tabWidget.currentIndex() + 1 # 得到当前页的序号+1 用于绑定十字 mousePoint = self.ge_vb[i].mapSceneToView(evt) self.ge_vLine[i].setPos(mousePoint.x()) self.ge_hLine[i].setPos(mousePoint.y()) # pitch elif self.window.tabWidget.currentIndex() == 2: i = self.window.pitch_tabWidget.currentIndex() + 1 # 得到当前页的序号+1 用于绑定十字 mousePoint = self.pitch_vb[i].mapSceneToView(evt) self.pitch_vLine[i].setPos(mousePoint.x()) self.pitch_hLine[i].setPos(mousePoint.y()) # converter elif self.window.tabWidget.currentIndex() == 3: i = self.window.co_tabWidget.currentIndex() + 1 # 得到当前页的序号+1 用于绑定十字 mousePoint = self.co_vb[i].mapSceneToView(evt) self.co_vLine[i].setPos(mousePoint.x()) self.co_hLine[i].setPos(mousePoint.y()) # hydraulic elif self.window.tabWidget.currentIndex() == 4: i = self.window.hy_tabWidget.currentIndex() + 1 # 得到当前页的序号+1 用于绑定十字 mousePoint = self.hy_vb[i].mapSceneToView(evt) self.hy_vLine[i].setPos(mousePoint.x()) self.hy_hLine[i].setPos(mousePoint.y()) # sensor elif self.window.tabWidget.currentIndex() == 5: i = self.window.se_tabWidget.currentIndex() + 1 # 得到当前页的序号+1 用于绑定十字 mousePoint = self.se_vb[i].mapSceneToView(evt) self.se_vLine[i].setPos(mousePoint.x()) self.se_hLine[i].setPos(mousePoint.y()) def refresh_ui(self): self.timer.start(1000) self.timer.timeout.connect(self.get_memory) def get_memory(self): mem = psutil.virtual_memory() # round方法进行四舍五入,然后转换成字符串 字节/1024得到kb 再/1024得到M total = str(round(mem.total / 1024 / 1024)) used = str(round(mem.used / 1024 / 1024)) use_per = str(round(mem.percent)) free = str(round(mem.free / 1024 / 1024)) process = psutil.Process(os.getpid()) memInfo = process.memory_info() me = str(round(memInfo.rss / 1024 / 1024)) self.window.memory_label.setText( "本机内存:{}M,已使用:{}M({}%),本程序占用:{}M,可使用:{}M)".format(total, used, use_per, me, free)) # ----------------------->>>载入基本数据事件区 def load_data(self): self.window.mode_comboBox.setItemData(0, QVariant(1 | 32), Qt.UserRole - 1) self.window.mode_comboBox.setItemData(1, QVariant(1 | 32), Qt.UserRole - 1) self.window.mode_comboBox.setItemData(2, QVariant(1 | 32), Qt.UserRole - 1) Dialog = QFileDialog() file_names, filetype = Dialog.getOpenFileNames(self.window, "选取文件", # 获得当前路径 os.getcwd(), # 起始路径 "CSV文件 (*.csv);;所有文件 (*)") # 设置文件扩展名过滤,用双分号间隔 if file_names == []: print("\n取消选择") Dialog.deleteLater() sip.delete(Dialog) gc.collect() else: """ 如果选择单个文件则判断是合并的周级数据还是天级数据 周级数据:大文件模式 天级数据:小文件绘图模式 如果选择多个文件 多个文件:多文件模式 """ if len(file_names) == 1: """ 得到文件的大小 单位 kb 1M = 1024 kb 300M = 307200 kb 如果文件小于300M则选择小文件绘图模式 否则大文件模式 """ self.need_deal_data = file_names[0] self.window.select_project_comboBox.setEnabled(True) file_size = os.path.getsize(self.need_deal_data) file_size = float(file_size) / float(1024 * 1024) # M if file_size <= 300: # 设置模式为小文件绘图模式,1,2 选项不可选 self.window.mode_comboBox.setItemData(1, 0, Qt.UserRole - 1) self.window.mode_comboBox.setItemData(2, 0, Qt.UserRole - 1) # 设置模式1 self.mode = 1 self.window.mode_comboBox.setCurrentIndex(0) print("mode1") else: self.need_deal_data = file_names[0] self.window.select_project_comboBox.setEnabled(True) # 设置模式为大文件,0,2 选项不可选 self.window.mode_comboBox.setItemData(0, 0, Qt.UserRole - 1) self.window.mode_comboBox.setItemData(2, 0, Qt.UserRole - 1) # 设置模式2 self.mode = 2 self.window.mode_comboBox.setCurrentIndex(1) print("mode2") else: self.window.select_project_comboBox.setEnabled(True) # 还未设计多文件选择模式 self.need_deal_data = file_names # 设置模式为多文件模式,0,2 选项不可选 self.window.mode_comboBox.setItemData(0, 0, Qt.UserRole - 1) self.window.mode_comboBox.setItemData(1, 0, Qt.UserRole - 1) # 设置模式3 self.mode = 3 print("mode3") # 设置选择下拉框可见 Dialog.deleteLater() sip.delete(Dialog) gc.collect() def select_project(self): # self.window.run_pushButton.setEnabled(True) self.select_project_index = self.window.select_project_comboBox.currentIndex() # 管家 self.model_manager = model_manager.Manager(mode=self.mode, tickets_file_path='config/tickets.json', import_data_path=self.need_deal_data, project_index=self.select_project_index, plot_setting={}) # 开始管家线程 self.model_manager.signal_return_data.connect(self.start_analyse) self.model_manager.signal_manager_over.connect(self.over_threading) self.window.plot_comboBox.setEnabled(True) def select_mode(self): # print(self.mode) # print(self.window.mode_comboBox.currentIndex()) if self.mode == 1: self.load_plot() elif self.mode == 2: self.load_plot() else: pass # 功能区事件 # --------------------->>>绑定列表与tab界面事件区 def select_gb_tabWidget_items(self): QApplication.processEvents() self.window.gb_tabWidget.setCurrentIndex(self.window.gb_listWidget.currentRow()) def select_ge_tabWidget_items(self): self.window.ge_tabWidget.setCurrentIndex(self.window.ge_listWidget.currentRow()) def select_pitch_tabWidget_items(self): self.window.pitch_tabWidget.setCurrentIndex(self.window.pitch_listWidget.currentRow()) def select_co_tabWidget_items(self): self.window.co_tabWidget.setCurrentIndex(self.window.co_listWidget.currentRow()) def select_hy_tabWidget_items(self): self.window.hy_tabWidget.setCurrentIndex(self.window.hy_listWidget.currentRow()) def select_se_tabWidget_items(self): self.window.se_tabWidget.setCurrentIndex(self.window.se_listWidget.currentRow()) # --------------------->>>改变list颜色类事件区 def change_gearbox_list_color(self, li): """ :param li: li[0]: 返回一个int 类型数据 -1:数据标签点丢失,0 预警 1 正常 ; li[1]:序号,需要修改的items :return: """ item = li[1] for i in range(35): if i == item: if li[0] == -1: self.window.gb_listWidget.item(i).setBackground(QColor('gray')) self.window.gb_listWidget.item(i).setCheckState(1) elif li[0] == 0: self.window.gb_listWidget.item(i).setBackground(QColor('red')) self.window.gb_listWidget.item(i).setCheckState(2) else: self.window.gb_listWidget.item(i).setBackground(QColor('green')) self.window.gb_listWidget.item(i).setCheckState(0) else: pass def change_generator_list_color(self, li): """ :param li: li[0]: 返回一个int 类型数据 -1:数据标签点丢失,0 预警 1 正常 ; li[1]:序号,需要修改的items :return: """ item = li[1] for i in range(14): if i == item: if li[0] == -1: self.window.ge_listWidget.item(i).setBackground(QColor('gray')) self.window.ge_listWidget.item(i).setCheckState(1) elif li[0] == 0: self.window.ge_listWidget.item(i).setBackground(QColor('red')) self.window.ge_listWidget.item(i).setCheckState(2) else: self.window.ge_listWidget.item(i).setBackground(QColor('green')) self.window.ge_listWidget.item(i).setCheckState(0) else: pass def change_pitch_list_color(self, li): """ :param li: li[0]: 返回一个int 类型数据 -1:数据标签点丢失,0 预警 1 正常 ; li[1]:序号,需要修改的items :return: """ item = li[1] for i in range(9): if i == item: if li[0] == -1: self.window.pitch_listWidget.item(i).setBackground(QColor('gray')) self.window.pitch_listWidget.item(i).setCheckState(1) elif li[0] == 0: self.window.pitch_listWidget.item(i).setBackground(QColor('red')) self.window.pitch_listWidget.item(i).setCheckState(2) else: self.window.pitch_listWidget.item(i).setBackground(QColor('green')) self.window.pitch_listWidget.item(i).setCheckState(0) else: pass def change_converter_list_color(self, li): """ :param li: li[0]: 返回一个int 类型数据 -1:数据标签点丢失,0 预警 1 正常 ; li[1]:序号,需要修改的items :return: """ item = li[1] for i in range(8): if i == item: if li[0] == -1: self.window.co_listWidget.item(i).setBackground(QColor('gray')) self.window.co_listWidget.item(i).setCheckState(1) elif li[0] == 0: self.window.co_listWidget.item(i).setBackground(QColor('red')) self.window.co_listWidget.item(i).setCheckState(2) else: self.window.co_listWidget.item(i).setBackground(QColor('green')) self.window.co_listWidget.item(i).setCheckState(0) else: pass def change_hydraulic_list_color(self, li): """ :param li: li[0]: 返回一个int 类型数据 -1:数据标签点丢失,0 预警 1 正常 ; li[1]:序号,需要修改的items :return: """ item = li[1] for i in range(12): if i == item: if li[0] == -1: self.window.hy_listWidget.item(i).setBackground(QColor('gray')) self.window.hy_listWidget.item(i).setCheckState(1) elif li[0] == 0: self.window.hy_listWidget.item(i).setBackground(QColor('red')) self.window.hy_listWidget.item(i).setCheckState(2) else: self.window.hy_listWidget.item(i).setBackground(QColor('green')) self.window.hy_listWidget.item(i).setCheckState(0) else: pass def change_sensor_list_color(self, li): """ :param li: li[0]: 返回一个int 类型数据 -1:数据标签点丢失,0 预警 1 正常 ; li[1]:序号,需要修改的items :return: """ item = li[1] for i in range(9): if i == item: if li[0] == -1: self.window.se_listWidget.item(i).setBackground(QColor('gray')) self.window.se_listWidget.item(i).setCheckState(1) elif li[0] == 0: self.window.se_listWidget.item(i).setBackground(QColor('red')) self.window.se_listWidget.item(i).setCheckState(2) else: self.window.se_listWidget.item(i).setBackground(QColor('green')) self.window.se_listWidget.item(i).setCheckState(0) else: pass # --------------------->>>选择list项目类事件区 # -------------------->>>日志区 # 写日志区 def write_log(self, li): """ :param li: li[0]:用于判断部件 li[1]:li li[0]:判断正常或异常 li[1]:日志 :return: """ if li[0] == 0: self.log.window.summary_plainTextEdit.appendHtml("<p><font color='red'>{}</font></p>".format(li[1][1])) elif li[0] == 1: if li[1][0] == 1: self.log.window.gb_plainTextEdit.appendHtml("<p><font color='green'>{}</font></p>".format(li[1][1])) elif li[1][0] == 0: self.log.window.gb_plainTextEdit.appendHtml("<p><font color='red'>{}</font></p>".format(li[1][1])) else: self.log.window.gb_plainTextEdit.appendHtml("<p><font color='gray'>{}</font></p>".format(li[1][1])) elif li[0] == 2: if li[1][0] == 1: self.log.window.ge_plainTextEdit.appendHtml("<p><font color='green'>{}</font></p>".format(li[1][1])) elif li[1][0] == 0: self.log.window.ge_plainTextEdit.appendHtml("<p><font color='red'>{}</font></p>".format(li[1][1])) else: self.log.window.ge_plainTextEdit.appendHtml("<p><font color='gray'>{}</font></p>".format(li[1][1])) elif li[0] == 3: if li[1][0] == 1: self.log.window.pitch_plainTextEdit.appendHtml("<p><font color='green'>{}</font></p>".format(li[1][1])) elif li[1][0] == 0: self.log.window.pitch_plainTextEdit.appendHtml("<p><font color='red'>{}</font></p>".format(li[1][1])) else: self.log.window.pitch_plainTextEdit.appendHtml("<p><font color='gray'>{}</font></p>".format(li[1][1])) elif li[0] == 4: if li[1][0] == 1: self.log.window.co_plainTextEdit.appendHtml("<p><font color='green'>{}</font></p>".format(li[1][1])) elif li[1][0] == 0: self.log.window.co_plainTextEdit.appendHtml("<p><font color='red'>{}</font></p>".format(li[1][1])) else: self.log.window.co_plainTextEdit.appendHtml("<p><font color='gray'>{}</font></p>".format(li[1][1])) elif li[0] == 5: if li[1][0] == 1: self.log.window.hy_plainTextEdit.appendHtml("<p><font color='green'>{}</font></p>".format(li[1][1])) elif li[1][0] == 0: self.log.window.hy_plainTextEdit.appendHtml("<p><font color='red'>{}</font></p>".format(li[1][1])) else: self.log.window.hy_plainTextEdit.appendHtml("<p><font color='gray'>{}</font></p>".format(li[1][1])) elif li[0] == 6: if li[1][0] == 1: self.log.window.se_plainTextEdit.appendHtml("<p><font color='green'>{}</font></p>".format(li[1][1])) elif li[1][0] == 0: self.log.window.se_plainTextEdit.appendHtml("<p><font color='red'>{}</font></p>".format(li[1][1])) else: self.log.window.se_plainTextEdit.appendHtml("<p><font color='gray'>{}</font></p>".format(li[1][1])) def open_log(self): if not self.show_log: self.log.window.show() self.show_log = True else: self.log.window.hide() self.show_log = False # ----------------------->>>运行区 # 分析启动几个线程 def start_analyse(self, signal): """ :param signal: signal 是一个列表 li[0]:何种模式,大文件还是小文件 li[1]:df 还是None, 小文件df 大文件None li[2]:项目序号 li[3]: 开启的线程数量,小文件6个线程 0 表示6个全部开启,大文件1个线程 如果是小文件模式则启动所有的线程,如果是大文件模式则只启动一个线程,等待上一个线程结束再启动下一个线程 :return: """ # 如果模式1 if signal[0] == int(1): self.window.statusBar.showMessage("读取数据成功!正在计算...") # >>> gearbox self.gearbox = grGearBox.GearBox1(tickets_file_path='config/tickets.json', import_data_path=self.need_deal_data, df=signal[1], project_index=signal[2], plt_list=self.gb_graphicsViews, draw=self.is_plot) # >>> generator self.generator = grGenerator.Generator1(tickets_file_path='config/tickets.json', import_data_path=self.need_deal_data, df=signal[1], project_index=signal[2], plt_list=self.ge_graphicsViews, draw=self.is_plot) # >>> pitch self.pitch = grPitch.Pitch1(tickets_file_path='config/tickets.json', import_data_path=self.need_deal_data, df=signal[1], project_index=signal[2], plt_list=self.pitch_graphicsViews, draw=self.is_plot) # >>> converter self.converter = grConverter.Converter1(tickets_file_path='config/tickets.json', import_data_path=self.need_deal_data, df=signal[1], project_index=signal[2], plt_list=self.co_graphicsViews, draw=self.is_plot) # >>> hydraulic self.hydraulic = grHydraulic.Hydraulic1(tickets_file_path='config/tickets.json', import_data_path=self.need_deal_data, df=signal[1], project_index=signal[2], plt_list=self.hy_graphicsViews, draw=self.is_plot) # >>> sensor self.sensor = grSensor.Sensor1(tickets_file_path='config/tickets.json', import_data_path=self.need_deal_data, df=signal[1], project_index=signal[2], plt_list=self.se_graphicsViews, draw=self.is_plot) # ----------------开启线程 elif signal[0] == int(2): self.gearbox = grGearBox.GearBox1(tickets_file_path='config/tickets.json', import_data_path=self.need_deal_data, df=None, project_index=self.select_project_index, plt_list=self.gb_graphicsViews, draw=self.is_plot) self.generator = grGenerator.Generator1(tickets_file_path='config/tickets.json', import_data_path=self.need_deal_data, df=None, project_index=self.select_project_index, plt_list=self.ge_graphicsViews, draw=self.is_plot) self.pitch = grPitch.Pitch1(tickets_file_path='config/tickets.json', import_data_path=self.need_deal_data, df=None, project_index=self.select_project_index, plt_list=self.pitch_graphicsViews, draw=self.is_plot) self.converter = grConverter.Converter1(tickets_file_path='config/tickets.json', import_data_path=self.need_deal_data, df=None, project_index=self.select_project_index, plt_list=self.co_graphicsViews, draw=self.is_plot) self.hydraulic = grHydraulic.Hydraulic1(tickets_file_path='config/tickets.json', import_data_path=self.need_deal_data, df=None, project_index=self.select_project_index, plt_list=self.hy_graphicsViews, draw=self.is_plot) self.sensor = grSensor.Sensor1(tickets_file_path='config/tickets.json', import_data_path=self.need_deal_data, df=None, project_index=self.select_project_index, plt_list=self.se_graphicsViews, draw=self.is_plot) # ----------------------信号 self.gearbox.signal_gb_progress.connect(self.progress) self.gearbox.signal_gb_color.connect(self.change_gearbox_list_color) self.gearbox.signal_gb_write_log.connect(self.write_log) self.gearbox.signal_gb_over.connect(self.over_threading) self.generator.signal_ge_progress.connect(self.progress) self.generator.signal_ge_color.connect(self.change_generator_list_color) self.generator.signal_ge_write_log.connect(self.write_log) self.generator.signal_ge_over.connect(self.over_threading) self.pitch.signal_pitch_progress.connect(self.progress) self.pitch.signal_pitch_color.connect(self.change_pitch_list_color) self.pitch.signal_pitch_write_log.connect(self.write_log) self.pitch.signal_pitch_over.connect(self.over_threading) self.converter.signal_co_progress.connect(self.progress) self.converter.signal_co_color.connect(self.change_converter_list_color) self.converter.signal_co_write_log.connect(self.write_log) self.converter.signal_co_over.connect(self.over_threading) self.hydraulic.signal_hy_progress.connect(self.progress) self.hydraulic.signal_hy_color.connect(self.change_hydraulic_list_color) self.hydraulic.signal_hy_write_log.connect(self.write_log) self.hydraulic.signal_hy_over.connect(self.over_threading) self.sensor.signal_se_progress.connect(self.progress) self.sensor.signal_se_color.connect(self.change_sensor_list_color) self.sensor.signal_se_write_log.connect(self.write_log) self.sensor.signal_se_over.connect(self.over_threading) # ------------------绑定线程 self.gearbox.moveToThread(self.gearbox_thread) self.gearbox_thread.started.connect(self.gearbox.run) self.generator.moveToThread(self.generator_thread) self.generator_thread.started.connect(self.generator.run) self.pitch.moveToThread(self.pitch_thread) self.pitch_thread.started.connect(self.pitch.run) self.converter.moveToThread(self.converter_thread) self.converter_thread.started.connect(self.converter.run) self.hydraulic.moveToThread(self.hydraulic_thread) self.hydraulic_thread.started.connect(self.hydraulic.run) self.sensor.moveToThread(self.sensor_thread) self.sensor_thread.started.connect(self.sensor.run) # 判断需要开启的线程数量 if signal[3] == int(6): self.signal_thread_number.emit(0) elif signal[3] == int(1): self.signal_thread_number.emit(1) def start_thread(self, thread_number): print("thread_number:{}".format(thread_number)) """ :param thread_number: 值为int 类型, thread_number == -1 结束所有线程 thread_number == 0 开启所有线程 thread_number == 1 开启第一个线程 thread_number == 2 退出第一个线程后,开启第二个线程 thread_number == 3 退出上一个线程后,开启下一个线程 ... :return: """ if thread_number == int(0): self.gearbox_thread.start() self.generator_thread.start() self.hydraulic_thread.start() self.sensor_thread.start() self.pitch_thread.start() self.converter_thread.start() for i in range(self.window.gb_tabWidget.count()): self.window.gb_tabWidget.setCurrentIndex(i) self.window.gb_tabWidget.setCurrentIndex(0) for i in range(self.window.ge_tabWidget.count()): self.window.ge_tabWidget.setCurrentIndex(i) self.window.ge_tabWidget.setCurrentIndex(0) for i in range(self.window.pitch_tabWidget.count()): self.window.pitch_tabWidget.setCurrentIndex(i) self.window.pitch_tabWidget.setCurrentIndex(0) for i in range(self.window.co_tabWidget.count()): self.window.co_tabWidget.setCurrentIndex(i) self.window.co_tabWidget.setCurrentIndex(0) for i in range(self.window.hy_tabWidget.count()): self.window.hy_tabWidget.setCurrentIndex(i) self.window.hy_tabWidget.setCurrentIndex(0) for i in range(self.window.se_tabWidget.count()): self.window.se_tabWidget.setCurrentIndex(i) self.window.se_tabWidget.setCurrentIndex(0) elif thread_number == int(1): self.gearbox_thread.start() elif thread_number == int(2): self.generator_thread.start() elif thread_number == int(3): self.pitch_thread.start() elif thread_number == int(4): self.converter_thread.start() elif thread_number == int(5): self.hydraulic_thread.start() elif thread_number == int(6): self.sensor_thread.start() def over_threading(self, li): """ :param li: li[0]:第几个线程 ,li[1]:是否结束 总共7个子线程,用变量over记录结束线程的数量,每结束一个子线程over增加1 :return: """ if self.mode == int(2): if li[0] == int(0): self.model_manager.exec_() self.over += 1 elif li[0] == int(1): print("齿轮箱结束,发电机开始") self.over += 1 # 齿轮箱结束,开启发电机 self.signal_thread_number.emit(2) self.gearbox_thread.quit() elif li[0] == 2: self.over += 1 # 发电机结束,开启变桨 print("发电机结束,开启变桨") self.signal_thread_number.emit(3) self.generator_thread.quit() elif li[0] == 3: self.over += 1 # 变桨结束,开启变频 print("变桨结束,开启变频") self.signal_thread_number.emit(4) self.pitch_thread.quit() elif li[0] == 4: self.over += 1 # 变频结束,开启液压 print("变频结束,开启液压") self.signal_thread_number.emit(5) self.converter_thread.quit() elif li[0] == 5: self.over += 1 # 液压结束,开启传感器 print("液压结束,开启传感器") self.signal_thread_number.emit(6) self.hydraulic_thread.quit() elif li[0] == 6: self.over += 1 self.sensor_thread.quit() if self.over == 7: # 线程结束,开始写日志 self.save_log() self.window.run_file_name_label.setText("None") elif self.mode == int(1): if li[0] == 0: print("manager") # self.model_manager.quit() self.over += 1 elif li[0] == 1: print("gb") # self.gearbox_thread.quit() self.over += 1 elif li[0] == 2: print("ge") # self.generator_thread.quit() self.over += 1 elif li[0] == 3: print("pi") # self.pitch_thread.quit() self.over += 1 elif li[0] == 4: print("co") # self.converter_thread.quit() self.over += 1 elif li[0] == 5: print("hy") # self.hydraulic_thread.quit() self.over += 1 elif li[0] == 6: print("se") # self.sensor_thread.quit() self.over += 1 if self.over == 7: # 线程结束,开始写日志 self.save_log() self.window.run_file_name_label.setText("None") print(self.over) def run(self): print("run") """ :多进程 """ # 选择模式 self.model_manager.start() self.window.statusBar.showMessage("正在读取数据请稍等!") self.window.run_file_name_label.setText(str(self.need_deal_data)) # ----------------------->>> 进度条区 def progress(self, dictionary): if "gearbox" in dictionary.keys(): self.gb_process = dictionary['gearbox'] elif "generator" in dictionary.keys(): self.ge_process = dictionary['generator'] elif "pitch" in dictionary.keys(): self.pitch_process = dictionary['pitch'] elif "converter" in dictionary.keys(): self.co_process = dictionary['converter'] elif "hydraulic" in dictionary.keys(): self.hy_process = dictionary['hydraulic'] elif "sensor" in dictionary.keys(): self.se_process = dictionary['sensor'] process = int(( self.ge_process + self.gb_process + self.pitch_process + self.co_process + self.hy_process + self.se_process) / 6) # process = int(self.gb_process) # print(process) self.window.progressBar.setValue(process) # # 3.3 >>> 退出事件 def closeEvent(self, QCloseEvent): res = QMessageBox.question(self, '消息', '是否清除合并后的数据?', QMessageBox.Yes | QMessageBox.No, QMessageBox.No) # 两个按钮是否, 默认No则关闭这个提示框 if res == QMessageBox.Yes: QCloseEvent.accept() # shutil.rmtree('data') else: QCloseEvent.accept() # QCloseEvent.ignore() # ----------------------->>>测试区 def save_log(self): """ 保存日志 :return: """ import csv try: file_name = r'/log/' + str(self.need_deal_data).split('/')[-1].split('.')[0] except IndexError: file_name = r'/log' try: f = open(os.getcwd() + file_name + r"/日志.txt", mode="w", encoding="gbk") f.write(self.log.window.summary_plainTextEdit.toPlainText()) f.close() lines = self.log.window.summary_plainTextEdit.document().lineCount() f = open(os.getcwd() + file_name + r"/日志.csv", mode="w", encoding="gbk", newline='') csv_writer = csv.writer(f) csv_writer.writerow(['模型', '报警次数']) for i in range(lines): str1 = self.log.window.summary_plainTextEdit.document().findBlockByLineNumber(i).text() str1 = str(str1).split(':')[-1] result = str1.split('报警次数') csv_writer.writerow(result) f.close() f = open(os.getcwd() + file_name + r"/日志.txt", mode="a", encoding="gbk") f.writelines('\r\n') f.write(self.log.window.gb_plainTextEdit.toPlainText()) f.writelines('\r\n') f.write(self.log.window.ge_plainTextEdit.toPlainText()) f.writelines('\r\n') f.write(self.log.window.pitch_plainTextEdit.toPlainText()) f.writelines('\r\n') f.write(self.log.window.co_plainTextEdit.toPlainText()) f.writelines('\r\n') f.write(self.log.window.hy_plainTextEdit.toPlainText()) f.writelines('\r\n') f.write(self.log.window.se_plainTextEdit.toPlainText()) f.close() self.window.statusBar.showMessage("日志写入成功!") except Exception as e: self.window.statusBar.showMessage("日志写入失败!") def add_project(self): text, okPressed = QInputDialog.getText(self, "添加项目", "输入密码:", QLineEdit.Password, "") if okPressed and text == '12345': try: subprocess.call(['notepad.exe', 'config/tickets.json']) except IOError as e: QMessageBox.warning(self, '警告', '文件丢失', QMessageBox.Yes) elif okPressed and text != '12345': QMessageBox.warning(self, '警告', '密码错误!', QMessageBox.Yes) else: pass def refactor_project(self): text, okPressed = QInputDialog.getText(self, "添加项目", "输入密码:", QLineEdit.Password, "") if okPressed and text == '12345': try: subprocess.call(['notepad.exe', 'config/tickets.json']) except IOError as e: QMessageBox.warning(self, '警告', '文件丢失', QMessageBox.Yes) elif okPressed and text != '12345': QMessageBox.warning(self, '警告', '密码错误!', QMessageBox.Yes) else: pass def help(self): pass def about(self): msg = "预警模型1.0版本\n海上工程技术部" QMessageBox.about(self.window, "关于", msg) def test1(self): print(123)
class DebugExporter(QDialog): def __init__(self, core, parent=None): super().__init__(parent=None) self.core = core self.parent = parent self.log_loader = LogLoader(self.core) self.log_loader_thread = QThread() self.log_loader.moveToThread(self.log_loader_thread) self.log_loader.done.connect(self.on_loaded) self.log_loader_thread.started.connect(self.log_loader.load) self.setMinimumSize(800, 600) self.setWindowTitle("{} - Debug Information".format(APP_NAME)) self.plaintextedit = QPlainTextEdit(self) self.plaintextedit.setPlainText("Loading logs; please wait...") self.plaintextedit.setReadOnly(True) font = QFontDatabase.systemFont(QFontDatabase.FixedFont) self.plaintextedit.setFont(font) self.plaintextedit.setStyleSheet( "QPlainTextEdit { background-color: black; color: lime }") self.scrollbar = self.plaintextedit.verticalScrollBar() self.reload_button = QPushButton("Reload") self.reload_button.clicked.connect(self.load) self.checkbox = QCheckBox( "Conceal potentially-identifying information", self) self.checkbox.setCheckState(Qt.Checked) self.checkbox.stateChanged.connect(self.on_checkbox_state_changed) self.filter_info_text = ( "When enabled, {} will filter some information that could be used " "to identify a user or computer. This feature is not perfect, " "however, nor is it a substitute for manually checking logs for " "sensitive information before sharing.".format(APP_NAME)) self.filter_info_button = QPushButton() self.filter_info_button.setToolTip(self.filter_info_text) self.filter_info_button.setFlat(True) self.filter_info_button.setFocusPolicy(Qt.NoFocus) self.filter_info_button.setIcon(QIcon(resource("question"))) self.filter_info_button.setIconSize(QSize(13, 13)) if sys.platform == "darwin": self.filter_info_button.setFixedSize(16, 16) else: self.filter_info_button.setFixedSize(13, 13) self.filter_info_button.clicked.connect( self.on_filter_info_button_clicked) self.copy_button = QPushButton("Copy to clipboard") self.copy_button.clicked.connect(self.copy_to_clipboard) self.export_button = QPushButton("Export to file...") self.export_button.setDefault(True) self.export_button.clicked.connect(self.export_to_file) checkbox_layout = QGridLayout() checkbox_layout.setHorizontalSpacing(0) checkbox_layout.addWidget(self.checkbox, 1, 1) checkbox_layout.addWidget(self.filter_info_button, 1, 2) buttons_layout = QGridLayout() buttons_layout.addWidget(self.reload_button, 1, 1) buttons_layout.addWidget(self.copy_button, 1, 2) buttons_layout.addWidget(self.export_button, 1, 3) bottom_layout = QGridLayout() bottom_layout.addLayout(checkbox_layout, 1, 1) bottom_layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, 0), 1, 2) bottom_layout.addLayout(buttons_layout, 1, 3) layout = QGridLayout(self) layout.addWidget(self.plaintextedit, 1, 1) layout.addLayout(bottom_layout, 2, 1) def on_checkbox_state_changed(self, state): scrollbar_position = self.scrollbar.value() if state == Qt.Checked: self.plaintextedit.setPlainText(self.log_loader.filtered_content) else: self.plaintextedit.setPlainText(self.log_loader.content) # Needed on some platforms to maintain scroll step accuracy/consistency self.scrollbar.setValue(self.scrollbar.maximum()) self.scrollbar.setValue(scrollbar_position) def on_filter_info_button_clicked(self): msgbox = QMessageBox(self) msgbox.setIcon(QMessageBox.Information) if sys.platform == "darwin": msgbox.setText("About Log Filtering") msgbox.setInformativeText(self.filter_info_text) else: msgbox.setWindowTitle("About Log Filtering") msgbox.setText(self.filter_info_text) msgbox.show() def on_loaded(self): self.on_checkbox_state_changed(self.checkbox.checkState()) self.log_loader_thread.quit() self.log_loader_thread.wait() def load(self): if self.log_loader_thread.isRunning(): logging.warning("LogLoader thread is already running; returning") return self.log_loader_thread.start() def copy_to_clipboard(self): for mode in get_clipboard_modes(): set_clipboard_text(self.plaintextedit.toPlainText(), mode) self.close() def export_to_file(self): dest, _ = QFileDialog.getSaveFileName( self, "Select a destination", os.path.join(os.path.expanduser("~"), APP_NAME + " Debug Information.txt"), ) if not dest: return try: with atomic_write(dest, mode="w", overwrite=True) as f: f.write(self.plaintextedit.toPlainText()) except Exception as e: # pylint: disable=broad-except logging.error("%s: %s", type(e).__name__, str(e)) error( self, "Error exporting debug information", str(e), ) return self.close()
class TrayIconUpdates(QSystemTrayIcon): """ Tray Icon to show Updates of new versions """ # Signals closeTrayIcon = pyqtSignal() def __init__(self, parent): super(TrayIconUpdates, self).__init__(parent) icon = QIcon(":img/iconUpdate") self.setIcon(icon) self.setup_menu() self.ide_version = '0' self.download_link = '' notify = parent.ninja_settings().value( 'preferences/general/notifyUpdates', True, type=bool) if notify: self.thread = QThread() self.worker_updates = WorkerUpdates() self.worker_updates.moveToThread(self.thread) self.worker_updates.versionReceived['QString', 'QString'].connect( self._show_messages) self.thread.started.connect(self.worker_updates.check_version) self.worker_updates.finished.connect(self.__on_worker_finished) self.thread.start() def __on_worker_finished(self): self.thread.quit() self.thread.wait() def setup_menu(self, show_downloads=False): self.menu = QMenu() if show_downloads: self.download = QAction(self.tr("Download Version: {}!".format( self.ide_version)), self, triggered=self._show_download) self.menu.addAction(self.download) self.menu.addSeparator() self.quit_action = QAction(self.tr("Close Update Notifications"), self, triggered=self._close) self.menu.addAction(self.quit_action) self.setContextMenu(self.menu) def _show_messages(self, ide_version, download): if not ide_version: return self.ide_version = str(ide_version) self.download_link = str(download) try: local_version = version.LooseVersion(ninja_ide.__version__) web_version = version.LooseVersion(self.ide_version) if local_version < web_version: if self.supportsMessages(): self.setup_menu(True) self.showMessage(self.tr("NINJA-IDE Updates"), self.tr("New Version of NINJA-IDE" "\nAvailable: ") + self.ide_version + self.tr("\n\nCheck the Update Menu in " "the NINJA-IDE " "System Tray icon to Download!"), QSystemTrayIcon.Information, 10000) else: button = QMessageBox.information( self.parent(), self.tr("NINJA-IDE Updates"), self.tr("New Version of NINJA-IDE\nAvailable: ") + self.ide_version) if button == QMessageBox.Ok: self._show_download() else: logger.info("There is no new version") self._close() except Exception as reason: logger.warning('Versions can not be compared: %r', reason) self._close() def _close(self): self.closeTrayIcon.emit() def _show_download(self): webbrowser.open(self.download_link) self._close()
class Ui_Window(QDialog, Ui_Dialog): def __init__(self, parent=None): super(Ui_Window, self).__init__(parent=parent) self.setupUi(self) font = QFont() font.setFamily("微软雅黑") font.setPointSize(9) self.buttonBox.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.buttonBox.button(QDialogButtonBox.Ok).setFont(font) self.buttonBox.button(QDialogButtonBox.Ok).setText("确定") self.buttonBox.button(QDialogButtonBox.Cancel).setFont(font) self.buttonBox.button(QDialogButtonBox.Cancel).setText("取消") vlayout = QVBoxLayout(self) vlayout.addWidget(self.splitter) vlayout.setContentsMargins(0, 0, 10, 10) self.splitter.setGeometry(0, 0, self.width(), self.height()) self.splitter.setOrientation(Qt.Horizontal) self.splitter.setProperty("Stretch", SplitterState.collapsed) self.splitter.setProperty("Dock", Dock.right) self.splitter.setProperty("WidgetToHide", self.txt_log) self.splitter.setProperty("ExpandParentForm", True) self.splitter.setSizes([600, self.splitter.width() - 590]) self.resize(self.splitter.width(), self.splitter.height()) self.paras = {} # 存储参数信息 self.selIndex = QModelIndex() self.table_init() log.setLogViewer(parent=self, logViewer=self.txt_log) self.txt_log.setReadOnly(True) self.btn_addressFile.clicked.connect(self.open_addressFile) self.buttonBox.clicked.connect(self.buttonBox_clicked) self.btn_addRow.clicked.connect(self.btn_addRow_Clicked) self.btn_removeRow.clicked.connect(self.btn_removeBtn_clicked) self.btn_obtainMeta.clicked.connect(self.btn_obtainMeta_clicked) self.btn_saveMetaFile.clicked.connect(self.btn_saveMetaFile_clicked) self.splitter.splitterMoved.connect(self.splitterMoved) self.splitter.handle(1).handleClicked.connect(self.handleClicked) self.tbl_address.clicked.connect(self.table_index_clicked) self.tbl_address.verticalHeader().sectionClicked.connect( self.table_section_clicked) # 最后运算过程放至到另一个线程避免GUI卡住 self.thread = QThread(self) self.crawlVectorThread = crawlVectorWorker() self.crawlVectorThread.moveToThread(self.thread) self.crawlVectorThread.crawl.connect( self.crawlVectorThread.crawlVector) self.crawlVectorThread.crawlBatch.connect( self.crawlVectorThread.crawlVectorBatch) self.crawlVectorThread.finished.connect(self.threadStop) def showEvent(self, a0: QShowEvent) -> None: log.setLogViewer(parent=self, logViewer=self.txt_log) self.table_layout() def resizeEvent(self, a0: QResizeEvent) -> None: self.table_layout() def splitterMoved(self): self.table_layout() def handleClicked(self): self.table_layout() def table_layout(self): self.tbl_address.setColumnWidth(0, self.tbl_address.width() * 0.4) self.tbl_address.setColumnWidth(1, self.tbl_address.width() * 0.2) self.tbl_address.setColumnWidth(2, self.tbl_address.width() * 0.2) self.tbl_address.setColumnWidth(3, self.tbl_address.width() * 0.2) def threadStop(self): self.thread.quit() @Slot(QAbstractButton) def buttonBox_clicked(self, button: QAbstractButton): if button == self.buttonBox.button(QDialogButtonBox.Ok): if not self.check_paras(): return self.thread.start() self.run_process() elif button == self.buttonBox.button(QDialogButtonBox.Cancel): self.close() def run_process(self): rows = range(0, self.tbl_address.model().rowCount(QModelIndex())) for row in rows: url_index, service_index, url, service = self.return_url_and_level( row) # layername_index = self.tbl_address.model().index(row, 2, QModelIndex()) output_index = self.tbl_address.model().index( row, 3, QModelIndex()) # layername = str(self.tbl_address.model().data(layername_index, Qt.DisplayRole)).strip() output = str(self.tbl_address.model().data( output_index, Qt.DisplayRole)).strip() if service != "*": key = url + "_" + str(service) sr = self.paras[key]['spatialReference'] service = self.paras[key]['service'] service_name = self.paras[key]['service_name'] layername = self.paras[key]['new_layername'] res_url = url + "/" + str(service) if self.paras[key]['output'] != "": output = self.paras[key]['output'] if layername == "": layername = self.paras[key]['old_layername'] # crawl_vector(res_url, service_name=service_name, layer_order=service, layer_name=layername, output_path=output, sr=sr) self.crawlVectorThread.crawl.emit(res_url, service_name, str(service), layername, output, sr) else: key_all = url + "_*" if self.paras[key_all]['output'] != "": output = self.paras[key_all]['output'] self.crawlVectorThread.crawlBatch.emit(url, key_all, output, self.paras) def check_paras(self): rows = range(0, self.tbl_address.model().rowCount(QModelIndex())) for row in rows: url_index, service_index, url, service = self.return_url_and_level( row) layername_index = self.tbl_address.model().index( row, 2, QModelIndex()) output_index = self.tbl_address.model().index( row, 3, QModelIndex()) layername = str(self.tbl_address.model().data( layername_index, Qt.DisplayRole)).strip() output = str(self.tbl_address.model().data( output_index, Qt.DisplayRole)).strip() if url == "": log.error('第{}行缺失必要参数"地址",请补全!'.format(row), dialog=True) return False if service == "": log.error('第{}行缺失必要参数"服务号",请补全!'.format(row), dialog=True) return False # in_format = get_suffix(in_path) # if in_format is None: # log.error('第{}行的输入数据格式不支持!目前只支持csv, excel和dbf'.format(row), dialog=True) # return False # else: # in_wks = workspaceFactory().get_factory(in_format) # if in_wks.driver is None: # log.error("缺失图形文件引擎{}!".format(in_wks.driverName)) # return False out_format = get_suffix(output) out_wks = workspaceFactory().get_factory(DataType.fileGDB) if out_wks.driver is None: log.error("缺失图形文件引擎{}!".format(out_wks.driverName)) return False if service != "*": key = url + "_" + str(service) url_lst = url.split(r'/') service_name = url_lst[-2] # filepath, filename = os.path.split(output) if layername == "": layername = self.paras[key]['old_layername'] log.warning('第{}行缺失非必要参数"输出图层名",将使用默认值"{}".'.format( row + 1, layername)) if out_format != DataType.fileGDB: # if os.path.splitext(filename)[1] != '.gdb': gdb_name = service_name + ".gdb" if output == "": if not os.path.exists("res"): os.makedirs("res") output = os.path.join(os.path.abspath("res"), gdb_name) self.paras[key]['output'] = output log.warning('第{}行缺失非必要参数"输出路径",将使用默认值"{}".'.format( row + 1, output)) else: output = os.path.join(output, gdb_name) self.paras[key]['output'] = output log.warning( '第{}行"输出路径"参数缺失输出gdb数据库名,将使用默认值"{}".'.format( row + 1, gdb_name)) else: filepath, filename = os.path.split(output) key_all = url + "_*" # if os.path.splitext(filename)[1] != '.gdb': if out_format != DataType.fileGDB: gdb_name = urlEncodeToFileName(url) + ".gdb" output = os.path.join(output, gdb_name) self.paras[key_all]['output'] = output log.warning('第{}行缺失非必要参数"输出路径",将使用默认值"{}".'.format( row + 1, output)) return True @Slot(int) def table_section_clicked(self, section): self.row_clicked(section) @Slot(QModelIndex) def table_index_clicked(self, index): self.row_clicked(index.row()) @Slot() def btn_addRow_Clicked(self): selModel = self.tbl_address.selectionModel() if selModel is None: return if len(selModel.selectedIndexes()) == 0: self.model.addEmptyRow(self.model.rowCount(QModelIndex()), 1, 0) next_index = self.model.index( self.model.rowCount(QModelIndex()) - 1, 0) elif len(selModel.selectedRows()) == 1: next_row = selModel.selectedRows()[0].row() + 1 self.model.addEmptyRow(next_row, 1, 0) next_index = self.model.index(next_row, 0) selModel.select( next_index, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) self.tbl_address.setFocus() @Slot() def btn_removeBtn_clicked(self): index_list = [] selModel = self.tbl_address.selectionModel() if selModel is None: return if len(selModel.selectedRows()) < 1: return for model_index in selModel.selectedRows(): index = QPersistentModelIndex(model_index) index_list.append(index) oldrow = index.row() for index in index_list: self.model.removeRows(index.row(), 1, 0) if self.model.rowCount(QModelIndex()) == 1: next_index = self.model.index(0, 0) else: next_index = self.model.index(oldrow, 0) # next_index = self.model.index(self.model.rowCount(QModelIndex()) - 1, 0) selModel.select( next_index, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) self.tbl_address.setFocus() @Slot() def open_addressFile(self): fileName, fileType = QFileDialog.getOpenFileName( self, "选择矢量服务参数文件", os.getcwd(), "All Files(*)") self.txt_addressFile.setText(fileName) if fileName == "": return try: with open(fileName, 'r', encoding='utf-8') as f: self.paras = json.load(f) self.add_address_rows_from_paras() except: if self.model.rowCount(QModelIndex()) > 0: self.model.removeRows( self.model.rowCount(QModelIndex()) - 1, 1, QModelIndex()) log.error("读取参数文件失败!", dialog=True) def add_address_rows_from_paras(self): keys = self.paras["exports"] for key in keys: if key in self.paras: row = self.model.rowCount(QModelIndex()) self.model.addEmptyRow(self.model.rowCount(QModelIndex()), 1, 0) url = self.paras[key]['url'] service = self.paras[key]['service'] output = self.paras[key]['output'] layername = self.paras[key]['new_layername'] url_index = self.tbl_address.model().index(row, 0) self.tbl_address.model().setData(url_index, url) service_index = self.tbl_address.model().index(row, 1) self.tbl_address.model().setData(service_index, service) index = self.tbl_address.model().index(row, 2) self.tbl_address.model().setData(index, layername) index = self.tbl_address.model().index(row, 3) self.tbl_address.model().setData(index, output) editor_delegate = self.tbl_address.itemDelegate(service_index) key_all = url + "_*" if key_all in self.paras: services = self.paras[key_all]['services'] if isinstance(editor_delegate, vectorTableDelegate): self.model.setLevelData(key, services) @Slot() def btn_saveMetaFile_clicked(self): datas = self.tbl_address.model().datas bHasData = False for i in datas: for j in i: if j != '': bHasData = True break else: continue break rows = range(0, len(datas)) if bHasData and bool(self.paras): fileName, fileType = QFileDialog.getSaveFileName( self, "请选择保存的参数文件", os.getcwd(), "json file(*.json)") self.paras['exports'] = [] for row in rows: url = datas[row][self.url_no] service = datas[row][self.service_no] if service == "": continue key = str(url) + "_" + str(service) if key in self.paras: self.paras[key]['new_layername'] = datas[row][2] self.paras[key]['output'] = datas[row][3] self.paras['exports'].append(key) try: if fileName != '': with open(fileName, 'w', encoding='UTF-8') as f: json.dump(self.paras, f, ensure_ascii=False) except: log.error("文件存储路径错误,无法保存!", parent=self, dialog=True) def table_init(self): self.tbl_address.setStyle(mTableStyle()) self.tbl_address.horizontalHeader().setStretchLastSection(True) self.tbl_address.verticalHeader().setDefaultSectionSize(20) self.tbl_address.verticalHeader().setSectionResizeMode( QHeaderView.Fixed) # 行高固定 color = self.palette().color(QPalette.Button) self.tbl_address.horizontalHeader().setStyleSheet( "QHeaderView::section {{ background-color: {}}}".format( color.name())) self.tbl_address.verticalHeader().setStyleSheet( "QHeaderView::section {{ background-color: {}}}".format( color.name())) self.tbl_address.setStyleSheet( "QTableCornerButton::section {{ color: {}; border: 1px solid; border-color: {}}}" .format(color.name(), color.name())) self.tbl_address.setSelectionMode(QAbstractItemView.ExtendedSelection) self.tbl_address.setEditTriggers(QAbstractItemView.SelectedClicked | QAbstractItemView.DoubleClicked) self.tbl_address.DragDropMode(QAbstractItemView.InternalMove) self.tbl_address.setSelectionBehavior(QAbstractItemView.SelectRows | QAbstractItemView.SelectItems) self.tbl_address.setDefaultDropAction(Qt.MoveAction) self.tbl_address.horizontalHeader().setSectionsMovable(False) self.tbl_address.setDragEnabled(True) self.tbl_address.setAcceptDrops(True) self.service_no = 1 # 服务名字段的序号 self.url_no = 0 # url地址的序号 self.model = TableModel() self.model.setHeaderData(0, Qt.Horizontal, "地址", Qt.DisplayRole) self.model.setHeaderData(1, Qt.Horizontal, "服务号", Qt.DisplayRole) self.model.setHeaderData(2, Qt.Horizontal, "输出图层名", Qt.DisplayRole) self.model.setHeaderData(3, Qt.Horizontal, "输出路径", Qt.DisplayRole) delegate = vectorTableDelegate( self, [None, { 'type': 'c' }, None, { 'text': "请选择或创建文件数据库", 'type': "d" }]) self.tbl_address.setModel(self.model) self.tbl_address.setItemDelegate(delegate) @Slot() def btn_obtainMeta_clicked(self): selModel = self.tbl_address.selectionModel() if selModel is None: return indexes = selModel.selectedIndexes() ## 如果有被选中的行,则只获取被选中行的信息 if len(indexes) > 0: rows = sorted( set(index.row() for index in self.tbl_address.selectedIndexes())) else: rows = range(0, self.tbl_address.model().rowCount(QModelIndex())) for row in rows: url_index, service_index, url, service = self.return_url_and_level( row) editor_delegate = self.tbl_address.itemDelegate(service_index) if url == "": continue getInfo = get_paraInfo(url) key = url + "_*" if getInfo is None: log.error(url + "无法获取远程参数信息,请检查地址是否正确以及网络是否连通!") continue else: log.info(url + "参数信息获取成功!") self.setAllParaToMemory(url, getInfo) services = self.paras[key]['services'] if isinstance(editor_delegate, vectorTableDelegate): self.model.setLevelData(key, services) if service != "*" and service != "": layername_index = self.tbl_address.model().index(row, 2) if url + "_" + str(service) in self.paras: layername = self.paras[url + "_" + str(service)]['old_layername'] self.tbl_address.model().setData(layername_index, layername) def setAllParaToMemory(self, url, getInfo): xmin = xmax = ymin = ymax = sp = "" services = ["*"] if 'layers' in getInfo.keys(): layers = getInfo['layers'] for layer in layers: if 'subLayerIds' in layer: if layer['subLayerIds'] is None: if 'id' in layer: service = layer['id'] services.append(service) new_url = url + "/" + str(service) layer_getInfo = get_paraInfo(new_url) self.setParaToMemory(url, str(service), layer_getInfo) if 'fullExtent' in getInfo.keys(): if 'xmin' in getInfo['fullExtent']: xmin = getInfo['fullExtent']['xmin'] if 'xmax' in getInfo['fullExtent']: xmax = getInfo['fullExtent']['xmax'] if 'ymin' in getInfo['fullExtent']: ymin = getInfo['fullExtent']['ymin'] if 'ymax' in getInfo['fullExtent']: ymax = getInfo['fullExtent']['ymax'] if 'spatialReference' in getInfo.keys(): if 'wkid' in getInfo['spatialReference']: sp = getInfo['spatialReference']['wkid'] url_encodeStr = urlEncodeToFileName(url) self.paras[url + "_*"] = { 'url': url, 'service': "*", 'code': url_encodeStr, 'services': services, 'xmin': xmin, 'xmax': xmax, 'ymin': ymin, 'ymax': ymax, 'spatialReference': sp, "output": "" } def setParaToMemory(self, url, service, getInfo): xmin = xmax = ymin = ymax = sp = layername = "" key = url + "_" + service if 'id' in getInfo.keys(): service = getInfo['id'] if 'name' in getInfo.keys(): layername = getInfo['name'] if service == "": return if 'extent' in getInfo.keys(): if 'xmin' in getInfo['extent']: xmin = getInfo['extent']['xmin'] if 'xmax' in getInfo['extent']: xmax = getInfo['extent']['xmax'] if 'ymin' in getInfo['extent']: ymin = getInfo['extent']['ymin'] if 'ymax' in getInfo['extent']: ymax = getInfo['extent']['ymax'] if 'spatialReference' in getInfo['extent']: if 'wkid' in getInfo['extent']['spatialReference']: sp = getInfo['extent']['spatialReference']['wkid'] url_encodeStr = urlEncodeToFileName(url) url_lst = url.split(r'/') service_name = url_lst[-3] self.paras[key] = { 'url': url, 'service': service, 'service_name': service_name, 'code': url_encodeStr, 'old_layername': layername, 'xmin': xmin, 'xmax': xmax, 'ymin': ymin, 'ymax': ymax, 'spatialReference': sp, 'new_layername': "", 'output': "" } def row_clicked(self, logicRow): sel_index = self.tbl_address.model().index(logicRow, 0, QModelIndex()) self.update_txt_info(sel_index) self.selIndex = sel_index def return_url_and_level(self, logicRow): url_index = self.tbl_address.model().index(logicRow, self.url_no) level_index = self.tbl_address.model().index(logicRow, self.service_no) url = self.tbl_address.model().data(url_index, Qt.DisplayRole) level = self.tbl_address.model().data(level_index, Qt.DisplayRole) return url_index, level_index, url, level def update_txt_info(self, index: QModelIndex, level=-1): key1_index = self.tbl_address.model().index(index.row(), 0) key1 = self.tbl_address.model().data(key1_index, Qt.DisplayRole) key2_index = self.tbl_address.model().index(index.row(), 1) key2 = self.tbl_address.model().data(key2_index, Qt.DisplayRole) self.txt_xmin.setText("") self.txt_xmax.setText("") self.txt_ymin.setText("") self.txt_ymax.setText("") self.txt_spatialReference.setText("") self.lbl_layer.setText("") if level == -1: key = key1 + "_" + str(key2) else: key = key1 + "_" + str(level) if key not in self.paras: return getInfo = self.paras[key] if 'old_layername' in getInfo: self.lbl_layer.setText(str(getInfo['old_layername'])) layername_index = self.tbl_address.model().index(index.row(), 2) if key1 + "_" + str(level) in self.paras: layername = self.paras[key1 + "_" + str(level)]['old_layername'] self.tbl_address.model().setData(key2_index, level) self.tbl_address.model().setData(layername_index, layername) if 'xmin' in getInfo: self.txt_xmin.setText(str(getInfo['xmin'])) self.txt_xmin.home(False) if 'xmax' in getInfo: self.txt_xmax.setText(str(getInfo['xmax'])) self.txt_xmax.home(False) if 'ymin' in getInfo: self.txt_ymin.setText(str(getInfo['ymin'])) self.txt_ymin.home(False) if 'ymax' in getInfo: self.txt_ymax.setText(str(getInfo['ymax'])) self.txt_ymax.home(False) if 'spatialReference' in getInfo: self.txt_spatialReference.setText(str(getInfo['spatialReference'])) self.txt_spatialReference.home(False)
class Window(QMainWindow): # 播放控制信号 pause = pyqtSignal() resume = pyqtSignal() stop = pyqtSignal() # 传递片段到已移动到子线程的视频、音频对象的信号 set_video = pyqtSignal(object) set_audio = pyqtSignal(object, int, int, int) # 界面设定 def setupUi(self): self.setWindowTitle("VideoGenerator") self.centralwidget = QWidget(self) self.gridLayout = QtWidgets.QGridLayout(self.centralwidget) self.horizontalLayout_2 = QtWidgets.QHBoxLayout() self.horizontalSlider = QtWidgets.QSlider(self.centralwidget) self.horizontalSlider.setOrientation(Qt.Horizontal) self.horizontalSlider.setRange(0, 0) self.horizontalLayout_2.addWidget(self.horizontalSlider) self.label = QtWidgets.QLabel(self.centralwidget) self.label.setText("") self.horizontalLayout_2.addWidget(self.label) self.horizontalLayout_3 = QtWidgets.QHBoxLayout() sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) self.label_target = QtWidgets.QLabel(self.centralwidget) self.label_target.setText("目标") self.label_target.setSizePolicy(sizePolicy) self.horizontalLayout_3.addWidget(self.label_target) self.gridLayout.addLayout(self.horizontalLayout_3, 3, 0, 1, 1) self.horizontalLayout_4 = QtWidgets.QHBoxLayout() self.label_background = QtWidgets.QLabel(self.centralwidget) self.label_background.setSizePolicy(sizePolicy) self.label_background.setText("背景") self.horizontalLayout_4.addWidget(self.label_background) self.gridLayout.addLayout(self.horizontalLayout_4, 4, 0, 1, 1) self.horizontalLayout_5 = QtWidgets.QHBoxLayout() self.label_audio = QtWidgets.QLabel(self.centralwidget) self.label_audio.setSizePolicy(sizePolicy) self.label_audio.setText("音频") self.horizontalLayout_5.addWidget(self.label_audio) self.gridLayout.addLayout(self.horizontalLayout_5, 5, 0, 1, 1) self.gridLayout.addLayout(self.horizontalLayout_2, 2, 0, 1, 1) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) self.pushButton_7 = QtWidgets.QPushButton(self.centralwidget) self.horizontalLayout.addWidget(self.pushButton_7) self.pushButton_6 = QtWidgets.QPushButton(self.centralwidget) self.horizontalLayout.addWidget(self.pushButton_6) self.pushButton = QtWidgets.QPushButton(self.centralwidget) self.horizontalLayout.addWidget(self.pushButton) self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget) self.pushButton_2.setText("") self.horizontalLayout.addWidget(self.pushButton_2) self.pushButton_4 = QtWidgets.QPushButton(self.centralwidget) self.horizontalLayout.addWidget(self.pushButton_4) self.pushButton_8 = QtWidgets.QPushButton(self.centralwidget) self.horizontalLayout.addWidget(self.pushButton_8) self.pushButton_10 = QtWidgets.QPushButton(self.centralwidget) self.horizontalLayout.addWidget(self.pushButton_10) self.pushButton_3 = QtWidgets.QPushButton(self.centralwidget) self.pushButton_2.setIcon(self.style().standardIcon( QStyle.SP_MediaPlay)) self.pushButton_6.setText("打开背景") self.pushButton_7.setText("打开目标") self.pushButton.setText("打开音频") self.pushButton_4.setText("设置切分起始时间") self.pushButton_8.setText("原地切分") self.pushButton_10.setText("重复") self.pushButton_3.setText("生成") self.pushButton_3.setEnabled(False) self.pushButton_2.setEnabled(False) self.pushButton_4.setEnabled(False) self.pushButton_10.setEnabled(False) self.pushButton_8.setEnabled(False) self.openGLWidget = MyOpenGLWidget(self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.openGLWidget.sizePolicy().hasHeightForWidth()) self.openGLWidget.setSizePolicy(sizePolicy) self.gridLayout.addWidget(self.openGLWidget, 0, 0, 1, 1) self.horizontalLayout.addWidget(self.pushButton_3) self.gridLayout.addLayout(self.horizontalLayout, 1, 0, 1, 1) self.listWidget_3 = QtWidgets.QListWidget(self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.listWidget_3.sizePolicy().hasHeightForWidth()) self.listWidget_3.setSizePolicy(sizePolicy) self.listWidget_3.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.listWidget_3.setDragEnabled(True) self.listWidget_3.setDragDropMode( QtWidgets.QAbstractItemView.InternalMove) self.listWidget_3.setDefaultDropAction(Qt.MoveAction) self.listWidget_3.setFlow(QtWidgets.QListView.LeftToRight) self.listWidget_3.setContextMenuPolicy(Qt.CustomContextMenu) self.listWidget_3.setFixedHeight(listHeight) self.listWidget_3.setHorizontalScrollMode(QListWidget.ScrollPerPixel) self.horizontalLayout_5.addWidget(self.listWidget_3) self.listWidget = QtWidgets.QListWidget(self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.listWidget.sizePolicy().hasHeightForWidth()) self.listWidget.setSizePolicy(sizePolicy) self.listWidget.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.listWidget.setDragEnabled(True) self.listWidget.setDragDropMode( QtWidgets.QAbstractItemView.InternalMove) self.listWidget.setDefaultDropAction(Qt.MoveAction) self.listWidget.setFlow(QtWidgets.QListView.LeftToRight) self.listWidget.setLayoutMode(QtWidgets.QListView.SinglePass) self.listWidget.setFixedHeight(listHeight) self.listWidget.setContextMenuPolicy(Qt.CustomContextMenu) self.listWidget.setHorizontalScrollMode(QListWidget.ScrollPerPixel) self.horizontalLayout_3.addWidget(self.listWidget) self.listWidget_2 = QtWidgets.QListWidget(self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.listWidget_2.sizePolicy().hasHeightForWidth()) self.listWidget_2.setSizePolicy(sizePolicy) self.listWidget_2.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.listWidget_2.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.listWidget_2.setDragEnabled(True) self.listWidget_2.setDragDropMode( QtWidgets.QAbstractItemView.InternalMove) self.listWidget_2.setDefaultDropAction(Qt.MoveAction) self.listWidget_2.setFlow(QtWidgets.QListView.LeftToRight) self.listWidget_2.setFixedHeight(listHeight) self.listWidget_2.setContextMenuPolicy(Qt.CustomContextMenu) self.listWidget_2.setHorizontalScrollMode(QListWidget.ScrollPerPixel) self.horizontalLayout_4.addWidget(self.listWidget_2) self.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(self) self.menubar.setGeometry(QRect(0, 0, 905, 26)) self.setMenuBar(self.menubar) self.statusbar = QStatusBar(self) self.progressBar = QProgressBar() self.progressBar.setVisible(False) self.progressBar.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.progressBar.setFixedHeight(20) self.statusbar.addPermanentWidget(self.progressBar) self.setStatusBar(self.statusbar) # 保存该片段到本地 def saveItem(self): self.returnIDLE() item = self.cur_listWidget.currentItem() self.saveitem = item if item.video: self.saveObject = SaveTemp(item.video.copy()) else: self.saveObject = SaveTemp(item.audio.copy()) item.setFlags(item.flags() & ((Qt.ItemIsSelectable | Qt.ItemIsEnabled) ^ 0xff)) self.saveThread = QThread() self.saveObject.moveToThread(self.saveThread) self.saveObject.finish_process.connect(self.finishSave) self.saveObject.message.connect(self.thread_message) self.saveObject.progress.connect(self.thread_progress) self.saveThread.started.connect(self.saveObject.process) self.progressBar.setVisible(True) self.saveThread.start() def setupData(self): # 记录当前播放视频、音频 self.audio_backend = None self.video_backend = None # 切分起始结束时刻 self.cutter_end = 0 self.cutter_start = 0 # 当前选中的行 self.cur_listWidget = None # 播放、处理音视频的子线程及相应对象 self.video = None self.audio = None self.calthread = None self.audioThread = None self.compositeObject = None self.compositeThread = None # 状态变量 self.flag = 0 self.state = State.IDLE self.saveObject = None self.saveThread = None # 要处理的所有片段 self.items, self.items_2, self.items_3 = [], [], [] self.saveitem = None # 各信号与触发的相应函数绑定 def setupSlot(self): self.pushButton.clicked.connect(self.openAudio) self.pushButton_7.clicked.connect(self.openTarget) self.pushButton_6.clicked.connect(self.openBackground) self.pushButton_8.clicked.connect(self.cut) self.pushButton_10.clicked.connect(self.copySelected) self.pushButton_3.clicked.connect(self.setupPara) self.pushButton_2.clicked.connect(self.play) self.pushButton_4.clicked.connect(self.setStart) self.horizontalSlider.sliderMoved.connect(self.setPosition) self.horizontalSlider.sliderMoved.connect(self.setApos) self.horizontalSlider.sliderReleased.connect(self.openGLWidget.update) self.listWidget.itemDoubleClicked.connect(self.setupTarget) self.listWidget_2.itemDoubleClicked.connect(self.setupTarget) self.listWidget_3.itemDoubleClicked.connect(self.setupTarget) self.listWidget.customContextMenuRequested.connect( self.createContextMenu) self.listWidget_2.customContextMenuRequested.connect( self.createContextMenu2) self.listWidget_3.customContextMenuRequested.connect( self.createContextMenu3) def __init__(self): super(Window, self).__init__() self.setupData() self.setupUi() self.setupSlot() # 设置切分起始时间并显示 def setStart(self): self.cutter_start = self.video.t duration = self.video_backend.duration if self.video_backend else self.audio_backend.duration currentTime = QTime( int(self.cutter_start / 3600) % 60, int(self.cutter_start / 60) % 60, self.cutter_start % 60, (self.cutter_start * 1000) % 1000) format = 'hh:mm:ss' if duration > 3600 else 'mm:ss' tStr = currentTime.toString(format) self.statusbar.showMessage("设置起始时间%s" % tStr, 1000) # 关闭窗口前清理子线程 def closeEvent(self, a0: QtGui.QCloseEvent) -> None: self.stop.emit() if self.calthread: self.calthread.quit() self.calthread.wait() if self.audioThread: self.audioThread.quit() self.audioThread.wait() super().closeEvent(a0) # 重复添加选中的item def copySelected(self): self.cur_listWidget.addItem(self.cur_listWidget.currentItem().copy()) # 在对话框里设置处理参数 def setupPara(self): def choose(): if self.gauss.isChecked(): self.gauss_target.setEnabled(True) self.gauss_background.setEnabled(True) else: self.gauss_target.setEnabled(False) self.gauss_background.setEnabled(False) # 至少选择了目标和背景才能在对话框里设置处理参数 if self.listWidget.count() and self.listWidget_2.count(): self.dialog = QtWidgets.QDialog(self) self.dialog.setAttribute(Qt.WA_DeleteOnClose) self.dialog.setWindowModality(Qt.WindowModal) self.dialog.setWindowTitle("准备合成") gridLayout = QtWidgets.QGridLayout(self.dialog) self.dialog.setLayout(gridLayout) self.check = QtWidgets.QCheckBox("全局分身效果", self.dialog) self.gauss = QtWidgets.QCheckBox("高斯模糊", self.dialog) self.gauss_target = QtWidgets.QCheckBox("目标", self.dialog) self.gauss_background = QtWidgets.QCheckBox("背景", self.dialog) self.tiktok = QtWidgets.QCheckBox("抖音效果", self.dialog) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) self.gauss_target.setSizePolicy(sizePolicy) self.gauss_background.setSizePolicy(sizePolicy) self.check.setSizePolicy(sizePolicy) self.gauss.setSizePolicy(sizePolicy) self.tiktok.setSizePolicy(sizePolicy) self.gauss.stateChanged.connect(choose) self.gauss_target.setEnabled(False) self.gauss_background.setEnabled(False) dialogbtns = QDialogButtonBox(QDialogButtonBox.Cancel | QDialogButtonBox.Ok) dialogbtns.button(QDialogButtonBox.Cancel).setText("取消") dialogbtns.button(QDialogButtonBox.Ok).setText("开始") dialogbtns.rejected.connect(self.dialog.reject) dialogbtns.accepted.connect(self.dialog.accept) dialogbtns.accepted.connect(self.composite) dialogbtns.setSizePolicy(sizePolicy) label = QtWidgets.QLabel(self.dialog) label.setText("默认分辨率 1280x720") gridLayout.addWidget(self.check, 0, 0, 1, 1) gridLayout.addWidget(dialogbtns, 4, 0, 1, 3) gridLayout.addWidget(label, 3, 0, 1, 1) gridLayout.addWidget(self.gauss, 1, 0, 1, 1) gridLayout.addWidget(self.tiktok, 2, 0, 1, 1) gridLayout.addWidget(self.gauss_target, 1, 1, 1, 1) gridLayout.addWidget(self.gauss_background, 1, 2, 1, 1) self.dialog.resize(400, 300) self.dialog.show() def finishSave(self): self.progressBar.setVisible(False) self.saveitem.setFlags(self.saveitem.flags() | Qt.ItemIsEnabled | Qt.ItemIsSelectable) self.saveThread.quit() self.saveThread.wait() # 若当前进度大于切分起始时间,则对当前播放片段原地切分 def cut(self): if self.video.t <= self.cutter_start: QMessageBox.critical(self, "警告", "请在起始时间之后切分") elif self.video.t > self.cutter_start: if self.video_backend and self.audio_backend: self.video_backend = self.video_backend.subclip( self.cutter_start, self.video.t) self.audio_backend = self.video_backend.audio self.set_video.emit(self.video_backend) self.set_audio.emit( self.audio_backend, self.audio_backend.fps, int(1.0 / self.video_backend.fps * self.audio_backend.fps), 2) self.cur_listWidget.currentItem().setClip( self.video_backend, self.audio_backend) self.durationChanged(self.video_backend.duration) elif self.video_backend and not self.audio_backend: self.video_backend = self.video_backend.subclip( self.cutter_start, self.video.t) self.set_video.emit(self.video_backend) self.cur_listWidget.currentItem().setClip( self.video_backend, self.audio_backend) self.durationChanged(self.video_backend.duration) elif not self.video_backend and self.audio_backend: self.audio_backend = self.audio_backend.subclip( self.cutter_start, self.video.t) self.set_video.emit(self.video_backend) self.set_audio.emit( self.audio_backend, self.audio_backend.fps, int(1.0 / self.video.fps * self.audio_backend.fps), 2) self.cur_listWidget.currentItem().setClip( self.video_backend, self.audio_backend) self.durationChanged(self.audio_backend.duration) self.cutter_start = 0 # 准备生成 def composite(self): # 为防止读写冲突,保护所有待处理片段,先重置回初始状态 self.returnIDLE() targets, backgrounds, audios = [], [], [] # 将所有待处理片段的item置为不可选中且不可用,并存储这些片段准备让子线程处理 for i in range(self.listWidget.count()): item = self.listWidget.item(i) targets.append(item.video) self.items.append(item) item.setFlags(item.flags() & ((Qt.ItemIsSelectable | Qt.ItemIsEnabled) ^ 0xff)) for i in range(self.listWidget_2.count()): item_2 = self.listWidget_2.item(i) backgrounds.append(item_2.video) self.items_2.append(item_2) item_2.setFlags(item.flags() & ( (Qt.ItemIsSelectable | Qt.ItemIsEnabled) ^ 0xff)) for i in range(self.listWidget_3.count()): item_3 = self.listWidget_3.item(i) audios.append(item_3.audio) self.items_3.append(item_3) item_3.setFlags(item.flags() & ( (Qt.ItemIsSelectable | Qt.ItemIsEnabled) ^ 0xff)) # 传递待处理片段及相关参数,绑定开始、结束、处理进度的函数 self.compositeObject = CompositeObject( targets, backgrounds, audios, triple=self.check.isChecked(), gauss=self.gauss.isChecked(), tiktok=self.tiktok.isChecked(), gauss_target=self.gauss_target.isChecked(), gauss_background=self.gauss_background.isChecked()) self.compositeThread = QThread() self.compositeObject.moveToThread(self.compositeThread) self.compositeThread.started.connect(self.compositeObject.process) self.compositeObject.message.connect(self.thread_message) self.compositeObject.progress.connect(self.thread_progress) self.progressBar.setVisible(True) self.compositeObject.finish_process.connect(self.finishComposite) self.compositeThread.start() # 显示消息和进度 def thread_message(self, value): self.statusbar.showMessage(value) def thread_progress(self, value): self.progressBar.setValue(value) # 状态改变时需要的操作 def fitState(self): if self.state == State.PLAYING: self.pushButton_2.setIcon(self.style().standardIcon( QStyle.SP_MediaPause)) self.pushButton_8.setEnabled(False) elif self.state == State.FINISHED: self.pushButton_2.setIcon(self.style().standardIcon( QStyle.SP_MediaPlay)) elif self.state == State.PAUSE: self.pushButton_2.setIcon(self.style().standardIcon( QStyle.SP_MediaPlay)) self.pushButton_8.setEnabled(True) elif self.state == State.READY: self.pushButton_2.setIcon(self.style().standardIcon( QStyle.SP_MediaPlay)) self.pushButton_2.setEnabled(True) self.pushButton_3.setEnabled(True) self.pushButton_8.setEnabled(True) self.pushButton_10.setEnabled(True) self.pushButton_4.setEnabled(True) elif self.state == State.IDLE: self.audio_backend = None self.video_backend = None self.horizontalSlider.setRange(0, 0) self.updateDurationInfo(0) self.pushButton_2.setEnabled(False) self.pushButton_3.setEnabled(False) self.pushButton_4.setEnabled(False) self.pushButton_10.setEnabled(False) self.pushButton_8.setEnabled(False) # 各片段右键菜单所能进行的简单预处理 def createContextMenu(self, pos): self.cur_listWidget = self.listWidget curItem = self.listWidget.itemAt(pos) if not curItem or self.state == State.PLAYING: return popMenu = QMenu(self) deleteAction = QAction("删除", self) titokAction = QAction("tiktok效果", self) popMenu.addAction(deleteAction) popMenu.addAction(titokAction) deleteAction.triggered.connect(self.deleteItem) titokAction.triggered.connect(self.video.tiktok) saveAction = QAction("保存本地", self) popMenu.addAction(saveAction) saveAction.triggered.connect(self.saveItem) if curItem.audio: AddMusic = QAction("提取音频") popMenu.addAction(AddMusic) AddMusic.triggered.connect(self.addToAudio) popMenu.exec(QCursor.pos()) def createContextMenu2(self, pos): self.cur_listWidget = self.listWidget_2 curItem = self.listWidget_2.itemAt(pos) if not curItem or self.state == State.PLAYING: return popMenu = QMenu(self) deleteAction = QAction("删除", self) titokAction = QAction("tiktok效果", self) popMenu.addAction(deleteAction) popMenu.addAction(titokAction) deleteAction.triggered.connect(self.deleteItem) titokAction.triggered.connect(self.video.tiktok) saveAction = QAction("保存本地", self) popMenu.addAction(saveAction) saveAction.triggered.connect(self.saveItem) if curItem.audio: AddMusic = QAction("提取音频") popMenu.addAction(AddMusic) AddMusic.triggered.connect(self.addToAudio) popMenu.exec(QCursor.pos()) def createContextMenu3(self, pos): self.cur_listWidget = self.listWidget_3 curItem = self.listWidget_3.itemAt(pos) if not curItem or self.state == State.PLAYING: return popMenu = QMenu(self) deleteAction = QAction("删除", self) popMenu.addAction(deleteAction) saveAction = QAction("保存本地", self) popMenu.addAction(saveAction) saveAction.triggered.connect(self.saveItem) deleteAction.triggered.connect(self.deleteItem) popMenu.exec(QCursor.pos()) # 回退到初始状态 def returnIDLE(self): if self.calthread.isRunning() and self.video.timer.isActive(): self.stop.emit() self.state = State.IDLE self.fitState() # 删除item,若删除的是现在正在播放的,回退到初始状态以防止错误 def deleteItem(self): if self.video_backend: if self.cur_listWidget.currentItem().video == self.video_backend: self.returnIDLE() elif self.audio_backend: if self.cur_listWidget.currentItem().audio == self.audio_backend: self.returnIDLE() ditem = self.cur_listWidget.takeItem( self.cur_listWidget.row(self.cur_listWidget.currentItem())) del ditem # 将目标或背景的音频添加为音频行的item def addToAudio(self): newName = "(Audio)" + self.cur_listWidget.currentItem().text item = MyListWidgetItem(clipName=newName, video=None, audio=self.cur_listWidget.currentItem().audio) self.listWidget_3.addItem(item) self.setupMedia(item) # 结束生成后,使相关item重新可交互并清除工作线程 def finishComposite(self): self.progressBar.setVisible(False) for item in self.items: item.setFlags(item.flags() | Qt.ItemIsEnabled | Qt.ItemIsSelectable) for item in self.items_2: item.setFlags(item.flags() | Qt.ItemIsEnabled | Qt.ItemIsSelectable) for item in self.items_3: item.setFlags(item.flags() | Qt.ItemIsEnabled | Qt.ItemIsSelectable) self.compositeThread.quit() self.compositeThread.wait() # 播放结束 def processFinish(self): self.state = State.FINISHED self.fitState() # 播放进度改变时移动进度条滑块并记录时间 def positionChanged(self, position): self.horizontalSlider.setValue(position) self.updateDurationInfo(position / 10) # 播放对象改变时重设时间信息 def durationChanged(self, duration): self.horizontalSlider.setRange(0, duration * 10) self.updateDurationInfo(0) # 控制视频播放进度 def setPosition(self, position): self.video.setT(position / 10) # 控制音频播放进度 def setApos(self, position): if self.audio_backend: self.audio.setIndex( int(position / 10 * self.audio.fps / self.audio.buffersize)) # 更新时间信息 def updateDurationInfo(self, currentInfo): # 正确计算时长 if self.video_backend: duration = self.video_backend.duration elif self.audio_backend: duration = self.audio_backend.duration else: duration = 0 # 计算当前时间和总时间并显示 if currentInfo or duration: currentTime = QTime((currentInfo / 3600) % 60, (currentInfo / 60) % 60, currentInfo % 60, (currentInfo * 1000) % 1000) totalTime = QTime((duration / 3600) % 60, (duration / 60) % 60, duration % 60, (duration * 1000) % 1000) format = 'hh:mm:ss' if duration > 3600 else 'mm:ss' tStr = currentTime.toString(format) + " / " + totalTime.toString( format) else: tStr = "" self.label.setText(tStr) # 切换要播放的item def setupTarget(self, item): if self.state != State.IDLE: self.stop.emit() self.setupMedia(item) self.fitState() self.cur_listWidget = item.listWidget() # 打开目标文件 def openTarget(self): fileName, _ = QFileDialog.getOpenFileName( self, "Open Video", QDir.homePath(), "Video(*.mp4;*.wmv;*.rmvb;*.avi;*.mkv;*.mov;*.mpeg;*.flv;*.rm;*.mpeg;*.mpg)" ) if fileName != '': if self.state != State.IDLE: self.stop.emit() v = VideoFileClip(fileName) item = MyListWidgetItem(fileName.split("/")[-1], v, v.audio) self.listWidget.addItem(item) self.listWidget.setCurrentItem(item) self.cur_listWidget = self.listWidget self.setupMedia(item) # 打开背景文件 def openBackground(self): fileName, _ = QFileDialog.getOpenFileName( self, "Open Video", QDir.homePath(), "Video(*.mp4;*.wmv;*.rmvb;*.avi;*.mkv;*.mov;*.mpeg;*.flv;*.rm;*.mpeg;*.mpg)" ) if fileName != '': if self.state != State.IDLE: self.stop.emit() v = VideoFileClip(fileName) item = MyListWidgetItem(fileName.split("/")[-1], v, v.audio) self.listWidget_2.addItem(item) self.listWidget_2.setCurrentItem(item) self.cur_listWidget = self.listWidget_2 self.setupMedia(item) # 打开音频文件 def openAudio(self): fileName, _ = QFileDialog.getOpenFileName( self, "Open Audio", QDir.homePath(), "Audio(*.mp3;*.wav;*.ogg;*.wma;)") if fileName != '': if self.state != State.IDLE: self.stop.emit() a = AudioFileClip(fileName) item = MyListWidgetItem(clipName=fileName.split("/")[-1], video=None, audio=a) self.listWidget_3.addItem(item) self.listWidget_3.setCurrentItem(item) self.cur_listWidget = self.listWidget_3 self.setupMedia(item) # 设置要播放的视频、音频信息 def setupMedia(self, item): duration = item.video.duration if item.video else item.audio.duration self.video_backend = item.video self.audio_backend = item.audio # 还没有播放过片段,建立子线程并初始化相应对象 if self.flag == 0: self.video = Video(item.video) self.durationChanged(duration) self.calthread = QThread() self.video.moveToThread(self.calthread) self.video.t_changed.connect(self.positionChanged) self.stop.connect(self.video.stop) self.pause.connect(self.video.pause) self.resume.connect(self.video.resume) self.video.finish.connect(self.processFinish) self.calthread.started.connect(self.video.work) self.set_video.connect(self.video.setClip) self.audioThread = QThread() if self.audio_backend: self.audio = Audio(clip=item.audio, fps=item.audio.fps, buffersize=int(1.0 / self.video.fps * item.audio.fps), nbytes=2) else: self.audio = Audio(clip=None, fps=0, buffersize=0, nbytes=0) self.audio.moveToThread(self.audioThread) self.audioThread.started.connect(self.audio.work) self.set_audio.connect(self.audio.setClip) # 已经播放过,此时视频线程与音频线程均存在,设置相应对象信息即可 else: v_fps = item.video.fps if item.video else 30 if item.video and item.audio: self.durationChanged(duration) self.set_video.emit(item.video) self.set_audio.emit(item.audio, item.audio.fps, int(1.0 / v_fps * item.audio.fps), 2) elif item.video and not item.audio: self.set_video.emit(item.video) self.set_audio.emit(None, 44100, 0, 2) self.durationChanged(duration) else: self.set_video.emit(item.video) self.durationChanged(item.audio.duration) self.set_audio.emit(item.audio, item.audio.fps, int(1.0 / v_fps * item.audio.fps), 2) self.state = State.READY self.fitState() # 点击播放键时根据状态实施相应行为 def play(self): self.flag += 1 if self.state in (State.FINISHED, State.READY): self.state = State.PLAYING self.fitState() if self.flag == 1: self.calthread.start() self.audioThread.start() else: self.resume.emit() elif self.state == State.PLAYING: self.state = State.PAUSE self.pause.emit() self.fitState() elif self.state == State.PAUSE: self.resume.emit() self.state = State.PLAYING self.fitState()
class InferenceViewer(QtGui.QMainWindow): def __init__(self, model_name, model_format, image_dir, model_location, label, hierarchy, image_val, input_dims, output_dims, batch_size, output_dir, add, multiply, verbose, fp16, replace, loop, rali_mode, gui, container_logo, fps_file, cpu_name, gpu_name, parent): super(InferenceViewer, self).__init__(parent) self.parent = parent self.model_name = model_name self.model_format = model_format self.image_dir = image_dir self.model_location = model_location self.label = label self.hierarchy = hierarchy self.image_val = image_val self.input_dims = input_dims self.output_dims = output_dims self.batch_size = batch_size self.batch_size_int = (int)(batch_size) self.output_dir = output_dir self.add = add self.multiply = multiply self.gui = True if gui == 'yes' else False self.fp16 = fp16 self.replace = replace self.verbose = verbose self.loop = loop self.rali_mode = rali_mode inputImageDir = os.path.expanduser(image_dir) self.total_images = len(os.listdir(inputImageDir)) self.origImageQueue = queue.Queue() self.augImageQueue = queue.Queue() self.fps_file = fps_file self.inferenceEngine = None self.receiver_thread = None self.initEngines() if self.gui: self.container_index = (int)(container_logo) self.cpu_name = cpu_name self.gpu_name = gpu_name self.pauseState = False self.showAug = False self.elapsedTime = QTime.currentTime() self.lastTime = 0 self.totalElapsedTime = 0.0 self.totalAccuracy = 0 self.progIndex = 0 self.augIntensity = 0.0 self.imgCount = 0 self.frameCount = 9 self.lastIndex = self.frameCount - 1 self.x = [0] self.y = [0] self.augAccuracy = [] self.totalCurve = None self.augCurve = None self.graph = None self.legend = None self.lastAugName = None self.pen = pg.mkPen('w', width=4) self.AMD_Radeon_pixmap = QPixmap("./data/images/AMD_Radeon.png") self.AMD_Radeon_white_pixmap = QPixmap( "./data/images/AMD_Radeon-white.png") self.MIVisionX_pixmap = QPixmap("./data/images/MIVisionX-logo.png") self.MIVisionX_white_pixmap = QPixmap( "./data/images/MIVisionX-logo-white.png") self.EPYC_pixmap = QPixmap("./data/images/EPYC-blue.png") self.EPYC_white_pixmap = QPixmap( "./data/images/EPYC-blue-white.png") self.docker_pixmap = QPixmap("./data/images/Docker.png") self.singularity_pixmap = QPixmap("./data/images/Singularity.png") self.rali_pixmap = QPixmap("./data/images/RALI.png") self.rali_white_pixmap = QPixmap("./data/images/RALI-white.png") self.graph_image_pixmap = QPixmap("./data/images/Graph-image.png") self.graph_image_white_pixmap = QPixmap( "./data/images/Graph-image-white.png") self.initUI() self.updateTimer = QTimer() self.updateTimer.timeout.connect(self.update) self.updateTimer.timeout.connect(self.plotGraph) self.updateTimer.timeout.connect(self.setProgressBar) self.updateTimer.start(40) def initUI(self): uic.loadUi("inference_viewer.ui", self) self.setStyleSheet("background-color: white") self.name_label.setText("Model: %s" % (self.model_name)) self.cpuName_label.setText(self.cpu_name) self.gpuName_label.setText(self.gpu_name) self.dataset_label.setText("Augmentation set - %d" % (self.rali_mode)) self.imagesFrame.setStyleSheet( ".QFrame {border-width: 20px; border-image: url(./data/images/filmStrip.png);}" ) self.total_progressBar.setStyleSheet( "QProgressBar::chunk { background: lightblue; }") self.top1_progressBar.setStyleSheet( "QProgressBar::chunk { background: green; }") self.top5_progressBar.setStyleSheet( "QProgressBar::chunk { background: lightgreen; }") self.mis_progressBar.setStyleSheet( "QProgressBar::chunk { background: red; }") self.total_progressBar.setMaximum(self.total_images * self.batch_size_int) self.graph = pg.PlotWidget(title="Accuracy vs Time") self.graph.setLabel('left', 'Accuracy', '%') self.graph.setLabel('bottom', 'Time', 's') self.graph.setYRange(0, 100, padding=0) pg.setConfigOptions(antialias=True) self.totalCurve = self.graph.plot(pen=self.pen) self.augCurve = self.graph.plot(pen=pg.mkPen('b', width=4)) self.legend = pg.LegendItem(offset=(370, 1)) self.legend.setParentItem(self.graph.plotItem) self.legend.addItem(self.totalCurve, 'Cumulative') self.graph.setBackground(None) self.graph.setMaximumWidth(550) self.graph.setMaximumHeight(300) self.verticalLayout_2.addWidget(self.graph) self.level_slider.setMaximum(100) self.level_slider.valueChanged.connect(self.setIntensity) self.pause_pushButton.setStyleSheet( "color: white; background-color: darkBlue") self.stop_pushButton.setStyleSheet( "color: white; background-color: darkRed") self.pause_pushButton.clicked.connect(self.pauseView) self.stop_pushButton.clicked.connect(self.terminate) self.dark_checkBox.stateChanged.connect(self.setBackground) self.verbose_checkBox.stateChanged.connect(self.showVerbose) self.rali_checkBox.stateChanged.connect(self.showRALI) self.dark_checkBox.setChecked(True) self.graph_imageLabel.setPixmap(self.graph_image_pixmap) if self.container_index == 1: self.container_logo.setPixmap(self.docker_pixmap) elif self.container_index == 2: self.container_logo.setPixmap(self.singularity_pixmap) else: self.container_logo.hide() for augmentation in range(self.batch_size_int): self.augAccuracy.append([0]) self.showVerbose() self.showRALI() def initEngines(self): self.receiver_thread = QThread() # Creating an object for inference. self.inferenceEngine = modelInference( self.model_name, self.model_format, self.image_dir, self.model_location, self.label, self.hierarchy, self.image_val, self.input_dims, self.output_dims, self.batch_size, self.output_dir, self.add, self.multiply, self.verbose, self.fp16, self.replace, self.loop, self.rali_mode, self.origImageQueue, self.augImageQueue, self.gui, self.total_images, self.fps_file) self.inferenceEngine.moveToThread(self.receiver_thread) self.receiver_thread.started.connect(self.inferenceEngine.runInference) self.receiver_thread.finished.connect(self.inferenceEngine.deleteLater) self.receiver_thread.start() self.receiver_thread.terminate() def paintEvent(self, event): self.showAugImage() self.showImage() self.displayFPS() if self.imgCount == self.total_images: if self.loop == 'yes': self.resetViewer() else: self.terminate() def resetViewer(self): self.imgCount = 0 del self.x[:] self.x.append(0) del self.y[:] self.y.append(0) del self.augAccuracy[:] for augmentation in range(self.batch_size_int): self.augAccuracy.append([0]) self.lastTime = 0 self.elapsedTime = QTime.currentTime() self.totalElapsedTime = 0.0 self.progIndex = 0 self.showAug = False self.lastIndex = self.frameCount - 1 self.totalCurve.clear() self.augCurve.clear() self.name_label.setText("Model: %s" % (self.model_name)) self.legend.removeItem(self.lastAugName) def setProgressBar(self): if self.showAug: self.setAugProgress(self.progIndex) else: self.setTotalProgress() def setTotalProgress(self): totalStats = self.inferenceEngine.getTotalStats() top1 = totalStats[0] top5 = totalStats[1] mis = totalStats[2] totalCount = top5 + mis self.totalAccuracy = (float)(top5) / (totalCount + 1) * 100 self.total_progressBar.setValue(totalCount) self.total_progressBar.setMaximum(self.total_images * self.batch_size_int) self.imgProg_label.setText( "Processed: %d of %d" % (totalCount, self.total_images * self.batch_size_int)) self.top1_progressBar.setValue(top1) self.top1_progressBar.setMaximum(totalCount) self.top5_progressBar.setValue(top5) self.top5_progressBar.setMaximum(totalCount) self.mis_progressBar.setValue(mis) self.mis_progressBar.setMaximum(totalCount) def setAugProgress(self, augmentation): augStats = self.inferenceEngine.getAugStats(augmentation) top1 = augStats[0] top5 = augStats[1] mis = augStats[2] totalCount = top5 + mis self.total_progressBar.setValue(totalCount) self.total_progressBar.setMaximum(self.total_images) self.imgProg_label.setText("Processed: %d of %d" % (totalCount, self.total_images)) self.top1_progressBar.setValue(top1) self.top1_progressBar.setMaximum(totalCount) self.top5_progressBar.setValue(top5) self.top5_progressBar.setMaximum(totalCount) self.mis_progressBar.setValue(mis) self.mis_progressBar.setMaximum(totalCount) def plotGraph(self): if not self.pauseState: curTime = self.elapsedTime.elapsed() / 1000.0 if (curTime - self.lastTime > 0.01): self.x.append(curTime + self.totalElapsedTime) self.y.append(self.totalAccuracy) self.totalCurve.setData(x=self.x, y=self.y, pen=self.pen) for augmentation in range(self.batch_size_int): augStats = self.inferenceEngine.getAugStats(augmentation) top5 = augStats[1] mis = augStats[2] totalCount = top5 + mis totalAccuracy = (float)(top5) / (totalCount + 1) * 100 self.augAccuracy[augmentation].append(totalAccuracy) if self.showAug: self.augCurve.setData(x=self.x, y=self.augAccuracy[self.progIndex], pen=pg.mkPen('b', width=4)) self.lastTime = curTime def showImage(self): if not self.origImageQueue.empty(): origImage = self.origImageQueue.get() origWidth = origImage.shape[1] origHeight = origImage.shape[0] qOrigImage = QtGui.QImage(origImage, origWidth, origHeight, origWidth * 3, QtGui.QImage.Format_RGB888) qOrigImageResized = qOrigImage.scaled(self.image_label.width(), self.image_label.height(), QtCore.Qt.IgnoreAspectRatio) index = self.imgCount % self.frameCount self.origImage_layout.itemAt(index).widget().setPixmap( QtGui.QPixmap.fromImage(qOrigImageResized)) self.origImage_layout.itemAt(index).widget().setStyleSheet( "border: 5px solid yellow;") self.origImage_layout.itemAt( self.lastIndex).widget().setStyleSheet("border: 0") self.imgCount += 1 self.lastIndex = index def showAugImage(self): if not self.augImageQueue.empty(): augImage = self.augImageQueue.get() augWidth = augImage.shape[1] augHeight = augImage.shape[0] qAugImage = QtGui.QImage(augImage, augWidth, augHeight, augWidth * 3, QtGui.QImage.Format_RGB888) if self.batch_size_int == 64: qAugImageResized = qAugImage.scaled( self.aug_label.width(), self.aug_label.height(), QtCore.Qt.IgnoreAspectRatio) else: qAugImageResized = qAugImage.scaled(self.aug_label.width(), self.aug_label.height(), QtCore.Qt.KeepAspectRatio) self.aug_label.setPixmap(QtGui.QPixmap.fromImage(qAugImageResized)) def keyPressEvent(self, event): if event.key() == QtCore.Qt.Key_Escape: self.terminate() if event.key() == QtCore.Qt.Key_Space: self.pauseView() def mousePressEvent(self, event): if event.button() == QtCore.Qt.LeftButton: mousePos = event.pos() if self.aug_label.geometry().contains(mousePos): index = self.calculateIndex(mousePos.x(), mousePos.y()) self.progIndex = index self.showAug = True augName = self.inferenceEngine.getAugName(index) self.name_label.setText(augName) self.augCurve.setData(x=self.x, y=self.augAccuracy[self.progIndex], pen=pg.mkPen('b', width=4)) self.legend.removeItem(self.lastAugName) self.legend.addItem(self.augCurve, augName) self.lastAugName = augName else: self.showAug = False self.name_label.setText("Model: %s" % (self.model_name)) self.augCurve.clear() self.legend.removeItem(self.lastAugName) self.legend.removeItem('Cumulative') self.legend.addItem(self.totalCurve, 'Cumulative') if not self.pauseState: self.totalCurve.clear() self.augCurve.clear() def setBackground(self): if self.dark_checkBox.isChecked(): self.setStyleSheet("background-color: #25232F;") self.pen = pg.mkPen('w', width=4) self.graph.setBackground(None) self.origTitle_label.setStyleSheet("color: #C82327;") self.controlTitle_label.setStyleSheet("color: #C82327;") self.progTitle_label.setStyleSheet("color: #C82327;") self.graphTitle_label.setStyleSheet("color: #C82327;") self.augTitle_label.setStyleSheet("color: #C82327;") self.name_label.setStyleSheet("color: white;") self.dataset_label.setStyleSheet("color: white;") self.imgProg_label.setStyleSheet("color: white;") self.fps_label.setStyleSheet("color: #C82327;") self.dark_checkBox.setStyleSheet("color: white;") self.verbose_checkBox.setStyleSheet("color: white;") self.rali_checkBox.setStyleSheet("color: white;") self.level_label.setStyleSheet("color: white;") self.low_label.setStyleSheet("color: white;") self.high_label.setStyleSheet("color: white;") self.cpu_label.setStyleSheet("color: #C82327;") self.gpu_label.setStyleSheet("color: #C82327;") self.cpuName_label.setStyleSheet("color: white;") self.gpuName_label.setStyleSheet("color: white;") self.AMD_logo.setPixmap(self.AMD_Radeon_white_pixmap) if self.rali_checkBox.isChecked(): self.MIVisionX_logo.setPixmap(self.rali_white_pixmap) self.graph_imageLabel.setPixmap(self.graph_image_white_pixmap) else: self.MIVisionX_logo.setPixmap(self.MIVisionX_white_pixmap) self.EPYC_logo.setPixmap(self.EPYC_white_pixmap) self.totalCurve.setData(x=self.x, y=self.y, pen=self.pen) else: self.setStyleSheet("background-color: white;") self.pen = pg.mkPen('k', width=4) self.graph.setBackground(None) self.origTitle_label.setStyleSheet("color: 0;") self.controlTitle_label.setStyleSheet("color: 0;") self.progTitle_label.setStyleSheet("color: 0;") self.graphTitle_label.setStyleSheet("color: 0;") self.augTitle_label.setStyleSheet("color: 0;") self.name_label.setStyleSheet("color: 0;") self.dataset_label.setStyleSheet("color: 0;") self.imgProg_label.setStyleSheet("color: 0;") self.fps_label.setStyleSheet("color: 0;") self.dark_checkBox.setStyleSheet("color: 0;") self.verbose_checkBox.setStyleSheet("color: 0;") self.rali_checkBox.setStyleSheet("color: 0;") self.level_label.setStyleSheet("color: 0;") self.low_label.setStyleSheet("color: 0;") self.high_label.setStyleSheet("color: 0;") self.cpu_label.setStyleSheet("color: 0;") self.gpu_label.setStyleSheet("color: 0;") self.cpuName_label.setStyleSheet("color: 0;") self.gpuName_label.setStyleSheet("color: 0;") self.AMD_logo.setPixmap(self.AMD_Radeon_pixmap) if self.rali_checkBox.isChecked(): self.MIVisionX_logo.setPixmap(self.rali_pixmap) self.graph_imageLabel.setPixmap(self.graph_image_pixmap) else: self.MIVisionX_logo.setPixmap(self.MIVisionX_pixmap) self.EPYC_logo.setPixmap(self.EPYC_pixmap) self.totalCurve.setData(x=self.x, y=self.y, pen=self.pen) def showVerbose(self): if self.verbose_checkBox.isChecked(): self.dataset_label.show() self.fps_label.show() self.fps_lcdNumber.show() self.legend.show() self.cpu_label.show() self.gpu_label.show() self.cpuName_label.show() self.gpuName_label.show() else: self.dataset_label.hide() self.fps_label.hide() self.fps_lcdNumber.hide() self.legend.hide() self.cpu_label.hide() self.gpu_label.hide() self.gpuName_label.hide() self.cpuName_label.hide() def showRALI(self): if self.rali_checkBox.isChecked(): if self.dark_checkBox.isChecked(): self.MIVisionX_logo.setPixmap(self.rali_white_pixmap) self.graph_imageLabel.setPixmap(self.graph_image_white_pixmap) else: self.MIVisionX_logo.setPixmap(self.rali_pixmap) self.graph_imageLabel.setPixmap(self.graph_image_pixmap) self.graph_imageLabel.show() else: if self.dark_checkBox.isChecked(): self.MIVisionX_logo.setPixmap(self.MIVisionX_white_pixmap) else: self.MIVisionX_logo.setPixmap(self.MIVisionX_pixmap) self.graph_imageLabel.hide() def displayFPS(self): self.fps_lcdNumber.display(self.inferenceEngine.getFPS()) def pauseView(self): self.pauseState = not self.pauseState if self.pauseState: self.totalElapsedTime += self.elapsedTime.elapsed() / 1000.0 self.pause_pushButton.setText('Resume') else: self.elapsedTime.restart() self.pause_pushButton.setText('Pause') self.inferenceEngine.pauseInference() def terminate(self): self.inferenceEngine.terminate() self.receiver_thread.quit() for count in range(10): QThread.msleep(50) self.close() def closeEvent(self, event): self.terminate() exit(0) def setIntensity(self): augIntensity = (float)(self.level_slider.value()) / 100.0 self.inferenceEngine.setIntensity(augIntensity) def calculateIndex(self, x, y): if self.batch_size_int == 64: imgWidth = self.aug_label.width() / 16.0 else: imgWidth = self.aug_label.width() / 4.0 imgHeight = self.aug_label.height() / 4.0 x -= self.aug_label.x() y -= self.aug_label.y() column = (int)(x / imgWidth) row = (int)(y / imgHeight) index = 4 * column + row return index
class Thumbnailer(QObject): """ Extracts, caches and retrieves thumbnails for a set of files. For each set of files, a process runs to extract the files from their source. Each file is then processed, if necessary using worker processes fronted by a load balancer. """ frontend_port = pyqtSignal(int) # See also the four other signals below def __init__(self, parent, no_workers: int, logging_port: int, log_gphoto2: bool) -> None: """ :param parent: Qt parent window :param no_workers: how many thumbnail extractor processes to use :param logging_port: 0MQ port to use for logging control :param log_gphoto2: if True, log libgphoto2 logging message """ super().__init__(parent) self.context = zmq.Context.instance() self.log_gphoto2 = log_gphoto2 self._frontend_port = None # type: int self.no_workers = no_workers self.logging_port = logging_port inproc = "inproc://{}" self.thumbnailer_controller = self.context.socket(zmq.PAIR) self.thumbnailer_controller.bind(inproc.format( ThreadNames.thumbnailer)) self.load_balancer_controller = self.context.socket(zmq.PAIR) self.load_balancer_controller.bind( inproc.format(ThreadNames.load_balancer)) self.setupThumbnailManager() def generateThumbnails( self, scan_id: int, rpd_files: list, name: str, proximity_seconds: int, cache_dirs: CacheDirs, need_photo_cache_dir: bool, need_video_cache_dir: bool, camera_model: Optional[str] == None, camera_port: Optional[str] = None, entire_video_required: Optional[bool] = None, entire_photo_required: Optional[bool] = None) -> None: """ Initiates thumbnail generation. :param scan_id: worker id of the scan :param rpd_files: list of rpd_files, all of which should be from the same source :param name: name of the device :param proximity_seconds: the time elapsed between consecutive shots that is used to prioritize the order of thumbnail generation :param cache_dirs: the location where the cache directories should be created :param need_photo_cache_dir: if True, must use cache dir to extract photo thumbnail :param need_video_cache_dir: if True, must use cache dir to extract video thumbnail :param camera_model: If the thumbnails are being downloaded from a camera, this is the name of the camera, else None :param camera_port: If the thumbnails are being downloaded from a camera, this is the port of the camera, else None, :param entire_video_required: if the entire video is required to extract the thumbnail :param entire_photo_required: if the entire photo is required to extract the thumbnail """ self.thumbnailer_controller.send_multipart( create_inproc_msg( b'START_WORKER', worker_id=scan_id, data=GenerateThumbnailsArguments( scan_id=scan_id, rpd_files=rpd_files, name=name, proximity_seconds=proximity_seconds, cache_dirs=cache_dirs, need_photo_cache_dir=need_photo_cache_dir, need_video_cache_dir=need_video_cache_dir, frontend_port=self._frontend_port, log_gphoto2=self.log_gphoto2, camera=camera_model, port=camera_port, entire_video_required=entire_video_required, entire_photo_required=entire_photo_required))) @property def thumbnailReceived(self) -> pyqtBoundSignal: return self.thumbnail_manager.message @property def cacheDirs(self) -> pyqtBoundSignal: return self.thumbnail_manager.cacheDirs # Signal emitted when the worker has been forcefully stopped, rather than # merely finished in its work @property def workerStopped(self) -> pyqtSignal: return self.thumbnail_manager.workerStopped @property def workerFinished(self) -> pyqtSignal: return self.thumbnail_manager.workerFinished @property def cameraRemoved(self) -> pyqtSignal: return self.thumbnail_manager.cameraRemoved def setupThumbnailManager(self) -> None: logging.debug("Starting thumbnail model...") self.thumbnail_manager_thread = QThread() self.thumbnail_manager = ThumbnailManagerPara( logging_port=self.logging_port, thread_name=ThreadNames.thumbnailer) self.thumbnail_manager.moveToThread(self.thumbnail_manager_thread) self.thumbnail_manager_thread.started.connect( self.thumbnail_manager.run_sink) self.thumbnail_manager.receiverPortSignal.connect( self.managerReceiverPort) self.thumbnail_manager.sinkStarted.connect( self.thumbnailManagerSinkStarted) QTimer.singleShot(0, self.thumbnail_manager_thread.start) @pyqtSlot(int) def managerReceiverPort(self, port: int) -> None: self.thumbnail_manager_sink_port = port @pyqtSlot() def thumbnailManagerSinkStarted(self) -> None: logging.debug("...thumbnail model started") self.setupLoadBalancer() def setupLoadBalancer(self) -> None: logging.debug("Starting thumbnail load balancer...") self.load_balancer_thread = QThread() self.load_balancer = ThumbnailLoadBalancerManager( self.context, self.no_workers, self.thumbnail_manager_sink_port, self.logging_port) self.load_balancer.moveToThread(self.load_balancer_thread) self.load_balancer_thread.started.connect( self.load_balancer.start_load_balancer) self.load_balancer.load_balancer_started.connect( self.loadBalancerFrontendPort) QTimer.singleShot(0, self.load_balancer_thread.start) @pyqtSlot(int) def loadBalancerFrontendPort(self, frontend_port: int) -> None: logging.debug("...thumbnail load balancer started") self._frontend_port = frontend_port self.frontend_port.emit(frontend_port) def stop(self) -> None: self.thumbnailer_controller.send_multipart(create_inproc_msg(b'STOP')) self.load_balancer_controller.send_multipart( create_inproc_msg(b'STOP')) self.thumbnail_manager_thread.quit() if not self.thumbnail_manager_thread.wait(1000): self.thumbnailer_controller.send_multipart( create_inproc_msg(b'TERMINATE')) self.load_balancer_thread.quit() if not self.load_balancer_thread.wait(1000): self.load_balancer_controller.send_multipart( create_inproc_msg(b'TERMINATE')) def stop_worker(self, scan_id: int) -> None: self.thumbnailer_controller.send_multipart( create_inproc_msg(b'STOP_WORKER', worker_id=scan_id))
class Windows(QDialog, mainUI.Ui_Dialog): isRunning = False def __init__(self, parent=None): super(Windows, self).__init__(parent) self.selectedDict = None self.currentConfig = dict() self.localWords = [] self.selectedGroups = [] self.workerThread = QThread(self) self.workerThread.start() self.updateCheckThead = QThread(self) self.updateCheckThead.start() self.audioDownloadThread = QThread(self) self.updateCheckWork = None self.loginWorker = None self.queryWorker = None self.pullWorker = None self.audioDownloadWorker = None self.setupUi(self) self.setWindowTitle(MODEL_NAME) self.setupLogger() self.initCore() self.checkUpdate() # self.__dev() # 以备调试时使用 def __dev(self): def on_dev(): logger.debug('whatever') self.devBtn = QPushButton('Magic Button', self.mainTab) self.devBtn.clicked.connect(on_dev) self.gridLayout_4.addWidget(self.devBtn, 4, 3, 1, 1) def closeEvent(self, event): # 插件关闭时退出所有线程 if self.workerThread.isRunning(): self.workerThread.requestInterruption() self.workerThread.quit() self.workerThread.wait() if self.updateCheckThead.isRunning(): self.updateCheckThead.quit() self.updateCheckThead.wait() if self.audioDownloadThread.isRunning(): self.audioDownloadThread.requestInterruption() self.workerThread.quit() self.workerThread.wait() event.accept() def setupLogger(self): """初始化 Logger """ def onDestroyed(): logger.removeHandler(QtHandler) # 防止 debug 信息写入stdout/stderr 导致 Anki 崩溃 logging.basicConfig( handlers=[logging.FileHandler('dict2anki.log', 'w', 'utf-8')], level=logging.DEBUG, format='[%(asctime)s][%(levelname)8s] -- %(message)s - (%(name)s)') logTextBox = QPlainTextEdit(self) logTextBox.setLineWrapMode(QPlainTextEdit.NoWrap) layout = QVBoxLayout() layout.addWidget(logTextBox) self.logTab.setLayout(layout) QtHandler = Handler(self) logger.addHandler(QtHandler) QtHandler.newRecord.connect(logTextBox.appendPlainText) # 日志Widget销毁时移除 Handlers logTextBox.destroyed.connect(onDestroyed) def setupGUIByConfig(self): config = mw.addonManager.getConfig(__name__) self.deckComboBox.setCurrentText(config['deck']) self.dictionaryComboBox.setCurrentIndex(config['selectedDict']) self.apiComboBox.setCurrentIndex(config['selectedApi']) self.usernameLineEdit.setText( config['credential'][config['selectedDict']]['username']) self.passwordLineEdit.setText( config['credential'][config['selectedDict']]['password']) self.cookieLineEdit.setText( config['credential'][config['selectedDict']]['cookie']) self.definitionCheckBox.setChecked(config['definition']) self.imageCheckBox.setChecked(config['image']) self.sentenceCheckBox.setChecked(config['sentence']) self.phraseCheckBox.setChecked(config['phrase']) self.AmEPhoneticCheckBox.setChecked(config['AmEPhonetic']) self.BrEPhoneticCheckBox.setChecked(config['BrEPhonetic']) self.BrEPronRadioButton.setChecked(config['BrEPron']) self.AmEPronRadioButton.setChecked(config['AmEPron']) self.noPronRadioButton.setChecked(config['noPron']) self.selectedGroups = config['selectedGroup'] def initCore(self): self.usernameLineEdit.hide() self.usernameLabel.hide() self.passwordLabel.hide() self.passwordLineEdit.hide() self.dictionaryComboBox.addItems([d.name for d in dictionaries]) self.apiComboBox.addItems([d.name for d in apis]) self.deckComboBox.addItems(getDeckList()) self.setupGUIByConfig() def getAndSaveCurrentConfig(self) -> dict: """获取当前设置""" currentConfig = dict( selectedDict=self.dictionaryComboBox.currentIndex(), selectedApi=self.apiComboBox.currentIndex(), selectedGroup=self.selectedGroups, deck=self.deckComboBox.currentText(), username=self.usernameLineEdit.text(), password=Mask(self.passwordLineEdit.text()), cookie=Mask(self.cookieLineEdit.text()), definition=self.definitionCheckBox.isChecked(), sentence=self.sentenceCheckBox.isChecked(), image=self.imageCheckBox.isChecked(), phrase=self.phraseCheckBox.isChecked(), AmEPhonetic=self.AmEPhoneticCheckBox.isChecked(), BrEPhonetic=self.BrEPhoneticCheckBox.isChecked(), BrEPron=self.BrEPronRadioButton.isChecked(), AmEPron=self.AmEPronRadioButton.isChecked(), noPron=self.noPronRadioButton.isChecked(), ) logger.info(f'当前设置:{currentConfig}') self._saveConfig(currentConfig) self.currentConfig = currentConfig return currentConfig @staticmethod def _saveConfig(config): _config = deepcopy(config) _config['credential'] = [dict(username='', password='', cookie='') ] * len(dictionaries) _config['credential'][_config['selectedDict']] = dict( username=_config.pop('username'), password=str(_config.pop('password')), cookie=str(_config.pop('cookie'))) maskedConfig = deepcopy(_config) maskedCredential = [ dict(username=c['username'], password=Mask(c['password']), cookie=Mask(c['cookie'])) for c in maskedConfig['credential'] ] maskedConfig['credential'] = maskedCredential logger.info(f'保存配置项:{maskedConfig}') mw.addonManager.writeConfig(__name__, _config) def checkUpdate(self): @pyqtSlot(str, str) def on_haveNewVersion(version, changeLog): if askUser(f'有新版本:{version}是否更新?\n\n{changeLog.strip()}'): openLink(RELEASE_URL) self.updateCheckWork = VersionCheckWorker() self.updateCheckWork.moveToThread(self.updateCheckThead) self.updateCheckWork.haveNewVersion.connect(on_haveNewVersion) self.updateCheckWork.finished.connect(self.updateCheckThead.quit) self.updateCheckWork.start.connect(self.updateCheckWork.run) self.updateCheckWork.start.emit() @pyqtSlot(int) def on_dictionaryComboBox_currentIndexChanged(self, index): """词典候选框改变事件""" self.currentDictionaryLabel.setText( f'当前选择词典: {self.dictionaryComboBox.currentText()}') config = mw.addonManager.getConfig(__name__) self.cookieLineEdit.setText(config['credential'][index]['cookie']) @pyqtSlot() def on_pullRemoteWordsBtn_clicked(self): """获取单词按钮点击事件""" if not self.deckComboBox.currentText(): showInfo('\n请选择或输入要同步的牌组') return self.mainTab.setEnabled(False) self.progressBar.setValue(0) self.progressBar.setMaximum(0) currentConfig = self.getAndSaveCurrentConfig() self.selectedDict = dictionaries[currentConfig['selectedDict']]() # 登陆线程 self.loginWorker = LoginStateCheckWorker( self.selectedDict.checkCookie, json.loads(self.cookieLineEdit.text() or '{}')) self.loginWorker.moveToThread(self.workerThread) self.loginWorker.start.connect(self.loginWorker.run) self.loginWorker.logSuccess.connect(self.onLogSuccess) self.loginWorker.logFailed.connect(self.onLoginFailed) self.loginWorker.start.emit() @pyqtSlot() def onLoginFailed(self): showCritical('第一次登录或cookie失效!请重新登录') self.progressBar.setValue(0) self.progressBar.setMaximum(1) self.mainTab.setEnabled(True) self.cookieLineEdit.clear() self.loginDialog = LoginDialog( loginUrl=self.selectedDict.loginUrl, loginCheckCallbackFn=self.selectedDict.loginCheckCallbackFn, parent=self) self.loginDialog.loginSucceed.connect(self.onLogSuccess) self.loginDialog.show() @pyqtSlot(str) def onLogSuccess(self, cookie): self.cookieLineEdit.setText(cookie) self.getAndSaveCurrentConfig() self.selectedDict.checkCookie(json.loads(cookie)) self.selectedDict.getGroups() container = QDialog(self) group = wordGroup.Ui_Dialog() group.setupUi(container) for groupName in [ str(group_name) for group_name, _ in self.selectedDict.groups ]: item = QListWidgetItem() item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) item.setText(groupName) item.setCheckState(Qt.Unchecked) group.wordGroupListWidget.addItem(item) # 恢复上次选择的单词本分组 if self.selectedGroups: for groupName in self.selectedGroups[ self.currentConfig['selectedDict']]: items = group.wordGroupListWidget.findItems( groupName, Qt.MatchExactly) for item in items: item.setCheckState(Qt.Checked) else: self.selectedGroups = [list()] * len(dictionaries) def onAccepted(): """选择单词本弹窗确定事件""" # 清空 listWidget self.newWordListWidget.clear() self.needDeleteWordListWidget.clear() self.mainTab.setEnabled(False) selectedGroups = [ group.wordGroupListWidget.item(index).text() for index in range(group.wordGroupListWidget.count()) if group.wordGroupListWidget.item(index).checkState() == Qt.Checked ] # 保存分组记录 self.selectedGroups[ self.currentConfig['selectedDict']] = selectedGroups self.progressBar.setValue(0) self.progressBar.setMaximum(1) logger.info(f'选中单词本{selectedGroups}') self.getRemoteWordList(selectedGroups) def onRejected(): """选择单词本弹窗取消事件""" self.progressBar.setValue(0) self.progressBar.setMaximum(1) self.mainTab.setEnabled(True) group.buttonBox.accepted.connect(onAccepted) group.buttonBox.rejected.connect(onRejected) container.exec() def getRemoteWordList(self, selected_groups: [str]): """根据选中到分组获取分组下到全部单词,并添加到 newWordListWidget""" group_map = dict(self.selectedDict.groups) self.localWords = getWordsByDeck(self.deckComboBox.currentText()) # 启动单词获取线程 self.pullWorker = RemoteWordFetchingWorker(self.selectedDict, [( group_name, group_map[group_name], ) for group_name in selected_groups]) self.pullWorker.moveToThread(self.workerThread) self.pullWorker.start.connect(self.pullWorker.run) self.pullWorker.tick.connect( lambda: self.progressBar.setValue(self.progressBar.value() + 1)) self.pullWorker.setProgress.connect(self.progressBar.setMaximum) self.pullWorker.doneThisGroup.connect(self.insertWordToListWidget) self.pullWorker.done.connect(self.on_allPullWork_done) self.pullWorker.start.emit() @pyqtSlot(list) def insertWordToListWidget(self, words: list): """一个分组获取完毕事件""" for word in words: wordItem = QListWidgetItem(word, self.newWordListWidget) wordItem.setData(Qt.UserRole, None) self.newWordListWidget.clearSelection() @pyqtSlot() def on_allPullWork_done(self): """全部分组获取完毕事件""" localWordList = set(getWordsByDeck(self.deckComboBox.currentText())) remoteWordList = set([ self.newWordListWidget.item(row).text() for row in range(self.newWordListWidget.count()) ]) newWords = remoteWordList - localWordList # 新单词 needToDeleteWords = localWordList - remoteWordList # 需要删除的单词 logger.info(f'本地: {localWordList}') logger.info(f'远程: {remoteWordList}') logger.info(f'待查: {newWords}') logger.info(f'待删: {needToDeleteWords}') waitIcon = QIcon(':/icons/wait.png') delIcon = QIcon(':/icons/delete.png') self.newWordListWidget.clear() self.needDeleteWordListWidget.clear() for word in needToDeleteWords: item = QListWidgetItem(word) item.setCheckState(Qt.Checked) item.setIcon(delIcon) self.needDeleteWordListWidget.addItem(item) for word in newWords: item = QListWidgetItem(word) item.setIcon(waitIcon) self.newWordListWidget.addItem(item) self.newWordListWidget.clearSelection() self.dictionaryComboBox.setEnabled(True) self.apiComboBox.setEnabled(True) self.deckComboBox.setEnabled(True) self.pullRemoteWordsBtn.setEnabled(True) self.queryBtn.setEnabled(self.newWordListWidget.count() > 0) self.syncBtn.setEnabled(self.newWordListWidget.count() == 0 and self.needDeleteWordListWidget.count() > 0) if self.needDeleteWordListWidget.count( ) == self.newWordListWidget.count() == 0: logger.info('无需同步') tooltip('无需同步') self.mainTab.setEnabled(True) @pyqtSlot() def on_queryBtn_clicked(self): logger.info('点击查询按钮') currentConfig = self.getAndSaveCurrentConfig() self.queryBtn.setEnabled(False) self.pullRemoteWordsBtn.setEnabled(False) self.syncBtn.setEnabled(False) wordList = [] selectedWords = self.newWordListWidget.selectedItems() if selectedWords: # 如果选中单词则只查询选中的单词 for wordItem in selectedWords: wordBundle = dict() row = self.newWordListWidget.row(wordItem) wordBundle['term'] = wordItem.text() for configName in BASIC_OPTION + EXTRA_OPTION: wordBundle[configName] = currentConfig[configName] wordBundle['row'] = row wordList.append(wordBundle) else: # 没有选择则查询全部 for row in range(self.newWordListWidget.count()): wordBundle = dict() wordItem = self.newWordListWidget.item(row) wordBundle['term'] = wordItem.text() for configName in BASIC_OPTION + EXTRA_OPTION: wordBundle[configName] = currentConfig[configName] wordBundle['row'] = row wordList.append(wordBundle) logger.info(f'待查询单词{wordList}') # 查询线程 self.progressBar.setMaximum(len(wordList)) self.queryWorker = QueryWorker(wordList, apis[currentConfig['selectedApi']]) self.queryWorker.moveToThread(self.workerThread) self.queryWorker.thisRowDone.connect(self.on_thisRowDone) self.queryWorker.thisRowFailed.connect(self.on_thisRowFailed) self.queryWorker.tick.connect( lambda: self.progressBar.setValue(self.progressBar.value() + 1)) self.queryWorker.allQueryDone.connect(self.on_allQueryDone) self.queryWorker.start.connect(self.queryWorker.run) self.queryWorker.start.emit() @pyqtSlot(int, dict) def on_thisRowDone(self, row, result): """该行单词查询完毕""" doneIcon = QIcon(':/icons/done.png') wordItem = self.newWordListWidget.item(row) wordItem.setIcon(doneIcon) wordItem.setData(Qt.UserRole, result) @pyqtSlot(int) def on_thisRowFailed(self, row): failedIcon = QIcon(':/icons/failed.png') failedWordItem = self.newWordListWidget.item(row) failedWordItem.setIcon(failedIcon) @pyqtSlot() def on_allQueryDone(self): failed = [] for i in range(self.newWordListWidget.count()): wordItem = self.newWordListWidget.item(i) if not wordItem.data(Qt.UserRole): failed.append(wordItem.text()) if failed: logger.warning(f'查询失败或未查询:{failed}') self.pullRemoteWordsBtn.setEnabled(True) self.queryBtn.setEnabled(True) self.syncBtn.setEnabled(True) @pyqtSlot() def on_syncBtn_clicked(self): failedGenerator = (self.newWordListWidget.item(row).data(Qt.UserRole) is None for row in range(self.newWordListWidget.count())) if any(failedGenerator): if not askUser( '存在未查询或失败的单词,确定要加入单词本吗?\n 你可以选择失败的单词点击 "查询按钮" 来重试。'): return currentConfig = self.getAndSaveCurrentConfig() model = getOrCreateModel(MODEL_NAME) getOrCreateModelCardTemplate(model, 'default') deck = getOrCreateDeck(self.deckComboBox.currentText()) logger.info('同步点击') audiosDownloadTasks = [] newWordCount = self.newWordListWidget.count() # 判断是否需要下载发音 if currentConfig['noPron']: logger.info('不下载发音') whichPron = None else: whichPron = 'AmEPron' if self.AmEPronRadioButton.isChecked( ) else 'BrEPron' logger.info(f'下载发音{whichPron}') added = 0 for row in range(newWordCount): wordItem = self.newWordListWidget.item(row) wordItemData = wordItem.data(Qt.UserRole) if wordItemData: addNoteToDeck(deck, model, currentConfig, wordItemData) added += 1 # 添加发音任务 if whichPron and wordItemData.get(whichPron): audiosDownloadTasks.append(( f"{whichPron}_{wordItemData['term']}.mp3", wordItemData[whichPron], )) mw.reset() logger.info(f'发音下载任务:{audiosDownloadTasks}') if audiosDownloadTasks: self.syncBtn.setEnabled(False) self.progressBar.setValue(0) self.progressBar.setMaximum(len(audiosDownloadTasks)) if self.audioDownloadThread is not None: self.audioDownloadThread.requestInterruption() self.audioDownloadThread.quit() self.audioDownloadThread.wait() self.audioDownloadThread = QThread(self) self.audioDownloadThread.start() self.audioDownloadWorker = AudioDownloadWorker(audiosDownloadTasks) self.audioDownloadWorker.moveToThread(self.audioDownloadThread) self.audioDownloadWorker.tick.connect( lambda: self.progressBar.setValue(self.progressBar.value() + 1 )) self.audioDownloadWorker.start.connect( self.audioDownloadWorker.run) self.audioDownloadWorker.done.connect(lambda: tooltip(f'发音下载完成')) self.audioDownloadWorker.done.connect( self.audioDownloadThread.quit) self.audioDownloadWorker.start.emit() self.newWordListWidget.clear() needToDeleteWordItems = [ self.needDeleteWordListWidget.item(row) for row in range(self.needDeleteWordListWidget.count()) if self.needDeleteWordListWidget.item(row).checkState() == Qt.Checked ] needToDeleteWords = [i.text() for i in needToDeleteWordItems] deleted = 0 if needToDeleteWords and askUser( f'确定要删除这些单词吗:{needToDeleteWords[:3]}...({len(needToDeleteWords)}个)', title='Dict2Anki', parent=self): needToDeleteWordNoteIds = getNotes(needToDeleteWords, currentConfig['deck']) mw.col.remNotes(needToDeleteWordNoteIds) deleted += 1 mw.col.reset() mw.reset() for item in needToDeleteWordItems: self.needDeleteWordListWidget.takeItem( self.needDeleteWordListWidget.row(item)) logger.info('删除完成') logger.info('完成') if not audiosDownloadTasks: tooltip(f'添加{added}个笔记\n删除{deleted}个笔记')
class Window(QMainWindow, Ui_MainWindow1): final_clip = None video_backend = None cutter_end = 0 cutter_start = 0 withaudio = True def __init__(self): super(Window, self).__init__() self.setupUi(self) self.mediaPlayer = QMediaPlayer() self.pushButton.clicked.connect(self.openFile) self.pushButton_3.clicked.connect(self.process) self.pushButton_3.setEnabled(False) self.pushButton_2.setEnabled(False) self.pushButton_2.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay)) self.pushButton_2.clicked.connect(self.play) self.horizontalSlider.setRange(0,0) self.horizontalSlider.setStatusTip("slider") self.horizontalSlider.sliderMoved.connect(self.setPosition) self.label = QLabel() self.label.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) self.progressBar = QProgressBar() self.progressBar.setVisible(False) self.progressBar.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.statusbar.addPermanentWidget(self.label) self.statusbar.addPermanentWidget(self.progressBar) self.mediaPlayer.setVideoOutput(self.widget) self.mediaPlayer.stateChanged.connect(self.mediaStateChanged) self.mediaPlayer.positionChanged.connect(self.positionChanged) self.mediaPlayer.durationChanged.connect(self.durationChanged) self.mediaPlayer.error.connect(self.handleError) def changeChannel(self): Window.withaudio = not Window.withaudio self.pushButton_5.setText("有声") if Window.withaudio else self.pushButton_5.setText("无声") def setStart(self): Window.cutter_start = self.mediaPlayer.position()/1000 def process(self): Window.cutter_end = self.mediaPlayer.position()/1000 self.progressBar.setVisible(True) self.threadObject = ProcessObject() self.subThread = QThread() self.threadObject.moveToThread(self.subThread) self.subThread.started.connect(self.threadObject.process_work) self.threadObject.finish_process.connect(self.clearThread) self.threadObject.message.connect(self.thread_message) self.threadObject.progress.connect(self.thread_progress) self.subThread.start() if self.subThread.isRunning(): self.pushButton_3.setEnabled(False) def thread_message(self, value): self.statusbar.showMessage(value) def thread_progress(self, value): self.progressBar.setValue(value) def clearThread(self): self.progressBar.setVisible(False) self.pushButton_3.setEnabled(True) self.subThread.quit() def openFile(self): fileName, _ = QFileDialog.getOpenFileName(self, "Open Movie", QDir.homePath(),"Video(*.mp4;*.wmv;*.rmvb;*.avi);;Audio(*.mp3;*.wav)") if fileName != '': self.mediaPlayer.setMedia( QMediaContent(QUrl.fromLocalFile(fileName))) self.pushButton_2.setEnabled(True) Window.video_backend = VideoFileClip(fileName) self.pushButton_3.setEnabled(True) self.pushButton_5.setEnabled(True) self.pushButton_4.setEnabled(True) def play(self): if self.mediaPlayer.state() == QMediaPlayer.PlayingState: self.mediaPlayer.pause() else: self.mediaPlayer.play() def mediaStateChanged(self, state): if self.mediaPlayer.state() == QMediaPlayer.PlayingState: self.pushButton_2.setIcon( self.style().standardIcon(QStyle.SP_MediaPause)) else: self.pushButton_2.setIcon( self.style().standardIcon(QStyle.SP_MediaPlay)) def positionChanged(self, position): self.horizontalSlider.setValue(position) print(self.horizontalSlider.pageStep()) print(self.horizontalSlider.singleStep()) def durationChanged(self, duration): self.horizontalSlider.setRange(0, duration) def setPosition(self, position): self.mediaPlayer.setPosition(position) def handleError(self): self.pushButton.setEnabled(False) self.label.setText("Error: %s" % self.mediaPlayer.errorString())
class Cam_ui(QtWidgets.QMainWindow, Ui_MainWindow): #程序界面主线程 def __init__(self, parent=None): #初始化函数(每一次程序运行时,一定会执行的函数) super().__init__(parent) self.setupUi(self) #初始化槽函数 self.slot_init() #对config文件进行定义 global ip_red, ip_yellow, ip_blue, ip_green, speed #对全局变量进行写入 self.config = QSettings( "config.ini", QSettings.IniFormat ) #建立或读取config.ini文件,当程序目录下没有config文件时,建立文件;当目录有已经有config文件时,读取文件 ip_red = self.config.value("/ip_red/value") #从config中建立或读取ip_red的参数 ip_blue = self.config.value("/ip_blue/value") #从config中建立或读取ip_blue的参数 ip_yellow = self.config.value( "/ip_yellow/value") #从config中建立或读取ip_yellow的参数 ip_green = self.config.value( "/ip_green/value") #从config中建立或读取ip_green的参数 speed = self.config.value("/speed/value") #从config中建立或读取speed的参数 #对speed参数进行判断并显示 if speed != None: self.speed_edit.setText(speed) #在界面speed_edit中把speed参数值显示出来 speed = int(speed) #config中的参数读取后,得到的是str,需要将其转化为int speed = 11 - speed #speed参数的取值范围是[1,10] else: self.speed_edit.setText("1") #当第一次运行程序时,默认speed参数的值为1 speed = 1 #在各个对应的文本框中,将ip地址显示出来 self.ip_red_edit.setText(ip_red) self.ip_yellow_edit.setText(ip_yellow) self.ip_blue_edit.setText(ip_blue) self.ip_green_edit.setText(ip_green) #定义定位算法线程 #PyQt官方推荐使用moveToThread函数来实现多线程,而不是改写Threading中的run()函数 self.loc = Location(self) self.thread_loc = QThread() self.loc.moveToThread(self.thread_loc) #定义颜色分割线程 self.camera = Camera(self) self.thread_camera = QThread() self.camera.moveToThread(self.thread_camera) self.thread_camera.started.connect( self.camera.run) #设置线程执行时,执行的是camera中的run()函数 self.thread_camera.start() #启动线程 #定义socket通信相关参数 #定义各个机器人的ip地址和端口 self.addr_red = (ip_red, 13301) self.addr_yellow = (ip_yellow, 13302) self.addr_blue = (ip_blue, 13303) self.addr_green = (ip_green, 13304) #定义各个机器人对应socket self.socket_red = socket(AF_INET, SOCK_STREAM) self.socket_yellow = socket(AF_INET, SOCK_STREAM) self.socket_blue = socket(AF_INET, SOCK_STREAM) self.socket_green = socket(AF_INET, SOCK_STREAM) #定义默认状态下,socket连接状态为false self.connected = False def slot_init(self): #定义槽函数内容(参考Qt的“信号-槽”机制) self.butt_ip_edit.clicked.connect(self.ip_info_edit) #设置ip编辑按钮按下后执行的函数 self.butt_speed_edit.clicked.connect( self.speed_info_edit) #设置speed编辑按钮按下后执行的函数 self.butt_start.clicked.connect( self.dingwei_thread) #设置定位线程启动按钮按下后执行的函数 #设置各个文本框的焦点始终置底 self.loc.cursor_r.connect(self.dw_red) self.loc.cursor_y.connect(self.dw_yellow) self.loc.cursor_b.connect(self.dw_blue) self.loc.cursor_g.connect(self.dw_green) self.butt_connect.clicked.connect(self.socket) #设置网络通信按钮按下后执行的函数 self.butt_end.clicked.connect(self.end) #设置程序结束按钮按下后执行的函数 def ip_info_edit(self): global ip_red, ip_yellow, ip_blue, ip_green #对修改后的ip进行操作 ipx_red = self.ip_red_edit.text() #从文本框中获取修改后的ip地址信息 if ipx_red != "": #判断修改后的信息是否为空 self.config.setValue("/ip_red/value", ipx_red) #修改config文件中对应的值 ip_red = self.config.value("/ip_red/value") #重新读取并显示 self.ip_red_edit.setText(ip_red) ipx_yellow = self.ip_yellow_edit.text() if ipx_yellow != "": self.config.setValue("/ip_yellow/value", ipx_yellow) ip_yellow = self.config.value("/ip_yellow/value") self.ip_yellow_edit.setText(ip_yellow) ipx_blue = self.ip_blue_edit.text() if ipx_blue != "": self.config.setValue("/ip_blue/value", ipx_blue) ip_blue = self.config.value("/ip_blue/value") self.ip_blue_edit.setText(ip_blue) ipx_green = self.ip_green_edit.text() if ipx_green != "": self.config.setValue("/ip_green/value", ipx_green) ip_green = self.config.value("/ip_green/value") self.ip_green_edit.setText(ip_green) def speed_info_edit(self): global speed speedx = self.speed_edit.text() if speedx != "": self.config.setValue("/speed/value", speedx) self.speed_edit.setText(speedx) speedx = int(speedx) #令速度参数的取值为1-10,当速度为10时,参数取1;速度为1时,参数取10 speed = 11 - speedx def dw_red(self, red_dis): #设置文本框中的文本显示始终聚焦在底部(也就是始终显示最新的一条) self.Loc_red.append(red_dis) #append表示插入一段字符,并换行 self.Loc_red.moveCursor(QTextCursor.End) #将cusor移动到底部 if self.connected == True: #判断Socket是否已经连接 self.socket_red.send(red_dis.encode()) #通过Socket将字符发送出去,使用默认编码 def dw_yellow(self, yellow_dis): self.Loc_yellow.append(yellow_dis) self.Loc_yellow.moveCursor(QTextCursor.End) if self.connected == True: self.socket_yellow.send(yellow_dis.encode()) def dw_blue(self, blue_dis): self.Loc_blue.append(blue_dis) self.Loc_blue.moveCursor(QTextCursor.End) if self.connected == True: self.socket_blue.send(blue_dis.encode()) def dw_green(self, green_dis): self.Loc_green.append(green_dis) self.Loc_green.moveCursor(QTextCursor.End) if self.connected == True: self.socket_green.send(green_dis.encode()) def dingwei_thread(self): #定位算法线程设置 red_queue.queue.clear() #将清空queue中的内容清空 yellow_queue.queue.clear() blue_queue.queue.clear() green_queue.queue.clear() time.sleep(0.5) self.thread_loc.started.connect(self.loc.run) #开启定位算法线程 self.thread_loc.start() def socket(self): #设置Socket通信的连接 self.socket_red.connect(self.addr_red) self.socket_yellow.connect(self.addr_yellow) self.socket_blue.connect(self.addr_blue) self.socket_green.connect(self.addr_green) self.connected = True def end(self): #程序的结束 self.socket_red.close() #关闭Socket self.socket_yellow.close() self.socket_blue.close() self.socket_green.close() self.thread_loc.quit() #关闭线程 self.thread_camera.quit() self.camera.cap.release() #释放camera self.close() #关闭界面
class LabelPrinter(QObject): printerStatusReceived = pyqtSignal( str, bool, str, arguments=["comport", "success", "message"]) def __init__(self, app=None, db=None): super().__init__() self._app = app self._db = db self._printer_thread = QThread() self._printer_worker = None self._queue = Queue() self._is_running = False @pyqtSlot() def stopPrintJobs(self): """ Method to stop the print jobs when the application is trying to be closed :return: """ self._is_running = False def _print_job(self, equipment=None, rows=None): """ Method to start the printing thread :param equipment: Name of the printer equipment, as found in the Serial Port Manager serialPortsModel :param rows: List of rows to print :return: """ # if self._printer_thread.isRunning(): # logging.info(f"The printer thread is already running, waiting for it to conclude") self._is_running = True # start_time = time.clock() # while self._printer_thread.isRunning(): # if not self._is_running: # return # # current_time = time.clock() # if current_time - start_time > 5: # logging.error(f"Printer thread is taking too long to complete, exiting printing") # return try: if self._printer_thread.isRunning(): self._queue.put(rows) else: model = self._app.serial_port_manager.serialPortsModel idx = model.get_item_index(rolename="equipment", value=equipment) if idx != -1: comport = model.get(idx)["comPort"] logging.info(f"Printing to comport={comport}") self._queue.put(rows) kwargs = { "comport": comport, "rows": rows, "queue": self._queue } self._printer_worker = PrinterWorker(kwargs=kwargs) self._printer_worker.moveToThread(self._printer_thread) self._printer_worker.printerStatus.connect( self._printer_status_received) self._printer_thread.started.connect( self._printer_worker.run) self._printer_thread.start() except Exception as ex: logging.error(f"Error getting the comport: {ex}") def _printer_status_received(self, comport, success, message): """ Method to catch the printer results :return: """ self.printerStatusReceived.emit(comport, success, message) self._printer_thread.quit() @pyqtSlot(str, str, str, str, str, name="printADHLabel") def print_hookandline_angler_drop_hook(self, equipment: str, angler: str, drop: str, hook: str, species: str): """ Method for printing an Angler/Drop/Hook tag :param angler: :param drop: :param hook: :return: """ # Lines of Data to Print site = self._app.state_machine.site.zfill( 3) if self._app.state_machine.site else "" set_id = self._app.state_machine.setId if self._app.state_machine.setId else "" # species also used, from the input variable barcode_number = angler + drop + hook lead_in = """ N O q500 S3 D10 ZT\n""" lead_out = "\nP" + str( 1 ) + "\n\n" # lead out sends the label count (number of labels to print) # suffix = "\"\n" # Convert all of the rows to bytes lead_in_bytes = bytes(lead_in, "UTF-8") site_bytes = bytes("A0,10,0,4,1,1,N,\"Site Name: " + site + "\"\n", "UTF-8") set_id_bytes = bytes("A0,50,0,4,1,1,N,\"Set ID: " + set_id + "\"\n", "UTF-8") species_bytes = bytes("A0,90,0,4,1,2,N,\"" + species + "\"\n", "UTF-8") angler_drop_hook_bytes = bytes( "A0,150,0,5,1,2,N,\"" + str(barcode_number) + "\"\n", "UTF-8") barcode_bytes = bytes( "B0,250,0,1,3,3,100,N,\"" + str(barcode_number) + "\"\n", "UTF-8") lead_out_bytes = bytes(lead_out, "UTF-8") rows = [ lead_in_bytes, site_bytes, set_id_bytes, species_bytes, angler_drop_hook_bytes, barcode_bytes, lead_out_bytes ] if equipment is None: equipment = "Zebra Printer Aft" # logging.info(f"print ADH: {equipment}") # for row in rows: # logging.info(f"row={row}") self._print_job(equipment=equipment, rows=rows) @pyqtSlot(str, str, QVariant, str, name="printHookAndLineTagNumber") def print_hookandline_tag_number(self, tag_number, observation, species_observations, project): """ Great reference + example code for printing using EPL commands: https://www.xtuple.org/node/1083 EPL Reference: https://www.zebra.com/content/dam/zebra/manuals/en-us/printer/epl2-pm-en.pdf :return: """ if isinstance(species_observations, QJSValue): species_observations = species_observations.toVariant() # Lines of Data to Print date_time = arrow.now().to("US/Pacific").format("YYYYMMDD_HHmmss") site = self._app.state_machine.site.zfill( 3) if self._app.state_machine.site else "" set_id = self._app.state_machine.setId if self._app.state_machine.setId else "" vessel = self._app.state_machine.vessel if self._app.state_machine.vessel else "" try: sql = """ SELECT s.LATITUDE, s.LONGITUDE from SITES s INNER JOIN OPERATIONS o ON o.SITE_ID = s.SITE_ID WHERE o.OPERATION_ID = ?; """ params = [ self._app.state_machine.siteOpId, ] results = self._app.rpc.execute_query(sql=sql, params=params) latitude = longitude = "" if len(results) == 1: result = results[0] latitude = self.dd_to_formatted_lat_lon(type="latitude", value=result[0]) longitude = self.dd_to_formatted_lat_lon(type="longitude", value=result[1]) except Exception as ex: logging.error(f"Error retrieving the latitude and longitude: {ex}") depth = " m" species = species_observations["species"] if species_observations[ "species"] else "" length = species_observations[ "length"] + " cm" if species_observations["length"] else "" weight = species_observations[ "weight"] + " kg" if species_observations["weight"] else "" sex = species_observations["sex"] if species_observations["sex"] else "" pi = "" row1 = f"{date_time} Site: {site}" row2 = f"SetID: {set_id} {vessel}" row3 = f"{latitude} {longitude}" # row4 = f"{depth} {species}" row4 = f"{species}" row5 = f"{project} {observation}" row6 = f"{length} {weight} {sex}" # row7 = f"{pi} Barcode: {tag_number}" row7 = f"Barcode: {tag_number}" lead_in = """ N O q500 S3 D10 ZT\n""" lead_out = "\nP" + str( 1 ) + "\n\n" # lead out sends the label count (number of labels to print) # Convert all of the rows to bytes lead_in_bytes = bytes(lead_in, "UTF-8") row1_bytes = bytes("A0,10,0,4,1,1,N,\"" + row1 + "\"\n", "UTF-8") row2_bytes = bytes("A0,50,0,4,1,1,N,\"" + row2 + "\"\n", "UTF-8") row3_bytes = bytes("A0,90,0,4,1,1,N,\"" + row3 + "\"\n", "UTF-8") row4_bytes = bytes("A0,130,0,4,1,1,N,\"" + row4 + "\"\n", "UTF-8") row5_bytes = bytes("A0,170,0,4,1,1,N,\"" + row5 + "\"\n", "UTF-8") row6_bytes = bytes("A0,210,0,4,1,1,N,\"" + row6 + "\"\n", "UTF-8") row7_bytes = bytes("A0,250,0,4,1,1,N,\"" + row7 + "\"\n", "UTF-8") barcode_bytes = bytes( "B0,290,0,1,3,3,90,N,\"" + str(tag_number) + "\"\n", "UTF-8") lead_out_bytes = bytes(lead_out, "UTF-8") rows = [ lead_in_bytes, row1_bytes, row2_bytes, row3_bytes, row4_bytes, row5_bytes, row6_bytes, row7_bytes, barcode_bytes, lead_out_bytes ] for row in rows: logging.info(f"label row: {row}") self._print_job(equipment="Zebra Printer Cutter", rows=rows) def dd_to_formatted_lat_lon(self, type, value): """ Method to convert a latitude in decimal degrees to well-formatted string :param type: str - enumerate value: latitude / longitude :param value: :return: """ if not isinstance(value, float): logging.error( "Error formatting latitude to nice format: {0}".format(value)) return str(value) min, deg = math.modf(value) min *= 60 deg = int(deg) if type == "latitude": uom = "S" if value < 0 else "N" else: uom = "W" if value < 0 else "E" if uom in ["S", "W"] and deg <= 0 and min <= 0: deg = -deg min = -min return f"{deg:d} {min:6.3f}' {uom}" return f"{deg:d}\xb0 {min:6.3f}' {uom}"
class ManagerUpdates(QMessageBox): """ Self contained manager that checks if updates are available on GitHub and displays the ressults in a message box. """ def __init__(self, parent=None, pytesting=False): super(ManagerUpdates, self).__init__(parent) self._pytesting = pytesting self.setWindowTitle('GWHAT updates') self.setWindowIcon(icons.get_icon('master')) self.setMinimumSize(800, 700) self.setWindowFlags(Qt.Window | Qt.CustomizeWindowHint | Qt.WindowCloseButtonHint) self.setAttribute(Qt.WA_DeleteOnClose) self.setStandardButtons(QMessageBox.Ok) self.setDefaultButton(QMessageBox.Ok) self.thread_updates = QThread() self.worker_updates = WorkerUpdates() self.worker_updates.moveToThread(self.thread_updates) self.thread_updates.started.connect(self.worker_updates.start) self.worker_updates.sig_ready.connect(self._receive_updates_check) self.start_updates_check() def start_updates_check(self): """Check if updates are available.""" QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) self.thread_updates.start() @QSlot() def _receive_updates_check(self): self.thread_updates.quit() latest_release = self.worker_updates.latest_release if self.worker_updates.error is not None: icn = QMessageBox.Warning msg = self.worker_updates.error else: icn = QMessageBox.Information if self.worker_updates.update_available: url_i = ("https://gwhat.readthedocs.io/en/latest/" "getting_started.html") msg = ("<b>GWHAT %s is available!</b><br><br>" "This new version can be downloaded from our " "<a href=\"%s\">Releases</a> page.<br><br>" "Instructions to update GWHAT are provided on our " "<a href=\"%s\">Installation</a> instructions." ) % (latest_release, __releases_url__, url_i) else: url_m = "https://github.com/jnsebgosselin/gwhat/milestones" url_t = "https://github.com/jnsebgosselin/gwhat/issues" msg = ("<b>%s is up to date.</b><br><br>" "Further information about GWHAT releases are available" " on our <a href=\"%s\">Releases</a> page.<br><br>" "The roadmap of the GWHAT project can be consulted" " on our <a href=\"%s\">Milestones</a> page.<br><br>" "Please help GWHAT by reporting bugs or proposing new" " features on our" " <a href=\"%s\">Issues Tracker</a>." ) % (__namever__, __releases_url__, url_m, url_t) self.setText(msg) self.setIcon(icn) QApplication.restoreOverrideCursor() if self._pytesting is False: self.exec_() else: self.show()
class TextureMatcher(QWidget): def __init__(self, cc: ControlCenter, mat_output_node: GMaterialOutputNode): super().__init__(parent=None) self.cc = cc self._out_node = mat_output_node # Define components self._menu_bar = QMenuBar() self._settings_drawer = QDockWidget(self, Qt.Drawer) self._settings_panel = SettingsPanel(self.cc, self) self._openGL = OpenGLWidget(400, 400, None, OpenGLWidget.TEXTURE_RENDER_MODE) self._image_plotter = ImagePlotter("Render") self._target_plotter = ImagePlotter("Target") self._loss_plotter = pg.PlotWidget(title="Loss") self._layout = QGridLayout() self._shader = self._out_node.get_shader() self._program = self._out_node.get_program() self._loss_visualizer = LossVisualizer(mat_output_node) # Define properties self._loss_plot_color = (1., 0.6, 0., 1.0) self._loss_plot_style = 'default' # Define data self._target_image = None self._target_matrix = None self.thread = None self.gd = None self.ren_i = 0 self._params = {} self._loss_hist = None self._gd_info = dict() self._target_filename = None self._init_widget() def _init_widget(self): self.setWindowTitle("DiPTeR - Texture Matcher") # Setup menu visualize_menu = self._menu_bar.addMenu("visualize") visualize_loss_action = visualize_menu.addAction("visualize loss") visualize_loss_action.triggered.connect(self._open_loss_viz_window) # Setup settings panel self._settings_panel.texture_loaded.connect(self._set_image_to_match) self._settings_panel.match_start.connect( self._run_gradient_descent_torch) self._settings_panel.match_stop.connect(self._stop_gradient_descent) self._settings_panel.reset_requested.connect(self._reset) self._settings_panel.save_data.connect(self._save_data) self._settings_panel.restore_best.connect(self._restore_best_params) self._settings_panel.setMaximumWidth(250) # Setup plots self._openGL.init_done.connect(self._set_gl_program) self._image_plotter.hide_axes() self._image_plotter.set_user_input(False) self._target_plotter.hide_axes() self._target_plotter.set_user_input(False) # Setup openGL rendering window opengl_layout = QVBoxLayout() opengl_layout.setContentsMargins(20, 0, 20, 10) opengl_title = QLabel("OpenGL Render") opengl_title_font = QFont() opengl_title_font.setPointSize(14) opengl_title.setFont(opengl_title_font) opengl_layout.addWidget(opengl_title, alignment=Qt.AlignHCenter) opengl_layout.addWidget(self._openGL) # Setup strech factors self._layout.setColumnStretch(0, 2) self._layout.setColumnStretch(1, 2) self._layout.setColumnStretch(2, 2) self._layout.setColumnStretch(3, 1) self._layout.setMenuBar(self._menu_bar) self._layout.addLayout(opengl_layout, 0, 0) self._layout.addWidget(self._image_plotter, 0, 1) self._layout.addWidget(self._target_plotter, 0, 2) self._layout.addWidget(self._loss_plotter, 1, 0, 1, 3) self._layout.addWidget(self._settings_panel, 0, 3, 2, 1) self.setLayout(self._layout) def _set_image_to_match(self, image: Image): # We want to display the same image that we are testing the loss against. This image is columns major (input,y) # which is not what matplotlib want's so we have to transpose it back to row major self._target_image = image.convert("RGB") self._target_filename = image.filename self._target_matrix = image_funcs.image_to_tensor(self._target_image) self._target_plotter.set_image(self._target_matrix) def _set_gl_program(self): self._openGL.set_program(self._program) def _run_gradient_descent_torch(self): self._reset() self.gd = GradientDescent(self._target_image, self._out_node, self._settings_panel.settings()) self.thread = QThread() _logger.debug("Started Gradient Descent Thread...") run_in_thread(self.gd, self.thread, iteration_done_callback=self._gd_iter_callback, first_render_done_callback=self._set_parameter_values, gd_finished_callback=self._finish_gradient_descent) def _reset(self): # Reset procedural model parameters for key in self._params: self._params[key].restore_value() self._set_parameter_values(self._params) # Reset plots self._loss_plotter.plotItem.clear() self._image_plotter.clear() def _stop_gradient_descent(self): if self.thread.isRunning(): _logger.info("Stopping Gradient Descent Thread...") self._settings_panel.set_gd_finishing() self.gd.stop() self.thread.quit() self._settings_panel.set_gd_finished() def _finish_gradient_descent(self, params, loss_hist, info): self._params = params self._loss_hist = loss_hist self._gd_info = info min_loss = np.min(loss_hist) self._stop_gradient_descent() _logger.info( "Gradient Descent finished with a minimum loss of {:.4f}.".format( min_loss)) def _gd_iter_callback(self, props): loss_hist = props['loss_hist'] iter = props['iter'] params = props['params'] render = props['render'] self._image_plotter.set_image(render) x = np.linspace(0, iter, num=iter + 1, endpoint=True) self._loss_plotter.plot(x, loss_hist, symbol='o') self._set_parameter_values(params) _logger.info("{}. loss: {}, params: {}".format(props['iter'], props['loss'], params)) def _set_parameter_values(self, params: dict): for uniform_key, value in params.items(): try: if isinstance(value, Parameter): value = value.get_value() self._program[uniform_key] = value except IndexError as e: _logger.error("Uniform {} does not exist in program!".format( uniform_key)) raise e def _open_loss_viz_window(self): self._loss_visualizer.open(self._settings_panel.settings(), self._target_image, self._out_node) def _save_data(self, filename: str): mat_name = self.cc.active_material.name settings = self._settings_panel.settings().to_dict() f = h5py.File(filename, "w") dset = f.create_dataset("data", data=self._loss_hist, dtype="f") for key, value in settings.items(): try: dset.attrs[key] = value except TypeError: dset.attrs[key] = str(value) dset.attrs["material_name"] = mat_name dset.attrs["target_filename"] = self._target_filename f.close() _logger.info("Saved data to {}".format(filename)) def _restore_best_params(self): for k, v in self._gd_info["min_params"].items(): for pk, pv in self._params.items(): if k == pk: pv.set_value(v) self._set_parameter_values(self._params) _logger.info("Restored best parameter values with loss {}".format( self._gd_info["min_loss"]))
class ws_client(QObject): connect_signal = pyqtSignal(bool) def __init__(self, websocket_ip, port=9090, name='', frame_id="map"): super(ws_client, self).__init__() """ Class to manage publishing to ROS thru a rosbridge websocket. :param str websocket_ip: IP of the machine with the rosbridge server. :param int port: Port of the websocket server, defaults to 9090. """ self.name = websocket_ip if name == '' else name self._ip = websocket_ip self._port = port # List for for each sub topic. # Where: key = sub_topic; data = [type_data, pub_topic_name,rospy.Publisher(..)] self.sub_list = {} self.frame_id = frame_id self.abort = False self._runFlag = False self._connect_flag = False self._ws = None self._advertise_dict = {} self.ws_cb_t = QThread() self.moveToThread(self.ws_cb_t) self.ws_cb_t.started.connect(self.update) self.ws_cb_t.start() @pyqtSlot() def update(self): print("%s:%s\t|\tStart the thread of callback" % (self._ip, self._port)) while True: if self.abort: break self._callback() QApplication.processEvents() print("%s:%s\t|\tStop the thread of callback" % (self._ip, self._port)) def setIp(self, ipStr): self._ip = ipStr print("set ip", self._ip) def setPort(self, port): try: self._port = int(port) print("set port", self._port) except: pass def connect(self, ip=None, port=None): if self.is_connected(): print("The client is already connected") return if ip == None: ip = self._ip if port == None: port = self._port print("%s:%s\t|\tConnection to server" % (ip, port)) try: self._ws = websocket.create_connection('ws://' + ip + ':' + str(port)) except: print("%s:%s\t|\tError connecting to server !!!" % (ip, port)) self._connect_flag = False self.connect_signal.emit(False) return print("%s:%s\t|\tThe connection is successful: " % (ip, port)) # self.param_ws_signal.emit(self._ws) self._connect_flag = True self.connect_signal.emit(True) self._advertise_dict = {} # subscribe to for key in self.sub_list: self._subscribe(key, self.sub_list[key][0]) self._runFlag = True def disconnect(self): print("%s:%s\t|\tDisconnect: " % (self._ip, self._port)) """Cleanup all advertisings""" d = self._advertise_dict for k in d: self._unadvertise(k) self._connect_flag = False self.connect_signal.emit(False) if self._ws is not None: self._ws.close() def is_connected(self): return self._connect_flag def _advertise(self, topic_name, topic_type): """ Advertise a topic with it's type in 'package/Message' format. :param str topic_name: ROS topic name. :param str topic_type: ROS topic type, e.g. std_msgs/String. :returns str: ID to de-advertise later on. """ new_uuid = str(uuid4()) self._advertise_dict[new_uuid] = { 'topic_name': topic_name, 'topic_type': topic_type } advertise_msg = { "op": "advertise", "id": new_uuid, "topic": topic_name, "type": topic_type } # send if connect if self.is_connected(): try: self._ws.send(json.dumps(advertise_msg)) except: self._connect_flag = False self.connect_signal.emit(False) return return new_uuid def _unadvertise(self, uuid): unad_msg = { "op": "unadvertise", "id": uuid, # "topic": topic_name } # send if connect if self.is_connected(): try: self._ws.send(json.dumps(unad_msg)) except: self._connect_flag = False self.connect_signal.emit(False) def _killThread(self): self.abort = True self.ws_cb_t.quit() self.ws_cb_t.wait() def __del__(self): self._killThread() self.disconnect() self._connect_flag = False self.connect_signal.emit(False) self._runFlag = False print("%s:%s\t|\tDelete websocket: " % (self._ip, self._port)) def _publish(self, topic_name, message): """ Publish onto the already advertised topic the msg in the shape of a Python dict. :param str topic_name: ROS topic name. :param dict msg: Dictionary containing the definition of the message. """ msg = {'op': 'publish', 'topic': topic_name, 'msg': message} json_msg = json.dumps(msg) # send if connect if self.is_connected(): try: self._ws.send(json_msg) except: self._connect_flag = False self.connect_signal.emit(False) return def publish(self, topic_name, ros_message): """ Publish on a topic given ROS message thru rosbridge. :param str topic_name: ROS topic name. :param * ros_message: Any ROS message instance, e.g. LaserScan() from sensor_msgs/LaserScan. """ # First check if we already advertised the topic d = self._advertise_dict for k in d: if d[k]['topic_name'] == topic_name: # Already advertised, do nothing break else: # Not advertised, so we advertise topic_type = ros_message._type self._advertise(topic_name, topic_type) # Converting ROS message to a dictionary thru YAML ros_message_as_dict = yaml.load(ros_message.__str__()) # Publishing self._publish(topic_name, ros_message_as_dict) def subscribe(self, topic_name, msgs_data, pub_topic_name=''): if pub_topic_name == '': pub_topic_name = topic_name # added to list pub = rospy.Publisher(pub_topic_name, msgs_data.__class__, queue_size=10) self.sub_list[topic_name] = [msgs_data, pub_topic_name, pub] def _subscribe(self, topic_name, msgs_data): pub_msg = { 'op': 'subscribe', 'topic': topic_name, 'msgs_data': msgs_data._type } # send to server json_msg = json.dumps(pub_msg) self._ws.send(json_msg) self.initFlaf = True print("%s:%s\t|\tSubscribe to: %s \ttype: %s" % (self._ip, self._port, topic_name, msgs_data._type)) def _callback(self): """ Publish onto the already advertised topic the msg in the shape of a Python dict. :param str topic_name: ROS topic name. :param dict msg: Dictionary containing the definition of the message. """ if not self.is_connected(): return # send if connect try: json_message = self._ws.recv() self._connect_flag = True self.connect_signal.emit(True) type_msg = json.loads(json_message)['op'] except: self._connect_flag = False self.connect_signal.emit(False) self._runFlag = False print("connected loss") return if type_msg == 'publish': # conver json to ROS msgs msgs_conf = self.sub_list[json.loads(json_message)['topic']] dictionary = json.loads(json_message)['msg'] result = message_converter.convert_dictionary_to_ros_message( msgs_conf[0]._type, dictionary) # print("===============\n" # "Type: '%s' \n" # "===============\n '%s'" % (msgs_conf[0]._type, result)) # pub to topic #fix of unicode try: result.header.frame_id = str(header.frame_id) except: pass #fix of laserscan if msgs_conf[0]._type == 'sensor_msgs/LaserScan': for i in range(len(result.ranges)): if result.ranges[i] == None: result.ranges[i] = float('inf') else: result.ranges[i] = float(result.ranges[i]) msgs_conf[2].publish(result) if type_msg == 'service_response': print("service_response:", json.loads(json_message)['result']) # return result def call_service(self, topic_name, msgs_data, pub_topic_name=''): self.initFlaf = True if pub_topic_name == '': pub_topic_name = topic_name ros_message_as_dict = yaml.load(msgs_data.__str__()) pub_msg = { 'op': 'call_service', 'service': topic_name, "args": ros_message_as_dict } # send to server json_msg = json.dumps(pub_msg) # send if connect if self.is_connected(): try: self._ws.send(json_msg) except: self._connect_flag = False self.connect_signal.emit(False) return print("%s | call_service: %s msgs_data: %s" % (self.name, topic_name, msgs_data))
class FuncBox(QLabel): height_ = 25 def __init__(self, *args): super().__init__(*args) self.setObjectName("funcBox") self.mainWin = self.parent() self.setUi() self.defaultFlag = True # 读取按钮线程 # 提取按钮 self.extractBtn_ = self.setExtractBtn() self.typeBox_ = TypeBox(self) # 读取按钮 self.readBtn = self.setReadBtn() self.readThread = QThread() self.readWorker = ReadWorker(mainWin=self.mainWin) self.setReadThread() self.clearBtn() self.searchBtn = self.setSearchBtn() self.lstTypeSelectCombo() # 提取线程 self.extractThread = QThread() self.worker = ExtractWorker(mainWin=self.mainWin) self.extractWork() def setUi(self): self.resize(self.mainWin.width(), 50) self.move(0, self.mainWin.height() - 50) def setReadBtn(self): # 读取选中列表 readBtn = QPushButton("读取", self) readBtn.setObjectName("readBtn") readBtn.resize(80, self.height_) readBtn.move(0, self.height() - self.height_ * 2) readBtn.setEnabled(False) def readBtnFunc(): self.mainWin.findChild(QLabel, "indexSelect").pb.reset() self.readThread.start() readBtn.setEnabled(False) readBtn.clicked.connect(readBtnFunc) return readBtn def setReadThread(self): def readEnd(bool_): self.mainWin.indexList.scrollToItem(self.mainWin.indexList.item(0)) self.readBtn.setEnabled(True) self.readWorker.stop() self.readThread.quit() self.readWorker.readSignal[bool].connect(readEnd) def pbValue(int_): self.mainWin.findChild(QLabel, "indexSelect").pb.setValue(int_) self.readWorker.readSignal[int].connect(pbValue) self.readWorker.moveToThread(self.readThread) self.readThread.started.connect(self.readWorker.read) def clearBtn(self): # 清空列表 clearBtn = QPushButton("清空", self) clearBtn.setObjectName("clearBtn") clearBtn.resize(80, self.height_) clearBtn.move(80, self.height() - self.height_ * 2) def clearFunc(): self.mainWin.indexCheckList.clear() self.mainWin.extractList = [] clearBtn.clicked.connect(clearFunc) def setSearchBtn(self): # 搜索框 searchLine = QLineEdit("输入序号", self) searchLine.setObjectName("searchInput") searchLine.resize(80, self.height_) searchLine.move(0, self.height() - self.height_) # search searchBtn = QPushButton("搜索", self) searchBtn.setObjectName("searchBtn") searchBtn.resize(80, self.height_) searchBtn.move(80, self.height() - self.height_) searchBtn.setEnabled(False) def searchBtnFunc(): searchF(searchLine, self.mainWin) searchBtn.clicked.connect(searchBtnFunc) return searchBtn def lstTypeSelectCombo(self): # lstTypeSelect lstTypeSInfo = QLabel("模式选择:", self) lstTypeSInfo.resize(100, 25) lstTypeSInfo.move(160, self.height() - 50) lstTypeSelect = QComboBox(self) lstTypeSInfo.setAlignment(Qt.AlignLeft | Qt.AlignCenter) lstTypeSelect.setObjectName("lstTypeSelect") lstTypeSelect.resize(self.width() - lstTypeSInfo.width() - 220, 25) lstTypeSelect.move(lstTypeSInfo.width() + 160, self.height() - 50) for key in typeDict.keys(): lstTypeSelect.addItem(typeDict[key][2]) def actFunc(index): self.mainWin.lstType = str(index) if self.mainWin.lstType != "1": self.readBtn.click() lstTypeSelect.activated.connect(actFunc) def lstTypeSelectFunc(index): indexChangedFunc(index, self) lstTypeSelect.currentIndexChanged.connect(lstTypeSelectFunc) def setExtractBtn(self): # extractBtn extractBtn = QPushButton("提取", self) extractBtn.setObjectName("extractBtn") extractBtn.resize(60, 50) extractBtn.move(self.width() - 60, self.height() - 50) extractBtn.setEnabled(False) def extractFunc(): self.mainWin.findChild(QLabel, "typeBox").pb.reset() mainExtractFunc(extractBtn, self) extractBtn.clicked.connect(extractFunc) return extractBtn def extractWork(self): def pbValue(int_): self.mainWin.findChild(QLabel, "typeBox").pb.setValue(int_) self.worker.extractSignal[int].connect(pbValue) def endThread(bool_): self.extractBtn_.setEnabled(True) self.worker.stop() self.extractThread.quit() self.mainWin.indexCheckList.clear() self.mainWin.extractList = [] self.worker.extractSignal[bool].connect(endThread) self.worker.moveToThread(self.extractThread) self.extractThread.started.connect(self.worker.extract)
class Main(QtWidgets.QMainWindow, Ui_MainWindow): #signals #parameters = pyqtSignal(list, list, list, str, str, str) #initialize def __init__(self, parent=None): #QtWidgets.QMainWindow.__init__(self, parent) super(Main, self).__init__() ################################################## # execute the modelbuilder in an own thread -> as seen when starting the thread in the buildModel function, # a thread is only started if called from UI # create objects self.moBuThread = QThread() self.moBuObj = modelBuilder( ) # create an instance of the class modelBuilder # move modelbuilder object to thread self.moBuObj.moveToThread(self.moBuThread) # connect modelbuilder signals to slots in this class self.moBuObj.statusUpdate.connect( self.onStatusUpdate ) # the function onStatusUpdate is a slot in this class # connect modelbuilder signals to slots in this class -> the function onModelCreated contains a slot of the thread object (self.moBuThread.quit) self.moBuObj.finished.connect( self.onModelCreated ) # the function onModelCreated is a slot in this class # connect thread started signal to modelbuilder's slot # pass parameters as well: if you want to "pass parameters to QThread", then it's quite straightforward. # 1. You can access member variable of your subclass from any methods of your subclass. # 2. The slot of your worker object, that will be executed in another thread can take as many argument as you want. # 3. Use a lambda function: self.moBuThread.started.connect(lambda: self.moBuObj.build(objects, nodesWithoutMbAttribute, couplings, modelname, self.modelfolderpathname, fpesfilepath)) # Just for information: if build was executed in the main thread: modelCreated = self.moBuObj.build(self, objects, nodesWithoutMbAttribute, couplings, modelname, modelfolderpathname, fpesfilepath) # execute the build method (in the same thread) # Here the first method is used: The build method of the modelbuilder object is called without arguments, when the thread is started the build function waits until it receives data. # The data is set directly before the thread is started (see "set data for the modelbuilder object"). self.moBuThread.started.connect(self.moBuObj.build) ################################################### self.setupUi(self) #ui signals self.bselectfpesfile.clicked.connect(self.selectFPES) self.bbuildmodel.clicked.connect(self.buildModel) self.bdoc.clicked.connect(self.documentation) #slot #when the modelbuilder is finished, this function is called def onModelCreated(self, i): #when the thread has finished, quit it self.moBuThread.quit() modelCreated = i if modelCreated == 0: if self.calledFromUi: QMessageBox.information( None, "Model(s) created", "The model(s) was/were created in the folder \"%s\" (a subdirectory of the folder in which \"%s\" lies). In the <b>config.txt</b> file the model file(s) and the respective modelbase file(s) are listed." % (self.modelfolderpathname, self.selectedfpesfile), QtWidgets.QMessageBox.Ok) else: print( "OK - The model(s) was/were created in the \nMODELFOLDER: \"" + self.modelfolderpathname + "\"\nThis is a subdirectory of the folder in which \"" + self.selectedfpesfile + "\" lies. In the config.txt file the model file(s) and the respective modelbase file(s) are listed." ) print("\n") elif modelCreated == 1: QMessageBox.warning( None, "Model(s) not created", "The model(s) could not be created. The simulator or interface is not supported. Please refer to the documentation for simulators and the interface.", QtWidgets.QMessageBox.Ok) print( "Not OK - The model(s) could not be created. The simulator or interface is not supported. Please refer to the documentation for simulators and the interface." ) elif modelCreated == 2: QMessageBox.warning( None, "Model(s) not created", "The model(s) could not be created. The portnames for the basic models are not okay. Please refer to the documentation.", QtWidgets.QMessageBox.Ok) print( "Not OK - The model(s) could not be created. The portnames for the basic models are not okay. Please refer to the documentation." ) elif modelCreated == 3: QMessageBox.warning( None, "Model(s) not created", "The model(s) could not be created. The modelbasefile cannot be copied. Please check, that the modelbasefile is lying in the same folder (or a subdirectory, see documentation) as the .jsonsestree file containing the FPES. Furthermore the mb-attribute needs to refer to the modelbasefilename (see documentation). Using FMI there could also be a problem parameterizing the MB.", QtWidgets.QMessageBox.Ok) print( "Not OK - The model(s) could not be created. The modelbasefile cannot be copied. Please check, that the modelbasefile is lying in the same folder as the .jsonsestree file containing the FPES. Furthermore the mb-attribute needs to refer to the modelbasefilename (see documentation)." ) elif modelCreated == 4: QMessageBox.warning( None, "Model(s) not created", "The model(s) could not be created. A parametervalue to vary could not be interpreted as a Python variable.", QtWidgets.QMessageBox.Ok) print( "Not OK - The model(s) could not be created. A parametervalue to vary could not be interpreted as a Python variable." ) elif modelCreated == 5: QMessageBox.warning( None, "Model(s) not imported in the simulator", "The model(s) could not be imported in the simulator. The simulator could not be found. Please make sure it is accessible via Shell/Command.", QtWidgets.QMessageBox.Ok) print( "Not OK - The model(s) could not be imported in the simulator. The simulator could not be found. Please make sure it is accessible via Shell/Command." ) elif modelCreated == 6: QMessageBox.warning( None, "FMU(s) not created", "The FMU(s) could not be created. OpenModelica is necessary for creating FMU(s). Please make sure OpenModelica's omc executable is accessible via Shell/Command.", QtWidgets.QMessageBox.Ok) print( "Not OK - The FMUs could not be created. OpenModelica is necessary for creating FMUs. Please make sure OpenModelica's omc executable is accessible via Shell/Command." ) elif modelCreated == 7: QMessageBox.warning( None, "Model(s) not created", "The model(s) could not be created. The old model directory could not be removed automatically. For automatic remove no program may access any content of it. Please make sure no model directory is in the same directory as the FPES.", QtWidgets.QMessageBox.Ok) print( "Not OK - The model(s) could not be created. An old model directory could not be removed automatically. For automatic remove no program may access any content of it. Please make sure no model directory is in the same directory as the FPES." ) elif modelCreated == 8: QMessageBox.warning( None, "Model FMU(s) not imported in the simulator", "Model FMUs(s) could not be imported in the simulator (for OpenModelica / Dymola).", QtWidgets.QMessageBox.Ok) print( "Not OK - Model FMU(s) could not be imported in the simulator (for OpenModelica / Dymola)." ) elif modelCreated == 9: QMessageBox.warning( None, "Model(s) not imported in the simulator", "One FMU did not pass the compliance check. Check the statusmessage (it is reset when this message is closed)!", QtWidgets.QMessageBox.Ok) print( "Not OK - One FMU did not pass the compliance check. Check the statusmessage!" ) elif modelCreated == 10: QMessageBox.warning( None, "FMU(s) not created", "Please check the FPES and the created model which shall be translated to an FMU.", QtWidgets.QMessageBox.Ok) print( "Not OK - FMU(s) could not be created of the model(s)! Please check the FPES and the created model which shall be translated to an FMU." ) elif modelCreated == 11: if self.calledFromUi: QMessageBox.information( None, "Created", "Created in the folder \"%s\" (a subdirectory of the folder in which \"%s\" lies)." % (self.modelfolderpathname, self.selectedfpesfile), QtWidgets.QMessageBox.Ok) else: print("OK - Created in the \nFOLDER: \"" + self.modelfolderpathname + "\"\nThis is a subdirectory of the folder in which \"" + self.selectedfpesfile + "\" lies.") #if called from ui, the build model button needs to be activated again and clear the status if self.calledFromUi: self.bbuildmodel.setEnabled(True) self.lstatustext.setText("") #slot #show the statusmessage from the thread def onStatusUpdate(self, message): if self.calledFromUi: #update the UI self.lstatustext.setText(message) else: print(message + "\n") def selectFPES(self): fname = QFileDialog.getOpenFileName( self, "Open an FPES from JSON", '', "FPES SES Tree (*.jsonsestree);;All files (*)") self.leselectedfpesfile.setText(fname[0]) def buildModel(self, selectedfpesfile="", calledFromUi=True): #make variables class variables self.calledFromUi = calledFromUi #deactivate the build model button if self.calledFromUi: self.bbuildmodel.setEnabled(False) try: # get the fpesfile if self.calledFromUi: #if the modelbuilder was called from the UI self.selectedfpesfile = self.leselectedfpesfile.text() else: self.selectedfpesfile = selectedfpesfile # only if a file is selected if self.selectedfpesfile != "" and not " " in self.selectedfpesfile: # read file f = open(self.selectedfpesfile, "r") filestr = f.read() f.close() readJsonObj = readJson #create an instance of the class readJson okay, nodelist, sespes, sesvar = readJsonObj.fromJSON( self, filestr) #only if the file is okay and an FPES if okay and sespes[0][0] == "fpes" and len(nodelist) > 0: #read the FPES file objects, couplings, nodesWithoutMbAttribute = readJsonObj.readFPES( self, nodelist) if objects: print( "The nodes\n" + ",\n".join(nodesWithoutMbAttribute) + "\nhave no MB-Attribute. Are their attributes needed for the simulation? This is just a hint in case of searching for a mistake.\n" ) #get a name for the folder including the path, in which the models are created -> it shall get the name of the FPES .jsonsestree file fpesfilepathname, fpesfileext = os.path.splitext( self.selectedfpesfile) self.modelfolderpathname = fpesfilepathname + "_models" #get a name for the model -> it shall get the name of the FPES .jsonsestree file modelname = os.path.basename( fpesfilepathname) + "_model" #get the path of the selected FPES .jsonsestree file fpesfilepath = os.path.split( self.selectedfpesfile )[0] #get the path to the modelbase from the fpesfile #set data for the modelbuilder object self.moBuObj.objects = objects self.moBuObj.nodesWithoutMbAttribute = nodesWithoutMbAttribute self.moBuObj.couplings = couplings self.moBuObj.modelname = modelname self.moBuObj.modelfolderpathname = self.modelfolderpathname self.moBuObj.fpesfilepath = fpesfilepath self.moBuObj.sesvar = sesvar #if called from ui: start the modelbuilder thread #if called from shell: start the build function (not in an own thread) if self.calledFromUi: self.moBuThread.start() else: self.moBuObj.build() elif not objects: QMessageBox.warning( None, "Model(s) not created", "The model(s) could not be created. Objects cannot be created from the nodelist.", QtWidgets.QMessageBox.Ok) print( "Not OK - The model(s) could not be created. Objects cannot be created from the nodelist." ) # if called from ui, the build model button needs to be activated again and clear the status if self.calledFromUi: self.bbuildmodel.setEnabled(True) self.lstatustext.setText("") else: nWoMbA = ', '.join(nodesWithoutMbAttribute) QMessageBox.warning( None, "Model(s) not created", "The model(s) could not be created. The nodes \"%s\" do not have an mb-attribute." % nWoMbA, QtWidgets.QMessageBox.Ok) print( "Model(s) not created", "The model(s) could not be created. The nodes \"" + nWoMbA + "\" do not have an mb-attribute.") # if called from ui, the build model button needs to be activated again and clear the status if self.calledFromUi: self.bbuildmodel.setEnabled(True) self.lstatustext.setText("") else: QMessageBox.warning( None, "Can not read file", "The file \"%s\" seems not to contain an FPES. Please open this file in SESToPy, make sure it represents an FPES and that the Selector in the Information ToolBox is set to flattened PES." % self.selectedfpesfile, QtWidgets.QMessageBox.Ok) print( "Not OK - The file \"" + self.selectedfpesfile + "\" seems not to contain an FPES. Please open this file in SESToPy, make sure it represents an FPES and that the Selector in the Information ToolBox is set to flattened PES." ) # if called from ui, the build model button needs to be activated again and clear the status if self.calledFromUi: self.bbuildmodel.setEnabled(True) self.lstatustext.setText("") else: QMessageBox.information( None, "Selection missing or the path/filename contains whitespaces", "Please select an FPES .jsonsestree file. In the name of the .jsonsestree file and the path to it no whitespaces are allowed!", QtWidgets.QMessageBox.Ok) print("Not OK - Please select an FPES .jsonsestree file.") # if called from ui, the build model button needs to be activated again and clear the status if self.calledFromUi: self.bbuildmodel.setEnabled(True) self.lstatustext.setText("") except: QMessageBox.critical( None, "Can not read file", "The file \"%s\" could not be read or there was an unknown error." % self.selectedfpesfile, QtWidgets.QMessageBox.Ok) print("Not OK - The file \"" + self.selectedfpesfile + "\" could not be read or there was an unknown error.") # if called from ui, the build model button needs to be activated again and clear the status if self.calledFromUi: self.bbuildmodel.setEnabled(True) self.lstatustext.setText("") """documentation""" def documentation(self): #QDesktopServices.openUrl(QUrl(self.programPath + "Documentation/Doc_LaTeX/doc.pdf")) if not QDesktopServices.openUrl( QUrl("file:Documentation/Doc_LaTeX/doc.pdf")): QDesktopServices.openUrl( QUrl("file:doc.pdf") ) #if the doc.pdf is in the main folder (e.g. after building executable)
class StitcherUI(QDialog): thread_invoker = pyqtSignal() def __init__(self, argv, terminate, parent=None): super(StitcherUI, self).__init__(parent) self.setWindowTitle("Stitcher Preview") st = stitch.Stitcher(argv=argv) self.st = st #determine the shrink ratio to avoid too huge preview preview_ratio = 1.0 if st.image.shape[1] > 10000: preview_ratio = 10000.0 / st.image.shape[1] if st.image.shape[0]*preview_ratio > 500: preview_ratio = 500.0 / st.image.shape[0] self.terminate = terminate self.thread = QThread() self.thread.start() self.worker = Renderer(st=st, preview_ratio=preview_ratio) #it might be too early. #determine the window size height,width = st.image.shape[0:2] height = int(height*preview_ratio) #determine the preview area size width = int(width*preview_ratio) self.scrollArea = QScrollArea() #self.scrollArea.setMaximumHeight(1000) self.largecanvas = ExtensibleCanvasWidget(width, height) #print(width,height) self.worker.frameRendered.connect(self.largecanvas.updatePixmap) #Do not close the window when finished. #self.worker.finished.connect(self.finishIt) self.worker.moveToThread(self.thread) self.thread_invoker.connect(self.worker.task) self.thread_invoker.emit() self.scrollArea.setWidget(self.largecanvas) self.scrollArea.setMinimumHeight(500) #self.largecanvas.sizeHint().height()) self.scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.btnStop = QPushButton('Stop') self.btnStop.clicked.connect(lambda: self.worker.stop()) self.btnStop.clicked.connect(self.terminateIt) self.progress = QProgressBar(self) self.worker.progress.connect(self.progress.setValue) self.layout = QVBoxLayout() self.layout.addWidget(self.btnStop) self.layout.addWidget(self.progress) self.layout.addWidget(self.scrollArea) self.layout.addStretch(1) self.setLayout(self.layout) def terminateIt(self): self.close() if self.terminate: sys.exit(1) #terminated def finishIt(self): self.close() def closeEvent(self, event): self.stop_thread() def stop_thread(self): self.worker.stop() self.thread.quit() self.thread.wait()
class DAQ_PID(QObject): """ """ log_signal = pyqtSignal(str) #look for eventual model files command_pid = pyqtSignal(ThreadCommand) command_stage = pyqtSignal(ThreadCommand) move_done_signal = pyqtSignal(str, float) models = [] try: model_mod = importlib.import_module('pymodaq_pid_models') for ind_file, entry in enumerate(os.scandir(os.path.join(model_mod.__path__[0], 'models'))): if not entry.is_dir() and entry.name != '__init__.py': try: file, ext = os.path.splitext(entry.name) importlib.import_module('.'+file, model_mod.__name__+'.models') models.append(file) except Exception as e: print(e) if 'PIDModelMock' in models: mods = models mods.pop(models.index('PIDModelMock')) models = ['PIDModelMock'] models.extend(mods) except Exception as e: print(e) if len(models) == 0: logger.warning('No valid installed models') def __init__(self,area, detector_modules = [], actuator_modules =[]): QLocale.setDefault(QLocale(QLocale.English, QLocale.UnitedStates)) super(DAQ_PID,self).__init__() self.settings = Parameter.create(title='PID settings', name='pid_settings', type='group', children=params) self.title = 'PyMoDAQ PID' self.Initialized_state = False self.model_class = None self.detector_modules = detector_modules self.actuator_modules = actuator_modules self.dock_area = area self.overshoot = None self.check_moving = False self.preset_manager = PresetManager() self.setupUI() self.command_stage.connect(self.move_Abs) #to be compatible with actuator modules within daq scan self.enable_controls_pid(False) self.enable_controls_pid_run(False) def ini_PID(self): if self.ini_PID_action.isChecked(): output_limits =[None,None] if self.settings.child('main_settings', 'pid_controls', 'output_limits', 'output_limit_min_enabled').value(): output_limits[0] = self.settings.child('main_settings', 'pid_controls', 'output_limits', 'output_limit_min').value() if self.settings.child('main_settings', 'pid_controls', 'output_limits', 'output_limit_max_enabled').value(): output_limits[1] = self.settings.child('main_settings', 'pid_controls', 'output_limits', 'output_limit_max').value() self.PIDThread = QThread() pid_runner = PIDRunner(self.model_class, [mod.move_done_signal for mod in self.actuator_modules], [mod.grab_done_signal for mod in self.detector_modules], [mod.command_stage for mod in self.actuator_modules], [mod.command_detector for mod in self.detector_modules], dict(Kp=self.settings.child('main_settings', 'pid_controls', 'pid_constants', 'kp').value(), Ki=self.settings.child('main_settings', 'pid_controls', 'pid_constants', 'ki').value(), Kd=self.settings.child('main_settings', 'pid_controls', 'pid_constants', 'kd').value(), setpoint=self.settings.child('main_settings', 'pid_controls', 'set_point').value(), sample_time=self.settings.child('main_settings', 'pid_controls', 'sample_time').value()/1000, output_limits=output_limits, auto_mode=False), filter=dict(enable=self.settings.child('main_settings', 'pid_controls', 'filter', 'filter_enable').value(), value=self.settings.child('main_settings', 'pid_controls', 'filter', 'filter_step').value()), det_averaging=[mod.settings.child('main_settings', 'Naverage').value() for mod in self.detector_modules], ) self.PIDThread.pid_runner = pid_runner pid_runner.pid_output_signal.connect(self.process_output) pid_runner.status_sig.connect(self.thread_status) self.command_pid.connect(pid_runner.queue_command) pid_runner.moveToThread(self.PIDThread) self.PIDThread.start() self.pid_led.set_as_true() self.enable_controls_pid_run(True) else: if hasattr(self,'PIDThread'): if self.PIDThread.isRunning(): try: self.PIDThread.quit() except: pass self.pid_led.set_as_false() self.enable_controls_pid_run(False) self.Initialized_state = True pyqtSlot(dict) def process_output(self, datas): self.output_viewer.show_data([[dat] for dat in datas['output']]) self.input_viewer.show_data([[dat] for dat in datas['input']]) self.currpoint_sb.setValue(np.mean(datas['input'])) if self.check_moving: if np.abs(np.mean(datas['input'])-self.settings.child('main_settings', 'pid_controls', 'set_point').value()) < \ self.settings.child('main_settings', 'epsilon').value(): self.move_done_signal.emit(self.title, np.mean(datas['input'])) self.check_moving = False print('Move from {:s} is done: {:f}'.format('PID', np.mean(datas['input']))) @pyqtSlot(ThreadCommand) def move_Abs(self, command=ThreadCommand()): """ """ if command.command == "move_Abs": self.check_moving = True self.setpoint_sb.setValue(command.attributes[0]) QtWidgets.QApplication.processEvents() def enable_controls_pid(self,enable = False): self.ini_PID_action.setEnabled(enable) self.setpoint_sb.setOpts(enabled = enable) def enable_controls_pid_run(self,enable = False): self.run_action.setEnabled(enable) self.pause_action.setEnabled(enable) def setupUI(self): self.dock_pid = Dock('PID controller', self.dock_area) self.dock_area.addDock(self.dock_pid) #%% create logger dock self.logger_dock=Dock("Logger") self.logger_list=QtWidgets.QListWidget() self.logger_list.setMinimumWidth(300) self.logger_dock.addWidget(self.logger_list) self.dock_area.addDock(self.logger_dock,'right') self.logger_dock.setVisible(True) widget = QtWidgets.QWidget() widget_toolbar = QtWidgets.QWidget() verlayout = QtWidgets.QVBoxLayout() widget.setLayout(verlayout) toolbar_layout = QtWidgets.QGridLayout() widget_toolbar.setLayout(toolbar_layout) iconquit = QtGui.QIcon() iconquit.addPixmap(QtGui.QPixmap(":/icons/Icon_Library/close2.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.quit_action = QtWidgets.QPushButton(iconquit, "Quit") self.quit_action.setToolTip('Quit the application') toolbar_layout.addWidget(self.quit_action,0,0,1,2) self.quit_action.clicked.connect(self.quit_fun) iconini = QtGui.QIcon() iconini.addPixmap(QtGui.QPixmap(":/icons/Icon_Library/ini.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.ini_model_action = QtWidgets.QPushButton(iconini, "Init Model") self.ini_model_action.setToolTip('Initialize the chosen model') toolbar_layout.addWidget(self.ini_model_action,2,0) self.ini_model_action.clicked.connect(self.ini_model) self.model_led = QLED() toolbar_layout.addWidget(self.model_led, 2,1) self.ini_PID_action = QtWidgets.QPushButton(iconini, "Init PID") self.ini_PID_action.setToolTip('Initialize the PID loop') toolbar_layout.addWidget(self.ini_PID_action,2,2) self.ini_PID_action.setCheckable(True) self.ini_PID_action.clicked.connect(self.ini_PID) self.pid_led = QLED() toolbar_layout.addWidget(self.pid_led, 2,3) self.iconrun = QtGui.QIcon() self.iconrun.addPixmap(QtGui.QPixmap(":/icons/Icon_Library/run2.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.icon_stop = QtGui.QIcon() self.icon_stop.addPixmap(QtGui.QPixmap(":/icons/Icon_Library/stop.png")) self.run_action = QtWidgets.QPushButton(self.iconrun, "", None) self.run_action.setToolTip('Start PID loop') self.run_action.setCheckable(True) toolbar_layout.addWidget(self.run_action,0,2) self.run_action.clicked.connect(self.run_PID) iconpause = QtGui.QIcon() iconpause.addPixmap(QtGui.QPixmap(":/icons/Icon_Library/pause.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.pause_action = QtWidgets.QPushButton(iconpause, "", None) self.pause_action.setToolTip('Pause PID') self.pause_action.setCheckable(True) toolbar_layout.addWidget(self.pause_action,0,3) self.pause_action.setChecked(True) self.pause_action.clicked.connect(self.pause_PID) lab = QtWidgets.QLabel('Set Point:') toolbar_layout.addWidget(lab, 3,0,1,2) self.setpoint_sb = custom_tree.SpinBoxCustom() self.setpoint_sb.setMinimumHeight(40) font = self.setpoint_sb.font() font.setPointSizeF(20) self.setpoint_sb.setFont(font) self.setpoint_sb.setDecimals(6) toolbar_layout.addWidget(self.setpoint_sb,3,2,1,2) self.setpoint_sb.valueChanged.connect(self.settings.child('main_settings', 'pid_controls', 'set_point').setValue) lab1 = QtWidgets.QLabel('Current Point:') toolbar_layout.addWidget(lab1, 4,0,1,2) self.currpoint_sb = custom_tree.SpinBoxCustom() self.currpoint_sb.setMinimumHeight(40) self.currpoint_sb.setReadOnly(True) self.currpoint_sb.setDecimals(6) self.currpoint_sb.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons) font = self.currpoint_sb.font() font.setPointSizeF(20) self.currpoint_sb.setFont(font) toolbar_layout.addWidget(self.currpoint_sb,4,2,1,2) #create main parameter tree self.settings_tree = ParameterTree() self.settings_tree.setParameters(self.settings, showTop=False) verlayout.addWidget(widget_toolbar) verlayout.addWidget(self.settings_tree) self.dock_output = Dock('PID output') widget_output = QtWidgets.QWidget() self.output_viewer = Viewer0D(widget_output) self.dock_output.addWidget(widget_output) self.dock_area.addDock(self.dock_output, 'right') self.dock_input = Dock('PID input') widget_input = QtWidgets.QWidget() self.input_viewer = Viewer0D(widget_input) self.dock_input.addWidget(widget_input) self.dock_area.addDock(self.dock_input, 'bottom',self.dock_output) if len(self.models) != 0: self.get_set_model_params(self.models[0]) #connecting from tree self.settings.sigTreeStateChanged.connect(self.parameter_tree_changed)#any changes on the settings will update accordingly the detector self.dock_pid.addWidget(widget) def get_set_model_params(self, model_file): self.settings.child('models', 'model_params').clearChildren() model = importlib.import_module('.' + model_file, self.model_mod.__name__+'.models') model_class = getattr(model, model_file) params = getattr(model_class, 'params') self.settings.child('models', 'model_params').addChildren(params) def run_PID(self): if self.run_action.isChecked(): self.run_action.setIcon(self.icon_stop) self.command_pid.emit(ThreadCommand('start_PID', [self.model_class.curr_input])) QtWidgets.QApplication.processEvents() QtWidgets.QApplication.processEvents() self.command_pid.emit(ThreadCommand('run_PID', [self.model_class.curr_output])) else: self.run_action.setIcon(self.iconrun) self.command_pid.emit(ThreadCommand('stop_PID')) QtWidgets.QApplication.processEvents() def pause_PID(self): self.command_pid.emit(ThreadCommand('pause_PID', [self.pause_action.isChecked()])) def update_status(self,txt,log_type=None): """ Show the txt message in the status bar with a delay of wait_time ms. =============== =========== ======================= **Parameters** **Type** **Description** *txt* string The message to show *wait_time* int the delay of showing *log_type* string the type of the log =============== =========== ======================= """ try: if log_type is not None: self.log_signal.emit(txt) logging.info(txt) except Exception as e: pass @pyqtSlot(str) def add_log(self,txt): """ Add the QListWisgetItem initialized with txt informations to the User Interface logger_list and to the save_parameters.logger array. =============== =========== ====================== **Parameters** **Type** **Description** *txt* string the log info to add. =============== =========== ====================== """ try: now=datetime.datetime.now() new_item=QtWidgets.QListWidgetItem(now.strftime('%Y/%m/%d %H:%M:%S')+": "+txt) self.logger_list.addItem(new_item) except: pass def set_file_preset(self,model): """ Set a file managers from the converted xml file given by the filename parameter. =============== =========== =================================================== **Parameters** **Type** **Description** *filename* string the name of the xml file to be converted/treated =============== =========== =================================================== Returns ------- (Object list, Object list) tuple The updated (Move modules list, Detector modules list). See Also -------- custom_tree.XML_file_to_parameter, set_param_from_param, stop_moves, update_status,DAQ_Move_main.daq_move, DAQ_viewer_main.daq_viewer """ filename = os.path.join(get_set_pid_path(), model + '.xml') self.preset_file = filename self.preset_manager.set_file_preset(filename, show=False) self.move_docks = [] self.det_docks_settings = [] self.det_docks_viewer = [] move_forms = [] actuator_modules = [] detector_modules = [] move_types = [] ################################################################# ###### sort plugins by IDs and within the same IDs by Master and Slave status plugins=[{'type': 'move', 'value': child} for child in self.preset_manager.preset_params.child(('Moves')).children()]+[{'type': 'det', 'value': child} for child in self.preset_manager.preset_params.child(('Detectors')).children()] for plug in plugins: plug['ID']=plug['value'].child('params','main_settings','controller_ID').value() if plug["type"]=='det': plug['status']=plug['value'].child('params','detector_settings','controller_status').value() else: plug['status']=plug['value'].child('params','move_settings', 'multiaxes', 'multi_status').value() IDs=list(set([plug['ID'] for plug in plugins])) #%% plugins_sorted=[] for id in IDs: plug_Ids=[] for plug in plugins: if plug['ID']==id: plug_Ids.append(plug) plug_Ids.sort(key=lambda status: status['status']) plugins_sorted.append(plug_Ids) ################################################################# ####################### ind_move=-1 ind_det=-1 for plug_IDs in plugins_sorted: for ind_plugin, plugin in enumerate(plug_IDs): plug_name=plugin['value'].child(('name')).value() plug_init=plugin['value'].child(('init')).value() plug_settings=plugin['value'].child(('params')) if plugin['type'] == 'move': ind_move+=1 plug_type=plug_settings.child('main_settings','move_type').value() self.move_docks.append(Dock(plug_name, size=(150,250))) if ind_move==0: self.dock_area.addDock(self.move_docks[-1], 'top',self.logger_dock) else: self.dock_area.addDock(self.move_docks[-1], 'above',self.move_docks[-2]) move_forms.append(QtWidgets.QWidget()) mov_mod_tmp=DAQ_Move(move_forms[-1],plug_name) mov_mod_tmp.ui.Stage_type_combo.setCurrentText(plug_type) mov_mod_tmp.ui.Quit_pb.setEnabled(False) QtWidgets.QApplication.processEvents() set_param_from_param(mov_mod_tmp.settings,plug_settings) QtWidgets.QApplication.processEvents() mov_mod_tmp.bounds_signal[bool].connect(self.stop_moves) self.move_docks[-1].addWidget(move_forms[-1]) actuator_modules.append(mov_mod_tmp) try: if ind_plugin==0: #should be a master type plugin if plugin['status']!="Master": raise Exception('error in the master/slave type for plugin {}'.format(plug_name)) if plug_init: actuator_modules[-1].ui.IniStage_pb.click() QtWidgets.QApplication.processEvents() if 'Mock' in plug_type: QThread.msleep(500) else: QThread.msleep(4000) # to let enough time for real hardware to init properly QtWidgets.QApplication.processEvents() master_controller=actuator_modules[-1].controller else: if plugin['status']!="Slave": raise Exception('error in the master/slave type for plugin {}'.format(plug_name)) if plug_init: actuator_modules[-1].controller=master_controller actuator_modules[-1].ui.IniStage_pb.click() QtWidgets.QApplication.processEvents() if 'Mock' in plug_type: QThread.msleep(500) else: QThread.msleep(4000) # to let enough time for real hardware to init properly QtWidgets.QApplication.processEvents() except Exception as e: self.update_status(getLineInfo()+ str(e),'log') else: ind_det+=1 plug_type=plug_settings.child('main_settings','DAQ_type').value() plug_subtype=plug_settings.child('main_settings','detector_type').value() self.det_docks_settings.append(Dock(plug_name+" settings", size=(150,250))) self.det_docks_viewer.append(Dock(plug_name+" viewer", size=(350,350))) if ind_det==0: self.logger_dock.area.addDock(self.det_docks_settings[-1], 'bottom', self.dock_input) #dock_area of the logger dock else: self.dock_area.addDock(self.det_docks_settings[-1], 'bottom',self.det_docks_settings[-2]) self.dock_area.addDock(self.det_docks_viewer[-1],'right',self.det_docks_settings[-1]) det_mod_tmp=DAQ_Viewer(self.dock_area,dock_settings=self.det_docks_settings[-1], dock_viewer=self.det_docks_viewer[-1],title=plug_name, DAQ_type=plug_type, parent_scan=self) detector_modules.append(det_mod_tmp) detector_modules[-1].ui.Detector_type_combo.setCurrentText(plug_subtype) detector_modules[-1].ui.Quit_pb.setEnabled(False) set_param_from_param(det_mod_tmp.settings,plug_settings) QtWidgets.QApplication.processEvents() try: if ind_plugin==0: #should be a master type plugin if plugin['status']!="Master": raise Exception('error in the master/slave type for plugin {}'.format(plug_name)) if plug_init: detector_modules[-1].ui.IniDet_pb.click() QtWidgets.QApplication.processEvents() if 'Mock' in plug_subtype: QThread.msleep(500) else: QThread.msleep(4000) # to let enough time for real hardware to init properly QtWidgets.QApplication.processEvents() master_controller=detector_modules[-1].controller else: if plugin['status']!="Slave": raise Exception('error in the master/slave type for plugin {}'.format(plug_name)) if plug_init: detector_modules[-1].controller=master_controller detector_modules[-1].ui.IniDet_pb.click() QtWidgets.QApplication.processEvents() if 'Mock' in plug_subtype: QThread.msleep(500) else: QThread.msleep(4000) # to let enough time for real hardware to init properly QtWidgets.QApplication.processEvents() except Exception as e: self.update_status(getLineInfo()+ str(e),'log') detector_modules[-1].settings.child('main_settings','overshoot').show() detector_modules[-1].overshoot_signal[bool].connect(self.stop_moves) QtWidgets.QApplication.processEvents() return actuator_modules,detector_modules pyqtSlot(bool) def stop_moves(self,overshoot): """ Foreach module of the move module object list, stop motion. See Also -------- stop_scan, DAQ_Move_main.daq_move.stop_Motion """ self.overshoot = overshoot for mod in self.actuator_modules: mod.stop_Motion() def set_default_preset(self): actuators = self.model_class.actuators actuator_names = self.model_class.actuators_name detectors_type = self.model_class.detectors_type detectors = self.model_class.detectors detectors_name = self.model_class.detectors_name detector_modules = [] for ind_det, det in enumerate(detectors): detector_modules.append(DAQ_Viewer(area, title=detectors_name[ind_det], DAQ_type=detectors_type[ind_det])) #self.detector_modules[-1].ui.IniDet_pb.click() QtWidgets.QApplication.processEvents() detector_modules[-1].ui.Detector_type_combo.setCurrentText(detectors[ind_det]) detector_modules[-1].ui.Quit_pb.setEnabled(False) self.dock_area.addDock(self.dock_output, 'bottom') self.dock_area.moveDock(self.dock_input, 'bottom', self.dock_output) self.dock_area.addDock(self.dock_pid, 'left') dock_moves = [] actuator_modules = [] for ind_act, act in enumerate(actuators): form = QtWidgets.QWidget() dock_moves.append(Dock(actuator_names[ind_act])) area.addDock(dock_moves[-1], 'bottom', self.dock_pid) dock_moves[-1].addWidget(form) actuator_modules.append(DAQ_Move(form)) QtWidgets.QApplication.processEvents() actuator_modules[-1].ui.Stage_type_combo.setCurrentText(actuators[ind_act]) actuator_modules[-1].ui.Quit_pb.setEnabled(False) #self.actuator_modules[-1].ui.IniStage_pb.click() #QThread.msleep(1000) QtWidgets.QApplication.processEvents() return actuator_modules, detector_modules def ini_model(self): try: model_name = self.settings.child('models', 'model_class').value() model = importlib.import_module('.' +model_name, self.model_mod.__name__+'.models') self.model_class = getattr(model, model_name)(self) #try to get corresponding managers file filename = os.path.join(get_set_pid_path(), model_name + '.xml') if os.path.isfile(filename): self.actuator_modules, self.detector_modules = self.set_file_preset(model_name) else: self.actuator_modules, self.detector_modules = self.set_default_preset() # # connecting to logger # for mov in self.actuator_modules: # mov.log_signal[str].connect(self.add_log) # for det in self.detector_modules: # det.log_signal[str].connect(self.add_log) # self.log_signal[str].connect(self.add_log) self.model_class.ini_model() self.enable_controls_pid(True) self.model_led.set_as_true() self.ini_model_action.setEnabled(False) except Exception as e: self.update_status(getLineInfo() + str(e), log_type='log') def quit_fun(self): """ """ try: try: self.PIDThread.exit() except Exception as e: print(e) for module in self.actuator_modules: try: module.quit_fun() QtWidgets.QApplication.processEvents() QThread.msleep(1000) QtWidgets.QApplication.processEvents() except Exception as e: print(e) for module in self.detector_modules: try: module.stop_all() QtWidgets.QApplication.processEvents() module.quit_fun() QtWidgets.QApplication.processEvents() QThread.msleep(1000) QtWidgets.QApplication.processEvents() except Exception as e: print(e) areas=self.dock_area.tempAreas[:] for area in areas: area.win.close() QtWidgets.QApplication.processEvents() QThread.msleep(1000) QtWidgets.QApplication.processEvents() self.dock_area.parent().close() except Exception as e: print(e) def parameter_tree_changed(self,param,changes): """ Foreach value changed, update : * Viewer in case of **DAQ_type** parameter name * visibility of button in case of **show_averaging** parameter name * visibility of naverage in case of **live_averaging** parameter name * scale of axis **else** (in 2D pymodaq type) Once done emit the update settings signal to link the commit. =============== =================================== ================================================================ **Parameters** **Type** **Description** *param* instance of ppyqtgraph parameter the parameter to be checked *changes* tuple list Contain the (param,changes,info) list listing the changes made =============== =================================== ================================================================ See Also -------- change_viewer, daq_utils.custom_parameter_tree.iter_children """ for param, change, data in changes: path = self.settings.childPath(param) if change == 'childAdded': pass elif change == 'value': if param.name() == 'model_class': self.get_set_model_params(param.value()) elif param.name() == 'module_settings': if param.value(): self.settings.sigTreeStateChanged.disconnect( self.parameter_tree_changed) param.setValue(False) self.settings.sigTreeStateChanged.connect( self.parameter_tree_changed) self.preset_manager.set_PID_preset(self.settings.child('models','model_class').value()) elif param.name() == 'refresh_plot_time' or param.name() == 'timeout': self.command_pid.emit(ThreadCommand('update_timer', [param.name(),param.value()])) elif param.name() == 'set_point': if self.pid_led.state: self.command_pid.emit(ThreadCommand('update_options', dict(setpoint=param.value()))) else: output = self.model_class.convert_output(param.value(),0, stab=False) for ind_act, act in enumerate(self.actuator_modules): act.move_Abs(output[ind_act]) elif param.name() == 'sample_time': self.command_pid.emit(ThreadCommand('update_options', dict(sample_time=param.value()))) elif param.name() in custom_tree.iter_children(self.settings.child('main_settings', 'pid_controls', 'output_limits'), []): output_limits = [None, None] if self.settings.child('main_settings', 'pid_controls', 'output_limits', 'output_limit_min_enabled').value(): output_limits[0] = self.settings.child('main_settings', 'pid_controls', 'output_limits', 'output_limit_min').value() if self.settings.child('main_settings', 'pid_controls', 'output_limits', 'output_limit_max_enabled').value(): output_limits[1] = self.settings.child('main_settings', 'pid_controls', 'output_limits', 'output_limit_max').value() self.command_pid.emit(ThreadCommand('update_options', dict(output_limits=output_limits))) elif param.name() in custom_tree.iter_children(self.settings.child('main_settings', 'pid_controls', 'filter'), []): self.command_pid.emit(ThreadCommand('update_filter', [dict(enable=self.settings.child('main_settings', 'pid_controls', 'filter', 'filter_enable').value(), value=self.settings.child('main_settings', 'pid_controls', 'filter', 'filter_step').value())])) elif param.name() in custom_tree.iter_children(self.settings.child('main_settings', 'pid_controls', 'pid_constants'), []): Kp = self.settings.child('main_settings', 'pid_controls', 'pid_constants', 'kp').value() Ki = self.settings.child('main_settings', 'pid_controls', 'pid_constants', 'ki').value() Kd = self.settings.child('main_settings', 'pid_controls', 'pid_constants', 'kd').value() self.command_pid.emit(ThreadCommand('update_options', dict(tunings= (Kp, Ki, Kd)))) elif param.name() in custom_tree.iter_children(self.settings.child('models', 'model_params'),[]): self.model_class.update_settings(param) elif param.name() == 'detector_modules': self.model_class.update_detector_names() elif change == 'parent': pass @pyqtSlot(list) def thread_status(self,status): # general function to get datas/infos from all threads back to the main """ | General function to get datas/infos from all threads back to the main. | Switch the status with : * *"Update status"* : Update the status bar with the status attribute txt message """ if status[0]=="Update_Status": self.update_status(status[1],log_type=status[2])
class Ui_Window(QtWidgets.QDialog, Ui_Dialog): def __init__(self, parent=None): super(Ui_Window, self).__init__(parent=parent) self.setupUi(self) font = QtGui.QFont() font.setFamily("微软雅黑") font.setPointSize(9) self.buttonBox.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.buttonBox.button(QDialogButtonBox.Ok).setFont(font) self.buttonBox.button(QDialogButtonBox.Ok).setText("确定") self.buttonBox.button(QDialogButtonBox.Cancel).setFont(font) self.buttonBox.button(QDialogButtonBox.Cancel).setText("取消") vlayout = QtWidgets.QVBoxLayout(self) vlayout.addWidget(self.splitter) vlayout.setContentsMargins(0, 0, 10, 10) self.splitter.setGeometry(0, 0, self.width(), self.height()) self.splitter.setOrientation(Qt.Horizontal) self.splitter.setProperty("Stretch", SplitterState.collapsed) self.splitter.setProperty("Dock", Dock.right) self.splitter.setProperty("WidgetToHide", self.txt_log) self.splitter.setProperty("ExpandParentForm", True) self.splitter.setSizes([600, self.splitter.width() - 590]) self.resize(self.splitter.width(), self.splitter.height()) self.splitter.splitterMoved.connect(self.splitterMoved) self.splitter.handle(1).handleClicked.connect(self.handleClicked) self.btn_addRow.clicked.connect(self.btn_addRow_Clicked) self.btn_removeRow.clicked.connect(self.btn_removeBtn_clicked) self.rbtn_onlySpider.clicked.connect(self.rbtn_toggled) self.rbtn_onlyHandle.clicked.connect(self.rbtn_toggled) self.rbtn_spiderAndHandle.clicked.connect(self.rbtn_toggled) self.tbl_address.verticalHeader().sectionClicked.connect( self.table_section_clicked) self.btn_obtainMeta.clicked.connect(self.btn_obtainMeta_clicked) self.buttonBox.clicked.connect(self.buttonBox_clicked) self.btn_saveMetaFile.clicked.connect(self.btn_saveMetaFile_clicked) self.btn_tileInfoDialog.clicked.connect(self.open_tileInfoFile) self.btn_addressFile.clicked.connect(self.open_addressFile) self.txt_originX.editingFinished.connect(self.txt_originX_edited) self.txt_originY.editingFinished.connect(self.txt_originY_edited) self.txt_xmin.editingFinished.connect(self.txt_xmin_edited) self.txt_xmax.editingFinished.connect(self.txt_xmax_edited) self.txt_ymin.editingFinished.connect(self.txt_ymin_edited) self.txt_ymax.editingFinished.connect(self.txt_ymax_edited) self.txt_resolution.editingFinished.connect(self.txt_resolution_edited) self.txt_tilesize.editingFinished.connect(self.txt_tilesize_edited) self.tbl_address.clicked.connect(self.table_index_clicked) self.validateValue() self.paras = {} # 存储参数信息 self.selIndex = QModelIndex() self.table_init() log.setLogViewer(parent=self, logViewer=self.txt_log) self.txt_log.setReadOnly(True) # 最后运算过程放至到另一个线程避免GUI卡住 self.thread = QThread(self) self.crawlTilesThread = crawlTilesWorker() self.crawlTilesThread.moveToThread(self.thread) self.crawlTilesThread.crawl.connect(self.crawlTilesThread.crawlTiles) self.crawlTilesThread.crawlAndMerge.connect( self.crawlTilesThread.crawlAndMergeTiles) self.crawlTilesThread.finished.connect(self.threadStop) self.crawlTilesThread.merge.connect(self.crawlTilesThread.mergeTiles) def update_para_value(self, key, editor, bSel=True): value = editor.text() if bSel and self.selIndex.row() < 0: return if self.rbtn_onlyHandle.isChecked(): tileFolder_index = self.tbl_address.model().index( self.selIndex.row(), 0) tileFolder = self.tbl_address.model().data(tileFolder_index, Qt.DisplayRole) imageFile_index = self.tbl_address.model().index( self.selIndex.row(), 1) imageFile = self.tbl_address.model().data(imageFile_index, Qt.DisplayRole) para_key = tileFolder + "_" + imageFile # 以输入文件夹和输出文件作为关键字 if para_key in self.paras: self.paras[para_key][key] = value else: self.paras[para_key] = {key: value} else: url_index, level_index, url, level = self.return_url_and_level( self.selIndex.row()) if url in self.paras: if level in self.paras[url]['paras']: self.paras[url]['paras'][level][key] = value editor.home(False) def update_all_paras_value(self, oldValue, newValue, url, level): print(oldValue) if self.rbtn_onlyHandle.isChecked(): del self.paras[oldValue] self.paras[newValue] = self.update_para_dict() @Slot() def txt_originX_edited(self): self.update_para_value("origin_x", self.txt_originX) @Slot() def txt_originY_edited(self): self.update_para_value("origin_y", self.txt_originY) @Slot() def txt_xmin_edited(self): self.update_para_value("xmin", self.txt_xmin) @Slot() def txt_xmax_edited(self): self.update_para_value("xmax", self.txt_xmax) @Slot() def txt_ymin_edited(self): self.update_para_value("ymin", self.txt_ymin) @Slot() def txt_ymax_edited(self): self.update_para_value("ymax", self.txt_ymax) @Slot() def txt_resolution_edited(self): self.update_para_value("resolution", self.txt_resolution) @Slot() def txt_tilesize_edited(self): self.update_para_value("tilesize", self.txt_tilesize) def row_clicked(self, logicRow): if self.rbtn_spiderAndHandle.isChecked( ) or self.rbtn_onlySpider.isChecked(): level_index = self.tbl_address.model().index( logicRow, self.level_no, QModelIndex()) level = self.tbl_address.model().data(level_index, Qt.DisplayRole) self.update_txt_info(level_index, level) self.selIndex = level_index elif self.rbtn_onlyHandle.isChecked(): sel_index = self.tbl_address.model().index(logicRow, 0, QModelIndex()) self.update_txt_info(sel_index) self.selIndex = sel_index @Slot(int) def table_section_clicked(self, section): self.row_clicked(section) @Slot(QModelIndex) def table_index_clicked(self, index): self.row_clicked(index.row()) @Slot() def btn_saveMetaFile_clicked(self): datas = self.tbl_address.model().datas # selModel = self.tbl_address.selectionModel() # if selModel is None: # return # # selRow = -1 # paras_dict = {} # if self.selIndex is not None: # selRow = self.selIndex.row() # paras_dict = self.update_para_dict() bHasData = False for i in datas: for j in i: if j != '': bHasData = True break else: continue break rows = range(0, len(datas)) if self.rbtn_spiderAndHandle.isChecked( ) or self.rbtn_onlySpider.isChecked(): if bHasData and bool(self.paras): fileName, fileType = QFileDialog.getSaveFileName( self, "请选择保存的参数文件", os.getcwd(), "json file(*.json)") # rows = range(0, self.tbl_address.model().rowCount()) for v in self.paras.values(): v['exports'] = [] for row in rows: url = datas[row][self.url_no] if url in self.paras.keys(): level = datas[row][self.level_no] if level not in self.paras[url]['paras']: level = -1 # if row == selRow: # self.paras[url]['paras'][level] = paras_dict if self.rbtn_spiderAndHandle.isChecked(): self.paras[url]['exports'].append({ 'level': level, 'tileFolder': datas[row][2], 'imageFile': datas[row][3] }) elif self.rbtn_onlySpider.isChecked(): self.paras[url]['exports'].append({ 'level': level, 'tileFolder': datas[row][2], 'imageFile': '' }) elif self.rbtn_onlyHandle.isChecked(): if bHasData and bool(self.paras): fileName, fileType = QFileDialog.getSaveFileName( self, "请选择保存的参数文件", os.getcwd(), "json file(*.json)") for row in rows: tileFolder = datas[row][0] imageFile = datas[row][1] key = tileFolder + "_" + imageFile if key in self.paras: self.paras[key]['tileFolder'] = tileFolder self.paras[key]['imageFile'] = imageFile else: self.paras[key] = self.update_para_dict() self.paras[key]['tileFolder'] = tileFolder self.paras[key]['imageFile'] = imageFile try: if fileName != '': with open(fileName, 'w', encoding='UTF-8') as f: json.dump(self.paras, f, ensure_ascii=False) except: log.error("文件存储路径错误,无法保存!", parent=self, dialog=True) @Slot(QAbstractButton) def buttonBox_clicked(self, button: QAbstractButton): if button == self.buttonBox.button(QDialogButtonBox.Ok): if not self.check_paras(): return self.thread.start() self.run_process() elif button == self.buttonBox.button(QDialogButtonBox.Cancel): self.close() def run_process(self): rows = range(0, self.tbl_address.model().rowCount(QModelIndex())) for row in rows: url_index, level_index, url, level = self.return_url_and_level(row) if self.rbtn_spiderAndHandle.isChecked(): paras = self.paras[url]['paras'][level] tileFolder_index = self.tbl_address.model().index( row, 2, QModelIndex()) imgFile_index = self.tbl_address.model().index( row, 3, QModelIndex()) tileFolder = str(self.tbl_address.model().data( tileFolder_index, Qt.DisplayRole)).strip() imgFile = str(self.tbl_address.model().data( imgFile_index, Qt.DisplayRole)).strip() if tileFolder == "": tileFolder = defaultTileFolder(url, level) log.warning('第{}行参数缺失非必要参数"瓦片文件夹",将使用默认值"{}".'.format( row + 1, tileFolder)) else: url_encodeStr = urlEncodeToFileName(url) tileFolder = os.path.join(tileFolder, url_encodeStr, str(level)) if not os.path.exists(tileFolder): os.makedirs(tileFolder) if imgFile == "": imgFile = defaultImageFile(url, level) log.warning('第{}行参数缺失非必要参数"输出影像文件",将使用默认值"{}".'.format( row + 1, imgFile)) self.crawlTilesThread.crawlAndMerge.emit( url, int(level), int(paras['origin_x']), int(paras['origin_y']), float(paras['xmin']), float(paras['xmax']), float(paras['ymin']), float(paras['ymax']), float(paras['resolution']), int(paras['tilesize']), tileFolder, imgFile) elif self.rbtn_onlySpider.isChecked(): paras = self.paras[url]['paras'][level] tileFolder_index = self.tbl_address.model().index( row, 2, QModelIndex()) tileFolder = str(self.tbl_address.model().data( tileFolder_index, Qt.DisplayRole)).strip() if tileFolder == "": tileFolder = defaultTileFolder(url, level) log.warning('第{}行参数缺失非必要参数"瓦片文件夹",将使用默认值"{}".'.format( row + 1, tileFolder)) else: url_encodeStr = urlEncodeToFileName(url) tileFolder = os.path.join(tileFolder, url_encodeStr, str(level)) if not os.path.exists(tileFolder): os.makedirs(tileFolder) self.crawlTilesThread.crawl.emit(url, int(level), int(paras['origin_x']), int(paras['origin_y']), float(paras['xmin']), float(paras['xmax']), float(paras['ymin']), float(paras['ymax']), float(paras['resolution']), int(paras['tilesize']), tileFolder) elif self.rbtn_onlyHandle.isChecked(): key = url + "_" + level paras = self.paras[key] imgFile_index = self.tbl_address.model().index( row, 1, QModelIndex()) imgFile = str(self.tbl_address.model().data( imgFile_index, Qt.DisplayRole)).strip() if imgFile == "": imgFile = defaultImageFile(url, level) log.warning('第{}行参数缺失非必要参数"输出影像文件",将使用默认值"{}".'.format( row + 1, imgFile)) self.crawlTilesThread.merge.emit(url, int(paras['origin_x']), int(paras['origin_y']), float(paras['xmin']), float(paras['xmax']), float(paras['ymin']), float(paras['ymax']), float(paras['resolution']), int(paras['tilesize']), imgFile) def check_paras(self): rows = range(0, self.tbl_address.model().rowCount(QModelIndex())) for row in rows: url_index, level_index, url, level = self.return_url_and_level(row) if self.rbtn_spiderAndHandle.isChecked( ) or self.rbtn_onlySpider.isChecked(): if url == "": log.error('第{}行缺失必要参数"地址",请补全!'.format(row), dialog=True) return False if level == "": log.error('第{}行缺失必要参数"等级",请补全!'.format(row), dialog=True) return False elif self.rbtn_onlyHandle.isChecked(): if url == "": log.error('第{}行缺失必要参数"瓦片文件夹",请补全!'.format(row), dialog=True) return False return True def threadStop(self): self.thread.quit() def table_init(self): self.tbl_address.setStyle(mTableStyle()) self.tbl_address.horizontalHeader().setStretchLastSection(True) self.tbl_address.verticalHeader().setDefaultSectionSize(20) self.tbl_address.verticalHeader().setSectionResizeMode( QHeaderView.Fixed) # 行高固定 color = self.palette().color(QPalette.Button) self.tbl_address.horizontalHeader().setStyleSheet( "QHeaderView::section {{ background-color: {}}}".format( color.name())) self.tbl_address.verticalHeader().setStyleSheet( "QHeaderView::section {{ background-color: {}}}".format( color.name())) self.tbl_address.setStyleSheet( "QTableCornerButton::section {{ color: {}; border: 1px solid; border-color: {}}}" .format(color.name(), color.name())) self.tbl_address.setSelectionMode(QAbstractItemView.ExtendedSelection) self.tbl_address.setEditTriggers(QAbstractItemView.SelectedClicked | QAbstractItemView.DoubleClicked) self.tbl_address.DragDropMode(QAbstractItemView.InternalMove) self.tbl_address.setSelectionBehavior(QAbstractItemView.SelectRows | QAbstractItemView.SelectItems) self.tbl_address.setDefaultDropAction(Qt.MoveAction) self.tbl_address.horizontalHeader().setSectionsMovable(False) self.tbl_address.setDragEnabled(True) self.tbl_address.setAcceptDrops(True) self.level_no = 1 # 等级字段的序号 self.url_no = 0 # url地址的序号 # self.rbtn_spiderAndHandle.setChecked(True) def showEvent(self, a0: QtGui.QShowEvent) -> None: log.setLogViewer(parent=self, logViewer=self.txt_log) self.rbtn_spiderAndHandle.click() def resizeEvent(self, a0: QtGui.QResizeEvent) -> None: self.table_layout() def splitterMoved(self): self.table_layout() def handleClicked(self): self.table_layout() def table_layout(self): if self.rbtn_onlyHandle.isChecked(): self.tbl_address.setColumnWidth(0, self.tbl_address.width() / 2) self.tbl_address.setColumnWidth(1, self.tbl_address.width() / 2) elif self.rbtn_onlySpider.isChecked(): self.tbl_address.setColumnWidth(0, self.tbl_address.width() * 0.4) self.tbl_address.setColumnWidth(1, self.tbl_address.width() * 0.2) self.tbl_address.setColumnWidth(2, self.tbl_address.width() * 0.4) elif self.rbtn_spiderAndHandle.isChecked(): self.tbl_address.setColumnWidth(0, self.tbl_address.width() * 0.4) self.tbl_address.setColumnWidth(1, self.tbl_address.width() * 0.2) self.tbl_address.setColumnWidth(2, self.tbl_address.width() * 0.2) self.tbl_address.setColumnWidth(3, self.tbl_address.width() * 0.2) def return_url_and_level(self, logicRow): url_index = self.tbl_address.model().index(logicRow, self.url_no) level_index = self.tbl_address.model().index(logicRow, self.level_no) url = self.tbl_address.model().data(url_index, Qt.DisplayRole) level = self.tbl_address.model().data(level_index, Qt.DisplayRole) return url_index, level_index, url, level @Slot() def btn_addRow_Clicked(self): selModel = self.tbl_address.selectionModel() if selModel is None: return if len(selModel.selectedIndexes()) == 0: self.model.addEmptyRow(self.model.rowCount(QModelIndex()), 1, 0) next_index = self.model.index( self.model.rowCount(QModelIndex()) - 1, 0) elif len(selModel.selectedRows()) == 1: next_row = selModel.selectedRows()[0].row() + 1 self.model.addEmptyRow(next_row, 1, 0) next_index = self.model.index(next_row, 0) selModel.select( next_index, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) self.tbl_address.setFocus() def btn_removeBtn_clicked(self): index_list = [] selModel = self.tbl_address.selectionModel() if selModel is None: return if len(selModel.selectedRows()) < 1: return for model_index in selModel.selectedRows(): index = QPersistentModelIndex(model_index) index_list.append(index) oldrow = index.row() for index in index_list: self.model.removeRows(index.row(), 1, 0) if self.model.rowCount(QModelIndex()) == 1: next_index = self.model.index(0, 0) else: next_index = self.model.index(oldrow, 0) # next_index = self.model.index(self.model.rowCount(QModelIndex()) - 1, 0) selModel.select( next_index, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) self.tbl_address.setFocus() def validateValue(self): doubleValidator = QDoubleValidator() doubleValidator.setNotation(QDoubleValidator.StandardNotation) self.txt_originX.setValidator(doubleValidator) self.txt_originY.setValidator(doubleValidator) self.txt_xmin.setValidator(doubleValidator) self.txt_xmax.setValidator(doubleValidator) self.txt_ymin.setValidator(doubleValidator) self.txt_ymax.setValidator(doubleValidator) self.txt_tilesize.setValidator(doubleValidator) self.txt_resolution.setValidator(doubleValidator) integerValidator = QIntValidator(0, 99) self.txt_level.setValidator(integerValidator) def rbtn_toggled(self, btn): self.model = TableModel() self.txt_originX.setText("") self.txt_originY.setText("") self.txt_xmin.setText("") self.txt_xmax.setText("") self.txt_ymin.setText("") self.txt_ymax.setText("") self.txt_tilesize.setText("") self.txt_resolution.setText("") self.txt_level.setText("") if self.rbtn_onlyHandle.isChecked(): self.table_init_onlyHandle() else: self.txt_addressFile.setEnabled(True) self.btn_addressFile.setEnabled(True) self.txt_tileInfoFile.setEnabled(False) self.btn_tileInfoDialog.setEnabled(False) self.txt_tileInfoFile.clear() self.txt_originX.setEnabled(False) self.txt_originY.setEnabled(False) self.txt_resolution.setEnabled(False) self.txt_tilesize.setEnabled(False) self.txt_level.setEnabled(False) self.btn_obtainMeta.setEnabled(True) self.model.setHeaderData(0, Qt.Horizontal, "地址", Qt.DisplayRole) if self.rbtn_onlySpider.isChecked(): self.table_init_onlySpider() elif self.rbtn_spiderAndHandle.isChecked(): self.table_init_spiderAndHandle() def table_init_onlyHandle(self): self.txt_addressFile.setEnabled(False) self.btn_addressFile.setEnabled(False) self.txt_addressFile.clear() self.txt_tileInfoFile.setEnabled(True) self.btn_tileInfoDialog.setEnabled(True) self.txt_originX.setEnabled(True) self.txt_originY.setEnabled(True) self.txt_resolution.setEnabled(True) self.txt_tilesize.setEnabled(True) self.txt_level.setEnabled(False) self.btn_obtainMeta.setEnabled(False) self.model.setHeaderData(0, Qt.Horizontal, "输入瓦片文件夹", Qt.DisplayRole) # self.model.setHeaderData(1, Qt.Horizontal, "参数文件", Qt.DisplayRole) self.model.setHeaderData(1, Qt.Horizontal, "输出影像文件", Qt.DisplayRole) delegate = addressTableDelegate(self, [{ 'text': "请选择瓦片文件夹", 'type': "d" }, { 'text': "请选择或创建影像文件", 'type': "f" }]) self.tbl_address.setModel(self.model) self.tbl_address.setItemDelegate(delegate) self.tbl_address.setColumnWidth(0, self.tbl_address.width() / 2) self.tbl_address.setColumnWidth(1, self.tbl_address.width() / 2) self.paras = {} def table_init_onlySpider(self): self.model.setHeaderData(1, Qt.Horizontal, "等级", Qt.DisplayRole) self.model.setHeaderData(2, Qt.Horizontal, "瓦片文件夹", Qt.DisplayRole) delegate = addressTableDelegate( self, [None, { 'type': 'c' }, { 'text': "请选择输出瓦片文件夹", 'type': "d" }]) self.tbl_address.setModel(self.model) self.tbl_address.setItemDelegate(delegate) self.tbl_address.setColumnWidth(0, self.tbl_address.width() * 0.4) self.tbl_address.setColumnWidth(1, self.tbl_address.width() * 0.2) self.tbl_address.setColumnWidth(2, self.tbl_address.width() * 0.4) def table_init_spiderAndHandle(self): self.model.setHeaderData(1, Qt.Horizontal, "等级", Qt.DisplayRole) self.model.setHeaderData(2, Qt.Horizontal, "瓦片文件夹", Qt.DisplayRole) self.model.setHeaderData(3, Qt.Horizontal, "输出影像文件", Qt.DisplayRole) delegate = addressTableDelegate(self, [ None, { 'type': 'c' }, { 'text': "请选择输出瓦片文件夹", 'type': "d" }, { 'text': "请选择输出影像文件", 'type': "f" } ]) self.tbl_address.setModel(self.model) self.tbl_address.setItemDelegate(delegate) self.tbl_address.setColumnWidth(0, self.tbl_address.width() * 0.4) self.tbl_address.setColumnWidth(1, self.tbl_address.width() * 0.2) self.tbl_address.setColumnWidth(2, self.tbl_address.width() * 0.2) self.tbl_address.setColumnWidth(3, self.tbl_address.width() * 0.2) self.paras = {} @Slot() def open_addressFile(self): fileName, fileType = QtWidgets.QFileDialog.getOpenFileName( self, "选择影像服务参数文件", os.getcwd(), "All Files(*)") self.txt_addressFile.setText(fileName) if fileName == "": return try: with open(fileName, 'r', encoding='utf-8') as f: self.paras = json.load(f) self.add_address_rows_from_paras() except: if self.model.rowCount(QModelIndex()) > 0: self.model.removeRows( self.model.rowCount(QModelIndex()) - 1, 1, QModelIndex()) log.error("读取参数文件失败!", dialog=True) def add_address_rows_from_paras(self): for kv in self.paras.items(): url = kv[0] v = kv[1] imp = v['exports'] levels = v['levels'] for i in range(len(imp)): row = self.model.rowCount(QModelIndex()) self.model.addEmptyRow(self.model.rowCount(QModelIndex()), 1, 0) url_index = self.tbl_address.model().index(row, self.url_no) level_index = self.tbl_address.model().index( row, self.level_no) self.tbl_address.model().setData(url_index, url) if imp[i]['level'] == -1: self.tbl_address.model().setData(level_index, "") else: self.tbl_address.model().setData(level_index, imp[i]['level']) self.tbl_address.model().setData( self.tbl_address.model().index(row, 2), imp[i]['tileFolder']) if self.rbtn_spiderAndHandle.isChecked(): self.tbl_address.model().setData( self.tbl_address.model().index(row, 3), imp[i]['imageFile']) editor_delegate = self.tbl_address.itemDelegate(level_index) if isinstance(editor_delegate, addressTableDelegate): # self.model.setLevelData(level_index, levels) self.model.setLevelData(url, levels) @Slot() def open_tileInfoFile(self): fileName, fileType = QtWidgets.QFileDialog.getOpenFileName( self, "选择瓦片参数文件", os.getcwd(), "All Files(*)") self.txt_tileInfoFile.setText(fileName) if fileName == "": return try: with open(fileName, 'r') as f: self.paras = json.load(f) for kv in self.paras.items(): imp = kv[1] row = self.model.rowCount(QModelIndex()) self.model.addEmptyRow(self.model.rowCount(QModelIndex()), 1, 0) self.tbl_address.model().setData( self.tbl_address.model().index(row, 0), imp['tileFolder']) self.tbl_address.model().setData( self.tbl_address.model().index(row, 1), imp['imageFile']) except: log.error("读取参数文件失败!", dialog=True) @Slot() def btn_obtainMeta_clicked(self): selModel = self.tbl_address.selectionModel() if selModel is None: return indexes = selModel.selectedIndexes() if self.rbtn_onlyHandle.isChecked(): return ## 如果有被选中的行,则只获取被选中行的信息 if len(indexes) > 0: rows = sorted( set(index.row() for index in self.tbl_address.selectedIndexes())) else: rows = range(0, self.tbl_address.model().rowCount(QModelIndex())) for row in rows: url_index, level_index, url, level = self.return_url_and_level(row) editor_delegate = self.tbl_address.itemDelegate(level_index) url = str(self.tbl_address.model().data(url_index, Qt.DisplayRole)).strip() if url == "": continue # if url not in self.paras.keys(): getInfo = get_paraInfo(url) if getInfo is None: log.error(url + "无法获取远程参数信息,请检查地址是否正确以及网络是否连通!") continue else: log.info(url + "参数信息获取成功!") self.setParaToMemory(url, getInfo) levels = self.paras[url]['levels'] if isinstance(editor_delegate, addressTableDelegate): # self.model.setLevelData(level_index, levels) self.model.setLevelData(url, levels) def setParaToMemory(self, url, getInfo): levels = [] resolutions = [] lods = [] origin_x = origin_y = xmin = xmax = ymin = ymax = resolution = tilesize = level = "" if 'tileInfo' in getInfo.keys(): if 'origin' in getInfo['tileInfo'].keys(): if 'x' in getInfo['tileInfo']['origin'].keys(): origin_x = getInfo['tileInfo']['origin']['x'] if 'y' in getInfo['tileInfo']['origin'].keys(): origin_y = getInfo['tileInfo']['origin']['y'] if 'lods' in getInfo['tileInfo'].keys(): lods = getInfo['tileInfo']['lods'] if 'extent' in getInfo.keys(): if 'xmin' in getInfo['extent']: xmin = getInfo['extent']['xmin'] if 'xmax' in getInfo['extent']: xmax = getInfo['extent']['xmax'] if 'ymin' in getInfo['extent']: ymin = getInfo['extent']['ymin'] if 'ymax' in getInfo['extent']: ymax = getInfo['extent']['ymax'] if 'tileInfo' in getInfo.keys(): if 'rows' in getInfo['tileInfo']: tilesize = getInfo['tileInfo']['rows'] paras = {} for lod in lods: if 'level' in lod.keys(): level = lod['level'] levels.append(level) if 'resolution' in lod.keys(): resolution = lod['resolution'] resolutions.append(resolution) paras[str(level)] = { 'origin_x': origin_x, 'origin_y': origin_y, 'xmin': xmin, 'xmax': xmax, 'ymin': ymin, 'ymax': ymax, 'tilesize': tilesize, 'resolution': resolution } url_encodeStr = urlEncodeToFileName(url) self.paras[url] = { 'code': url_encodeStr, 'levels': levels, 'paras': paras } def update_para_dict(self): dict = { "origin_x": self.txt_originX.text(), "origin_y": self.txt_originY.text(), "xmin": self.txt_xmin.text(), "xmax": self.txt_xmax.text(), "ymin": self.txt_ymin.text(), "ymax": self.txt_ymax.text(), "tilesize": self.txt_tilesize.text(), "resolution": self.txt_resolution.text() } return dict def update_txt_info(self, index: QModelIndex, level=-1): # print([index.row(), index.column()]) key1_index = self.tbl_address.model().index(index.row(), 0) key1 = self.tbl_address.model().data(key1_index, Qt.DisplayRole) key2_index = self.tbl_address.model().index(index.row(), 1) key2 = self.tbl_address.model().data(key2_index, Qt.DisplayRole) self.txt_level.setText("") self.txt_originX.setText("") self.txt_originY.setText("") self.txt_xmin.setText("") self.txt_xmax.setText("") self.txt_ymin.setText("") self.txt_ymax.setText("") self.txt_tilesize.setText("") self.txt_resolution.setText("") bUpdate = False if self.rbtn_spiderAndHandle.isChecked( ) or self.rbtn_onlySpider.isChecked(): if key1 not in self.paras: return if level in self.paras[key1]['paras']: getInfo = self.paras[key1]['paras'][level] self.txt_level.setText(str(level)) bUpdate = True else: key = key1 + "_" + key2 if key in self.paras: getInfo = self.paras[key] bUpdate = True else: bUpdate = False if bUpdate: if 'origin_x' in getInfo: self.txt_originX.setText(str(getInfo['origin_x'])) self.txt_originX.home(False) if 'origin_y' in getInfo: self.txt_originY.setText(str(getInfo['origin_y'])) self.txt_originY.home(False) if 'xmin' in getInfo: self.txt_xmin.setText(str(getInfo['xmin'])) self.txt_xmin.home(False) if 'xmax' in getInfo: self.txt_xmax.setText(str(getInfo['xmax'])) self.txt_xmax.home(False) if 'ymin' in getInfo: self.txt_ymin.setText(str(getInfo['ymin'])) self.txt_ymin.home(False) if 'ymax' in getInfo: self.txt_ymax.setText(str(getInfo['ymax'])) self.txt_ymax.home(False) if 'tilesize' in getInfo: self.txt_tilesize.setText(str(getInfo['tilesize'])) self.txt_tilesize.home(False) if 'resolution' in getInfo: self.txt_resolution.setText(str(getInfo['resolution'])) self.txt_resolution.home(False)
class BusyLoaderDialog(QDialog): """ This class implements a loader showing a BusyProgressBar indicator. """ def __init__(self, worker, parent=None): """ Initialize the form dialog. :type worker: Loader :type parent: QWidget """ super().__init__(parent) self.worker = worker self.workerThread = None self.progressBar = QProgressBar(self) self.progressBar.setAlignment(Qt.AlignHCenter) self.progressBar.setRange(-1, -1) self.mainLayout = QVBoxLayout(self) self.mainLayout.addWidget(self.progressBar) self.setWindowTitle('Loading ...') self.setWindowIcon(QIcon(':/images/eddy')) self.setFixedSize(300, 100) #################################################################################################################### # # # EVENTS # # # #################################################################################################################### def showEvent(self, event): """ Executed when the dialog is shown :type event: QShowEvent """ self.workerThread = QThread() self.worker.moveToThread(self.workerThread) connect(self.worker.completed, self.completed) connect(self.worker.errored, self.errored) connect(self.workerThread.started, self.worker.work) self.workerThread.start() #################################################################################################################### # # # SLOTS # # # #################################################################################################################### @pyqtSlot(Exception) def errored(self, err): """ Executed whenever the translation errors. :type err: Exception """ msgbox = QMessageBox(self) msgbox.setIconPixmap(QPixmap(':/icons/error')) msgbox.setWindowIcon(QIcon(':/images/eddy')) msgbox.setWindowTitle('Load failed!') msgbox.setStandardButtons(QMessageBox.Close) msgbox.setText('Failed to load {}!'.format(os.path.basename(self.worker.filepath))) msgbox.setDetailedText(''.join(traceback.format_exception(type(err), err, err.__traceback__))) msgbox.exec_() self.workerThread.quit() self.reject() @pyqtSlot() def completed(self): """ Executed whenever the translation completes. """ self.workerThread.quit() self.worker.moveToThread(QApplication.instance().thread()) self.accept()