class Example(QObject): def __init__(self, parent=None): super(self.__class__, self).__init__(parent) self.gui = Window() self.worker = Worker() # 백그라운드에서 돌아갈 인스턴스 소환 self.worker_thread = QThread() # 따로 돌아갈 thread를 하나 생성 self.worker.moveToThread(self.worker_thread) # worker를 만들어둔 쓰레드에 넣어줍니다 self.worker_thread.start() # 쓰레드를 실행합니다. self._connectSignals() # 시그널을 연결하기 위한 함수를 호출 self.gui.show() # 시그널을 연결하기 위한 func. def _connectSignals(self): # gui의 버튼을 클릭 시 연결설정 self.gui.button_start.clicked.connect(self.worker.startWork) # worker에서 발생한 signal(sig_numbers) 의 연결 설정 self.worker.sig_numbers.connect(self.gui.updateStatus) # cancel 버튼 연결 설정 self.gui.button_cancel.clicked.connect(self.forceWorkerReset) # 쓰레드의 loop를 중단하고 다시 처음으로 대기 시키는 func. def forceWorkerReset(self): if self.worker_thread.isRunning(): # 쓰레드가 돌아가고 있다면 self.worker_thread.terminate() # 현재 돌아가는 thread를 중지시킨다 self.worker_thread.wait() # 새롭게 thread를 대기한 후 self.worker_thread.start() # 다시 처음부터 시작
class MainWin(QMainWindow): def __init__(self, parent=None): QMainWindow.__init__(self,parent) self.window = uic.loadUi(main_path,self) # 메인창 만들기, 불러오기 self는 왜잇냐 self.window.show() # 쓰레드 생성 및 연결 self.worker = Worker() # back 에서 구동될 쓰레드 self.threadObj = QThread() # 쓰레드 객체 생성 self.worker.moveToThread(self.threadObj) # 가동할 쓰레드 - 쓰레드 객체를 연결 self.connectionSignal() # signal 연결 작업 self.threadObj.start() # 쓰레드 가동 #쓰레드 중단 버튼을 누르면 호출된다. ? def connectionSignal(self): # 쓰레드 가동 버튼을 누르면 쓰레드가 시작. self.window.pushButton.clicked.connect(self.worker.start) # 쓰레드가 돌면서 이벤트를 발생하는데 받을 콜백함수 연결 self.worker.signal_obj.connect(self.window.recvCnt)# 서로 다른 클래스 끼리 연결 # 이벤트 이름이 없다. #쓰레드가 보내는 숫자값 받는 콜백함수 #여기는 두개가 있다. signal이 (시작, 종료) @pyqtSlot(int) def recvCnt(self, cnt): self.window.progressBar.setValue(cnt) # 받아서 화면을 바꿔준다. @pyqtSlot() def onThreadStop(self):# 이미 연결시켜놓음(디자이너에서) if self.threadObj.isRunning(): self.threadObj.terminate()
class ModDownloaderWindow(QWidget): def __init__(self, file: CurseFile, curse: CurseAPI, instance, initmods): super().__init__() self.initmods = initmods self.setWindowTitle(translate("downloading.mod").format(file.name)) self.layout = QVBoxLayout(self) self.progress = QProgressBar() self.layout.addWidget(self.progress) self.show() self.downloader = ModDownloaderThread(file, curse, instance) self.downloader.done.connect(self.download_done) self.downloader.update.connect(self.progress.setValue) self.download_thread = QThread() self.downloader.moveToThread(self.download_thread) self.download_thread.started.connect(self.downloader.download) self.download_thread.start() def download_done(self): self.downloader.terminate() self.download_thread.terminate() self.initmods() self.close() self.destroy()
class Example(QObject): def __init__(self, parent=None): super(self.__class__, self).__init__(parent) self.gui = Window() self.worker = Worker() # 백그라운드에서 돌아갈 인스턴스 소환 self.worker_thread = QThread() # 따로 돌아갈 thread를 하나 생성 self.worker.moveToThread(self.worker_thread) # worker를 만들어둔 쓰레드에 넣어줍니다 self.worker_thread.start() # 쓰레드를 실행합니다. self._connectSignals( ) # 시그널을 연결하기 위한 함수를 호출[출처] [Python3] PyQt5 | QThread-pyqtSignal/Slot Example|작성자 우리동네약사 self.gui.show() def _connectSignals(self): # // gui 의 버튼을 클릭시 연결설정 self.gui.button_start.clicked.connect(self.worker.startWork) # // worker에서 발생한 signal(sig_numbers)의 연결 설정 self.worker.sig_numbers.connect(self.gui.updateStatus) # // cancel 버튼 연결 설정 self.gui.button_cancel.clicked.connect(self.forceWorkerReset) # // 쓰레드의 loop를 중단하고 다시 처음으로 대기 시키는 func. def forceWorkerReset(self): if self.worker_thread.isRunning(): self.worker_thread.terminate() self.worker_thread.wait() self.worker_thread.start()
def mp_thread_callback(self, thread: QThread, ret): if isinstance(ret, Exception): print(ret) self.show_critical_error('Error contacting central market') thread.wait(2000) if thread.isRunning(): thread.terminate() self.mp_threads.remove(thread)
class DownloadDialog(QDialog, Ui_DownloadDialog): def __init__(self, url, filePath, threadno, totalSize, parent=None): super().__init__(parent) self.setupUi(self) self.setWindowTitle(os.path.split(filePath)[1]) self.url = url self.filePath = filePath self.threadno = threadno self.totalSize = totalSize self.downloadTask = None self.workerThread = QThread(self) self.workerThread.started.connect(self.notifyStarted) self.workerThread.finished.connect(self.notifyFinished) self.mStopPushButton.clicked.connect(self.stopProcess) self.mCancelPushButton.clicked.connect(self.cancelProcess) self.toggleDownloadTask() @pyqtSlot(int) def setProgress(self, v): self.mProgressBar.setValue(v) if v == self.totalSize: self.workerThread.quit() self.workerThread.wait() def toggleDownloadTask(self): self.mProgressBar.setMaximum(self.totalSize) self.downloadTask = DownloadTask(self.url, self.filePath, self.totalSize, self.threadno) self.downloadTask.progressed.connect(self.setProgress) self.workerThread.started.connect(self.downloadTask.startDownload) self.workerThread.finished.connect(self.downloadTask.deleteLater) self.downloadTask.moveToThread(self.workerThread) self.workerThread.start() @pyqtSlot() def stopProcess(self): logger.info("stop task of downloading {} finished.".format(self.url)) if self.workerThread.isRunning(): self.workerThread.terminate() self.workerThread.wait() self.mStopPushButton.setText("Start") else: self.mStopPushButton.setText("Stop") @pyqtSlot() def cancelProcess(self): logger.info("cancel task of downloading {} finished.".format(self.url)) if self.workerThread.isRunning(): self.workerThread.terminate() self.workerThread.wait() @pyqtSlot() def notifyStarted(self): pass @pyqtSlot() def notifyFinished(self): logger.info("finished download {}".format(self.url))
class AppDialog(QtWidgets.QDialog): def __init__(self, parent=None): super(AppDialog, self).__init__(parent) self.setWindowTitle("PyQt5 -- QThread app") ui_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'qthread.ui') self.ui_widget = uic.loadUi(ui_file, self) self.ui_widget.buttonBox.accepted.connect(self.on_accept) self.ui_widget.buttonBox.rejected.connect(self.on_reject) self.ui_widget.pushButton_start.clicked.connect(self.on_click_start) self.ui_widget.pushButton_stop.clicked.connect(self.on_click_stop) self.ui_widget.progressBar.setValue(0) print("") self.thread_qt = QThread() def on_update_from_helper(self, tick): self.ui_widget.progressBar.setValue(tick) def on_result_from_helper(self, tick): self.ui_widget.progressBar.setValue(tick) print("Thread is completed") @pyqtSlot() def on_click_start(self): if self.thread_qt.isRunning(): print("Thread is already running") else: print("Kicked off thread") self.helper_impl = helperThreadImpl(min=0, max=100) self.thread_qt.started.connect(self.helper_impl.start) self.helper_impl.sig_update.connect(self.on_update_from_helper) self.helper_impl.sig_result.connect(self.on_result_from_helper) self.helper_impl.moveToThread(self.thread_qt) self.thread_qt.start() @pyqtSlot() def on_click_stop(self): if self.thread_qt.isRunning(): print("Ask thread to temrinate...") self.thread_qt.terminate() # It takes time, as per docs it depends on underlying OS self.thread_qt.wait() print("Thread is terminated") else: print("Thread is not active") def on_accept(self): print("on_accept") def on_reject(self): print("on_reject")
def train_start(self, signal): print("Your computer's Ideal Thread Number :", QThread.idealThreadCount()) if signal is 'start': self.thread = Worker(self.parent, 1) self.thread.start() # run() else: QThread.terminate() self.thread.terminate() self.thread.quit()
class CameraSync(QObject): def __init__(self): super().__init__() self.shutdown = False self.items = dict() self.lock = threading.Lock() self.thread = QThread() self.moveToThread(self.thread) self.thread.started.connect(self.run) self.thread.start() def __del__(self): self.shutdown = True if not self.thread.wait(100): self.thread.terminate() self.thread = None def add(self, id, obj): with self.lock: self.items[id] = CameraStreamObject(id, obj) def remove(self, id): with self.lock: if id in self.items: obj = self.items.pop(id) obj.closeStream() def record(self, id): with self.lock: if id in self.items: self.items[id].rec = not self.items[id].rec def refresh(self, id): with self.lock: if id in self.items: self.items[id].refresh = True def run(self): global THREADING_SHUTDOWN, THREADING_EVENTS, THREADING_SUSPEND_TIME frames = dict() while not THREADING_SHUTDOWN and not self.shutdown: with self.lock: for id, obj in self.items.items(): frame = obj.render() if frame: frames[id] = frame # Send pixmap to ui thread, suspend otherwise, if nothing else to do! THREADING_EVENTS.dispatchPixmapEvent(frames.copy( )) if len(frames) > 0 else time.sleep(THREADING_SUSPEND_TIME) frames.clear() self.items.clear()
def run(self): if self._status == 1: self.train_thread = QThread.currentThreadId() self.parent.train_widget.train_command() elif self._status == 2: self.parent.train_widget.show_tensorboard() elif self._status == 3: self.parent.train_widget.start_chrome() elif self._status == 4: self.parent.show_widget.view_loss() elif self._status == 5: self.parent.validation_widget.run_val() else: print('terminate') QThread.terminate()
class Gui(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): # Buttons: self.btn_start = QPushButton('Start') self.btn_start.resize(self.btn_start.sizeHint()) self.btn_start.move(50, 50) self.btn_stop = QPushButton('Stop') self.btn_stop.resize(self.btn_stop.sizeHint()) self.btn_stop.move(150, 50) # GUI title, size, etc... self.setGeometry(300, 300, 300, 220) self.setWindowTitle('ThreadTest') self.layout = QGridLayout() self.layout.addWidget(self.btn_start, 0, 0) self.layout.addWidget(self.btn_stop, 0, 50) self.setLayout(self.layout) # Thread: self.thread = QThread() self.worker = Worker() self.worker.moveToThread(self.thread) self.thread.started.connect( self.worker.do_work) # when thread starts, start worker self.thread.finished.connect( self.worker.stop) # when thread finishes, stop worker # Start Button action: self.btn_start.clicked.connect(self.thread.start) # Stop Button action: self.btn_stop.clicked.connect(self.stop_thread) self.show() # When stop_btn is clicked this runs. Terminates the worker and the thread. def stop_thread(self): print("It should stop printing numbers now and not crash") self.worker.disconnect() self.thread.terminate()
class MicrophoneListener(QObject): IN = pyqtSignal(object) OUT = pyqtSignal() @pyqtSlot(object) def receive_data(self, data): logger.info("{} received data of size {}".format( self, data.shape)) def start(self): self._thread = QThread(self) self.moveToThread(self._thread) self._thread.start() self.IN.connect(self.receive_data) def stop(self): self._thread.terminate()
class PackDownloaderWindow(QWidget): def __init__(self, file: CurseFile, curse: CurseAPI, pack: CurseModpack): super().__init__() self.setWindowTitle( translate("downloading.pack").format(pack.project.title)) self.layout = QVBoxLayout(self) self.label = QLabel() self.layout.addWidget(self.label) self.progress = QProgressBar() self.layout.addWidget(self.progress) self.prog2 = QProgressBar() self.layout.addWidget(self.prog2) self.show() self.downloader = PackDownloaderThread(file, curse, pack) self.downloader.done.connect(self.download_done) self.downloader.bar1.connect(self.progress.setValue) self.downloader.bar2.connect(self.prog2.setValue) self.downloader.setLabel.connect(self.label.setText) self.download_thread = QThread() self.downloader.moveToThread(self.download_thread) self.download_thread.started.connect(self.downloader.download) self.download_thread.start() def download_done(self): self.downloader.terminate() self.download_thread.terminate() self.close() self.destroy()
def cmdFSUpdateMP_callback(self, thread: QThread, ret): model = self.enh_model frmMain = self.frmMain idx_NAME = self.HEADERS.index(HEADER_NAME) if isinstance(ret, Exception): print(ret) frmMain.sig_show_message.emit(frmMain.CRITICAL, 'Error contacting central market') else: settings = model.settings item_store: ItemStore = settings[settings.P_ITEM_STORE] with QBlockSig(self): for i in range(self.rowCount()): this_gear: Gear = self.cellWidget(i, idx_NAME).gear self.fs_gear_set_costs(this_gear, item_store, i) frmMain.sig_show_message.emit(frmMain.REGULAR, 'Fail stacking prices updated') thread.wait(2000) if thread.isRunning(): thread.terminate() self.mp_threads.remove(thread)
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): def __init__(self, parent=None): super(MainWindow, self).__init__() self.initUi() def initUi(self): pg.setConfigOption('background', 'w') # before loading widget self.setupUi(self) self.setWindowTitle("GIAS Recorder") self.setWindowIcon(QtGui.QIcon(appctxt.get_resource('GIAS_icon.png'))) self.StartButton.clicked.connect(self.start) self.StartButton.setIcon(QtGui.QIcon(appctxt.get_resource('Rec_button.png'))) self.SettingsButton.clicked.connect(self.open_settings) self.StopButton.clicked.connect(self.stop) self.progressBar.setMaximum(32768) pg.setConfigOptions(antialias=True) self.rec_settings = Settings() self.BIPRange = 0 self.srate = 500000 self.CHUNKSZ = 1024 # int(self.srate/1000) self.plot_iter = 0 self.setupThreads() self.open_settings() self.showMaximized() # app.aboutToQuit.connect(self.forceWorkerQuit) def open_settings(self): self.rec_settings.exec_() self.BIPRange = self.rec_settings.Range.currentIndex() self.srate = int(self.rec_settings.SampleRate.currentText())*10**3 self.CHUNKSZ = 1024 # int(self.srate/1000) self.set_plot_props() self.forceWorkerReset() def set_plot_props(self): # TODO: cehck for vispy module # Time Plot Properties # self.win = self.graphicsView ############## test 1 PLotWidget ############################################### # self.win.plotItem.setRange( # xRange=[0, self.CHUNKSZ], yRange=[-35000, 35000], padding=0, # disableAutoRange=True) # self.win.setMouseEnabled(x=True, y=False) # self.win.showGrid(True, True, 0.5) # self.win.setLabel('bottom', 'Time', units='s') # self.win.setLabel('left', 'Amplitude', units='counts') ############## test 2 GraphicsLayoutWidget #################################### # self.win.clear() # self.data1 = np.zeros(2*self.CHUNKSZ) # self.plot_index = 0 # self.p1 = self.win.addPlot(row=0, col=1) # self.p1.setDownsampling(mode='peak') # self.p1.setClipToView(True) # self.p1.setLimits(xMin=0, xMax=2*self.CHUNKSZ, yMin=-32768, yMax=32767) # self.p1.disableAutoRange() # self.p1.setXRange(0, 2*self.CHUNKSZ) # self.p1.setYRange(-32768, 32767) # self.curve1 = self.p1.plot(self.data1, pen='b') # self.win.addLabel('Time [s]', row=1, col=1) # self.win.addLabel('Amplitud [counts]', row=0, col=0, angle=-90) ############## test 3 GraphicsLayoutWidget #################################### # self.win.clear() # self.max_chunks = 10 # self.p1 = self.win.addPlot() # self.p1.setDownsampling(mode='peak') # self.p1.disableAutoRange() # self.p1.setXRange(-self.CHUNKSZ, 0) # self.p1.setYRange(-32768, 32767) # self.curves = [] # self.ptr = 0 # self.data1 = np.empty((self.CHUNKSZ+1, 2)) # self.win.addLabel('Time [s]', row=1, col=1) # self.win.addLabel('Amplitud [counts]', row=0, col=0, angle=-90) ############## test 4 PlotWidget (arrayToQPath) ################################ # self.win.clear() # .plotItem # self.win.setMouseEnabled(x=True, y=False) # self.win.showGrid(True, True, 0.5) # self.win.setLabel('bottom', 'Time', units='s') # self.win.setLabel('left', 'Amplitude', units='counts') # self.ptr = 0 # self.n = 5 # self.win.setRange( # xRange=[0, self.CHUNKSZ*self.n], yRange=[-35000, 35000], padding=0, # disableAutoRange=True) # self.win.enableAutoRange(False, False) # self.x = np.arange(self.n*self.CHUNKSZ) # self.ydata = np.zeros((self.n*self.CHUNKSZ)) # self.conn = np.ones((self.n*self.CHUNKSZ)) # self.item = QtGui.QGraphicsPathItem() # self.item.setPen(pg.mkPen('b', width=2, style=QtCore.Qt.SolidLine)) # self.win.addItem(self.item) ##################################################################################### # Spectrogram Properties self.win2 = self.graphicsView_2 self.win2.clear() self.win2.enableAutoRange(False, False) Xffts = 1024 # Number of ffts along X axis window_len = self.CHUNKSZ/self.srate # Time window length spect_len = window_len * Xffts # Spectrogram length self.img_array = np.zeros((Xffts, int(self.CHUNKSZ/2)+1)) self.win2.plotItem.setRange( xRange=[0, spect_len-2*window_len], yRange=[0, self.srate/2+100], padding=0, disableAutoRange=True) self.win2.setMouseEnabled(x=True, y=True) self.win2.setLabel('left', 'Frequency', units='Hz') self.win2.setLabel('bottom', 'Time', units='s') # ax = self.win2.getAxis('bottom') # ax.setTicks([ # [], # [], ]) self.win2.setLimits(minXRange=0, maxXRange=spect_len, minYRange=0, maxYRange=self.srate/2+100, xMin=0, xMax=spect_len, yMin=0, yMax=self.srate/2+100) self.img = pg.ImageItem() self.img.setAutoDownsample(True) self.win2.addItem(self.img) # self.img_array = np.zeros((1000, int(self.CHUNKSZ/2)+1)) self.img_index = 2 # bipolar colormap pos = np.array([0., 1., 0.5, 0.25, 0.75]) color = np.array([[0, 255, 255, 255], [255, 255, 0, 255], [0, 0, 0, 255], (0, 0, 255, 255), (255, 0, 0, 255)], dtype=np.ubyte) cmap = pg.ColorMap(pos, color) lut = cmap.getLookupTable(0.0, 1.0, 256) self.img.setLookupTable(lut) self.img.setLevels([-50, 40]) # freq = np.arange((256/2)+1)/(float(256)/self.srate) # yscale = 1.0/(self.img_array.shape[1]/freq[-1]) # self.img.scale((1./self.srate)*256, yscale) freq = np.arange((self.CHUNKSZ/2)+1)/(float(self.CHUNKSZ)/self.srate) yscale = 1.0/(self.img_array.shape[1]/freq[-1]) self.img.scale((1./self.srate)*self.CHUNKSZ, yscale) self.hann = np.hanning(self.CHUNKSZ) # self.empty_plot() # UNCOMMENT DESPUES DE TESTEO TIME PLOT ################################### test 1 s#################################### # # Add a histogram with which to control the gradient of the image # hist = pg.HistogramLUTItem() # # Link the histogram to the image # hist.setImageItem(self.img) # # If you don't add the histogram to the window, it stays invisible, but I find it useful. # self.win2.addItem(hist) # # Show the window # self.win2.show() # # # This gradient is roughly comparable to the gradient used by Matplotlib # # You can adjust it and then save it using hist.gradient.saveState() # hist.gradient.restoreState( # {'mode': 'rgb', # 'ticks': [(0.5, (0, 182, 188, 255)), # (1.0, (246, 111, 0, 255)), # (0.0, (75, 0, 113, 255))]}) # # Fit the min and max levels of the histogram to the data available # hist.setLevels(np.min(Sxx), np.max(Sxx)) ################################################################################ # Plot allocation def empty_plot(self): # self.timedata = np.zeros(self.CHUNKSZ) # self.x = np.arange(0, self.CHUNKSZ, 1) # self.set_plot_data(self.x, self.timedata) # self.data1 = np.zeros(5*self.CHUNKSZ) # self.curve1 = self.p1.plot(self.data1, pen='b') self.clipBar.setValue(0) self.image_index = 2 def setupThreads(self): # Data Aqcuisition Thread self.daq_thread = QThread() self.daq_worker = DaqThread(self.BIPRange, self.srate) self.daq_worker.moveToThread(self.daq_thread) self.daq_thread.start() # conexiones Thread -> Main self.daq_worker.chunk_signal.connect(self.update) # conexiones Main -> Thread self.StartButton.clicked.connect(self.daq_worker.start_scan) # self.StopButton.clicked.connect(self.daq_worker.stop_daq_thread) NOT WORKING self.daq_worker.finished_signal.connect(self.forceWorkerReset) # self.daq_thread.finished.connect(self.worker.deleteLater) # # Playback Thread # self.play_thread = QThread() UNCOMMENT PARA PLAY HASTA FINAL # self.play_worker = PlayThread(self.srate, self.CHUNKSZ) # self.play_worker.moveToThread(self.play_thread) # self.play_thread.start() # # Play thread signals # self.StartButton.clicked.connect(self.play_worker.open_stream) # self.StopButton.clicked.connect(self.play_worker.end_stream) # self.daq_worker.chunk_signal.connect(self.play_worker.get_signal) def stop(self): self.toggle_buttons() self.daq_worker.stop_daq_thread() self.empty_plot() # UNCOMMENT DESPUES DE TESTEO PLOT # self.play_worker.end_stream() UNCOMMENT PARA PLAY def toggle_buttons(self): self.StartButton.setEnabled(True) self.StopButton.setEnabled(False) self.SettingsButton.setEnabled(True) self.filenameLabel.setText('Status: Idle') @pyqtSlot() def forceWorkerReset(self): if self.daq_thread.isRunning(): # print('Quitting Thread') # self.daq_thread.quit # print('Terminating thread.') self.daq_thread.terminate() # print('Waiting for thread termination.') self.daq_thread.wait() self.toggle_buttons() # print('building new working object.') self.setupThreads() def forceWorkerQuit(self): if self.daq_thread.isRunning(): self.daq_thread.terminate() self.daq_thread.wait() # if self.play_thread.isRunning(): UNCOMMENT PARA PLAY # self.play_thread.terminate() # self.play_thread.wait() def start(self): self.StopButton.setEnabled(True) self.StartButton.setEnabled(False) self.SettingsButton.setEnabled(False) # if self.rec_settings.FolderLabel.text() == '': # and monitor not selected # QtGui.QMessageBox.critical(self, "No Destination Folder", "Please select settings") # #algun comando para que no avance el programa self.filenameLabel.setText('Status: Recording to ' + self.rec_settings.FolderLabel.text() + '/' + self.rec_settings.NamePrefixLabel.text() + '...') self.startTime = pg.ptime.time() def set_plot_data(self, x, y): self.win.plot(x, y, pen=pg.mkPen('b', style=QtCore.Qt.SolidLine), clear=True) @QtCore.pyqtSlot(np.ndarray) def update(self, chunk): self.chunk1 = copy(chunk) # # time series rolling array # now = pg.ptime.time() # self.timedata = np.roll(self.timedata, -len(self.chunk1)) # self.timedata[-len(self.chunk1):] = self.chunk1 # self.set_plot_data(self.x, self.timedata) # print("Plot time: %0.4f sec" % (pg.ptime.time()-now)) ############## test 1 ######################################################### # self.data1[:len(self.chunk1)] = self.data1[-len(self.chunk1):] # self.data1[-len(self.chunk1):] = self.chunk1 # shift data in the array one sample left # self.curve1.setData(self.data1) # print("Plot time: %0.4f sec" % (pg.ptime.time()-now)) ############## test 2 ######################################################### # if self.plot_index == 20*self.CHUNKSZ: # self.plot_index = 0 # self.data1[self.plot_index:self.plot_index+self.CHUNKSZ] = self.chunk1 # self.curve1.setData(self.data1) # self.plot_index += self.CHUNKSZ # print("Plot time: %0.4f sec" % (pg.ptime.time()-now)) ############## test 3 ######################################################### # now = pg.ptime.time() # for c in self.curves: # c.setPos(-(self.CHUNKSZ), 0) # # curve = self.p1.plot(pen='b') # self.curves.append(curve) # print('len curve: ', len(self.curves)) # if len(self.curves) > 2: # > self.max_chunks # c = self.curves.pop(0) # self.p1.removeItem(c) # print('len curve post pop: ', len(self.curves)) # curve.setData(x=np.arange(0, self.CHUNKSZ), y=self.chunk1) # print("Plot time: %0.4f sec" % (pg.ptime.time()-now)) ############## test 4 (arrayToPyqtGraph) ###################################### # now = pg.ptime.time() # if self.ptr == self.n*self.CHUNKSZ: # self.ptr = 0 # self.ydata.fill(0) # self.ydata[self.ptr:self.ptr+self.CHUNKSZ] = self.chunk1 # self.ptr += self.CHUNKSZ # # if self.ptr == int(self.n/2): # path = pg.arrayToQPath(self.x, self.ydata, self.conn) # self.item.setPath(path) # print("Plot time: %0.4f sec" % (pg.ptime.time()-now)) ############### test 5 (resampling chunk test) ################################ ###################################################################### # Spectrogram # normalized, windowed frequencies in data chunk spec = np.fft.rfft(self.chunk1*self.hann) / self.CHUNKSZ # add ,n=512/128/256 # spec = np.fft.rfft(self.chunk1*self.hann, n=256) / self.CHUNKSZ # add ,n=512/128/256 # get magnitude psd = abs(spec) # # nivel del vúmetro chunk_abs = np.abs(self.chunk1) # avg_vumeter = np.average(chunk_abs) peak_val = np.max(chunk_abs) self.plot_iter += 1 if self.plot_iter == 50: self.progressBar.setValue(peak_val) self.plot_iter = 0 # Clip indicator if peak_val > 32760 and self.plot_iter > 5: self.clipBar.setValue(1) # convert to dB scale try: psd = 20 * np.log10(psd) except: psd[psd == 0] = np.amin(psd[psd != 0]) psd = 20 * np.log10(psd) # parche momentaneo # # roll down one and replace leading edge with new data # self.img_array = np.roll(self.img_array, -1, 0) # self.img_array[-1:] = psd # roll down one and replace leading edge with new data if self.img_index == len(self.img_array): self.img_index = 2 self.img_array[self.img_index-2] = psd self.img_array[self.img_index-1:self.img_index+1] = 10*np.ones(len(psd)) self.img_index += 1 # self.plot_iter += 1 # if self.plot_iter == 2: self.img.setImage(self.img_array, autoLevels=False) # uncomment despues de time test
class QgsFmvPlayer(QMainWindow, Ui_PlayerWindow): """ Video Player Class """ def __init__(self, iface, path=None, parent=None): """ Constructor """ super(QgsFmvPlayer, self).__init__(parent) self.setupUi(self) self.parent = parent self.iface = iface self.fileName = None self.metadataDlg = None self.createingMosaic = False self.currentInfo = 0.0 self.RecGIF = QMovie(":/imgFMV/images/record.gif") self.videoWidget.customContextMenuRequested[QPoint].connect( self.contextMenuRequested) self.duration = 0 self.playerMuted = False self.HasFileAudio = False self.player = QMediaPlayer(None, QMediaPlayer.VideoSurface) self.player.setNotifyInterval(1000) # One second self.pass_time = 0.1 self.playlist = QMediaPlaylist() # self.player.setVideoOutput( # self.videoWidget) # Standar Surface self.player.setVideoOutput( self.videoWidget.videoSurface()) # Custom Surface self.player.durationChanged.connect(self.durationChanged) self.player.positionChanged.connect(self.positionChanged) self.player.mediaStatusChanged.connect(self.statusChanged) self.player.stateChanged.connect(self.setCurrentState) self.playerState = QMediaPlayer.StoppedState self.playFile(path) self.sliderDuration.setRange(0, self.player.duration() / 1000) self.volumeSlider.setValue(self.player.volume()) self.volumeSlider.enterEvent = self.showVolumeTip if self.metadataDlg is None: self.metadataDlg = QgsFmvMetadata(parent=self, player=self) self.addDockWidget(Qt.RightDockWidgetArea, self.metadataDlg) self.metadataDlg.setMinimumWidth(500) self.metadataDlg.hide() def HasMetadata(self, videoPath): """ Check if video have Metadata or not """ try: p = _spawn([ '-i', videoPath, '-map', 'data-re', '-codec', 'copy', '-f', 'data', '-' ]) stdout_data, _ = p.communicate() if stdout_data == b'': qgsu.showUserAndLogMessage(QCoreApplication.translate( "QgsFmvPlayer", "This video don't have Metadata ! : "), level=QGis.Info) return False return True except Exception as e: qgsu.showUserAndLogMessage(QCoreApplication.translate( "QgsFmvPlayer", "Metadata Callback Failed! : "), str(e), level=QGis.Info) def HasAudio(self, videoPath): """ Check if video have Metadata or not """ try: p = _spawn([ '-i', videoPath, '-show_streams', '-select_streams', 'a', '-loglevel', 'error' ], type="ffprobe") stdout_data, _ = p.communicate() if stdout_data == b'': qgsu.showUserAndLogMessage(QCoreApplication.translate( "QgsFmvPlayer", "This video don't have Audio ! : "), level=QGis.Info) return False return True except Exception as e: qgsu.showUserAndLogMessage(QCoreApplication.translate( "QgsFmvPlayer", "Audio check Failed! : "), str(e), level=QGis.Info) def callBackMetadata(self, currentTime, nextTime): """ Metadata CallBack """ try: # TODO : Speed this function # stdout_data = _check_output(['-i', self.fileName, # '-ss', currentTime, # '-to', nextTime, # '-f', 'data', '-']) t = callBackMetadataThread(cmds=[ '-i', self.fileName, '-ss', currentTime, '-to', nextTime, '-map', 'data-re', '-f', 'data', '-' ]) t.start() t.join(1) if t.is_alive(): t.p.terminate() t.join() if t.stdout == b'': return for packet in StreamParser(t.stdout): try: self.addMetadata(packet.MetadataList()) UpdateLayers(packet, parent=self, mosaic=self.createingMosaic) self.iface.mapCanvas().refresh() QApplication.processEvents() return except Exception as e: None except Exception as e: qgsu.showUserAndLogMessage(QCoreApplication.translate( "QgsFmvPlayer", "Metadata Callback Failed! : "), str(e), level=QGis.Info) def addMetadata(self, packet): ''' Add Metadata to List ''' self.clearMetadata() row = 0 for key in sorted(packet.keys()): self.metadataDlg.VManager.insertRow(row) self.metadataDlg.VManager.setItem(row, 0, QTableWidgetItem(str(key))) self.metadataDlg.VManager.setItem( row, 1, QTableWidgetItem(str(packet[key][0]))) self.metadataDlg.VManager.setItem( row, 2, QTableWidgetItem(str(packet[key][1]))) row += 1 self.metadataDlg.VManager.setVisible(False) self.metadataDlg.VManager.resizeColumnsToContents() self.metadataDlg.VManager.setVisible(True) self.metadataDlg.VManager.verticalScrollBar().setSliderPosition( self.sliderPosition) def clearMetadata(self): ''' Clear Metadata List ''' try: self.sliderPosition = self.metadataDlg.VManager.verticalScrollBar( ).sliderPosition() self.metadataDlg.VManager.setRowCount(0) except: None def saveInfoToJson(self): """ Save video Info to json """ if not self.KillAllProcessors(): return out_json, _ = QFileDialog.getSaveFileName(self, "Save File", "", "Json Files (*.json)") if out_json == "": return try: self.VPProbeToJson = Converter() self.VPTProbeToJson = QThread() self.VPProbeToJson.moveToThread(self.VPTProbeToJson) self.VPProbeToJson.finished.connect(self.QThreadFinished) self.VPProbeToJson.error.connect(self.QThreadError) self.VPProbeToJson.progress.connect( self.progressBarProcessor.setValue) self.VPTProbeToJson.start(QThread.LowPriority) QMetaObject.invokeMethod(self.VPProbeToJson, 'probeToJson', Qt.QueuedConnection, Q_ARG(str, self.fileName), Q_ARG(str, out_json)) except Exception as e: qgsu.showUserAndLogMessage( QCoreApplication.translate("QgsFmvPlayer", "Error saving Json")) self.QThreadFinished("probeToJson", "Closing ProbeToJson") def showVideoInfo(self): ''' Show default probe info ''' try: self.VPProbe = Converter() self.VPTProbe = QThread() self.VPProbe.moveToThread(self.VPTProbe) self.VPProbe.finishedJson.connect(self.QThreadFinished) self.VPProbe.error.connect(self.QThreadError) self.VPProbe.progress.connect(self.progressBarProcessor.setValue) self.VPTProbe.start(QThread.LowPriority) QMetaObject.invokeMethod(self.VPProbe, 'probeShow', Qt.QueuedConnection, Q_ARG(str, self.fileName)) except Exception as e: qgsu.showUserAndLogMessage( QCoreApplication.translate("QgsFmvPlayer", "Error Info Show")) self.QThreadFinished("probeShow", "Closing Probe") return def state(self): ''' Return Current State ''' return self.playerState def setCurrentState(self, state): ''' Set Current State ''' if state != self.playerState: self.playerState = state if state == QMediaPlayer.StoppedState: self.btn_play.setIcon(QIcon(":/imgFMV/images/play-arrow.png")) return def showColorDialog(self): ''' Show Color dialog ''' self.ColorDialog = ColorDialog(parent=self) self.ColorDialog.setWindowFlags(Qt.Window | Qt.WindowCloseButtonHint) # Fail if not uncheked self.actionMagnifying_glass.setChecked(False) self.actionZoom_Rectangle.setChecked(False) self.ColorDialog.exec_() return def createMosaic(self, value): ''' Function for create Video Mosaic ''' home = os.path.expanduser("~") qgsu.createFolderByName(home, "QGIS_FMV") homefmv = os.path.join(home, "QGIS_FMV") root, ext = os.path.splitext(os.path.basename(self.fileName)) qgsu.createFolderByName(homefmv, root) self.createingMosaic = value # Create Group CreateGroupByName() return def contextMenuRequested(self, point): ''' Context Menu Video ''' menu = QMenu() # actionColors = menu.addAction( # QCoreApplication.translate("QgsFmvPlayer", "Color Options")) # actionColors.setShortcut("Ctrl+May+C") # actionColors.triggered.connect(self.showColorDialog) actionMute = menu.addAction( QCoreApplication.translate("QgsFmvPlayer", "Mute/Unmute")) actionMute.setShortcut("Ctrl+May+U") actionMute.triggered.connect(self.setMuted) menu.addSeparator() actionAllFrames = menu.addAction( QCoreApplication.translate("QgsFmvPlayer", "Extract All Frames")) actionAllFrames.setShortcut("Ctrl+May+A") actionAllFrames.triggered.connect(self.ExtractAllFrames) actionCurrentFrames = menu.addAction( QCoreApplication.translate("QgsFmvPlayer", "Extract Current Frame")) actionCurrentFrames.setShortcut("Ctrl+May+Q") actionCurrentFrames.triggered.connect(self.ExtractCurrentFrame) menu.addSeparator() actionShowMetadata = menu.addAction( QCoreApplication.translate("QgsFmvPlayer", "Show Metadata")) actionShowMetadata.setShortcut("Ctrl+May+M") actionShowMetadata.triggered.connect(self.OpenQgsFmvMetadata) menu.exec_(self.mapToGlobal(point)) # Start Snnipet FILTERS def grayFilter(self, value): self.UncheckFilters(self.sender(), value) self.videoWidget.SetGray(value) self.videoWidget.UpdateSurface() return def edgeFilter(self, value): self.UncheckFilters(self.sender(), value) self.videoWidget.SetEdgeDetection(value) self.videoWidget.UpdateSurface() return def invertColorFilter(self, value): self.UncheckFilters(self.sender(), value) self.videoWidget.SetInvertColor(value) self.videoWidget.UpdateSurface() return def autoContrastFilter(self, value): self.UncheckFilters(self.sender(), value) self.videoWidget.SetAutoContrastFilter(value) self.videoWidget.UpdateSurface() return def monoFilter(self, value): self.UncheckFilters(self.sender(), value) self.videoWidget.SetMonoFilter(value) self.videoWidget.UpdateSurface() return def magnifier(self, value): self.UncheckUtils(self.sender(), value) self.videoWidget.SetMagnifier(value) self.videoWidget.UpdateSurface() return def zoomRect(self, value): self.UncheckUtils(self.sender(), value) self.videoWidget.SetZoomRect(value) self.videoWidget.UpdateSurface() return def UncheckUtils(self, sender, value): # p = self.player.position() # self.player.setVideoOutput( # self.videoWidget.videoSurface()) # Custom surface # self.player.setPosition(p) QApplication.processEvents() name = sender.objectName() self.actionMagnifying_glass.setChecked( True if name == "actionMagnifying_glass" else False) self.actionZoom_Rectangle.setChecked(True if name == "actionZoom_Rectangle" else False) sender.setChecked(value) return def UncheckFilters(self, sender, value): # p = self.player.position() # self.player.setVideoOutput( # self.videoWidget.videoSurface()) # Custom surface # self.player.setPosition(p) # QApplication.processEvents() name = sender.objectName() self.actionGray.setChecked(True if name == "actionGray" else False) self.actionInvert_Color.setChecked(True if name == "actionInvert_Color" else False) self.actionMono_Filter.setChecked(True if name == "actionMono_Filter" else False) self.actionCanny_edge_detection.setChecked( True if name == "actionCanny_edge_detection" else False) self.actionAuto_Contrast_Filter.setChecked( True if name == "actionAuto_Contrast_Filter" else False) self.videoWidget.SetGray(True if name == "actionGray" else False) self.videoWidget.SetEdgeDetection( True if name == "actionCanny_edge_detection" else False) self.videoWidget.SetInvertColor(True if name == "actionInvert_Color" else False) self.videoWidget.SetMonoFilter(True if name == "actionMono_Filter" else False) self.videoWidget.SetAutoContrastFilter( True if name == "actionAuto_Contrast_Filter" else False) sender.setChecked(value) return # End Snnipet FILTERS def isMuted(self): ''' Is muted video property''' return self.playerMuted def setMuted(self): ''' Muted video ''' if self.player.isMuted(): self.btn_volume.setIcon(QIcon(":/imgFMV/images/volume_up.png")) self.player.setMuted(False) self.volumeSlider.setEnabled(True) else: self.btn_volume.setIcon(QIcon(":/imgFMV/images/volume_off.png")) self.player.setMuted(True) self.volumeSlider.setEnabled(False) return def stop(self): ''' Stop video''' self.player.stop() self.videoWidget.update() return def volume(self): ''' Volume Slider ''' return self.volumeSlider.value() def setVolume(self, volume): ''' Tooltip and set value''' self.player.setVolume(volume) self.showVolumeTip(volume) if 0 < volume <= 30: self.btn_volume.setIcon(QIcon(":/imgFMV/images/volume_30.png")) elif 30 < volume <= 60: self.btn_volume.setIcon(QIcon(":/imgFMV/images/volume_60.png")) elif 60 < volume <= 100: self.btn_volume.setIcon(QIcon(":/imgFMV/images/volume_up.png")) elif volume == 0: self.btn_volume.setIcon(QIcon(":/imgFMV/images/volume_off.png")) def EndMedia(self): ''' Button end video position ''' if self.player.isVideoAvailable(): self.player.setPosition(self.player.duration()) self.videoWidget.update() return def StartMedia(self): ''' Button start video position ''' if self.player.isVideoAvailable(): self.player.setPosition(0) self.videoWidget.update() return def forwardMedia(self): ''' Button forward Video ''' forwardTime = int(self.player.position()) + 10 * 1000 if forwardTime > int(self.player.duration()): forwardTime = int(self.player.duration()) self.player.setPosition(forwardTime) def rewindMedia(self): ''' Button rewind Video ''' rewindTime = int(self.player.position()) - 10 * 1000 if rewindTime < 0: rewindTime = 0 self.player.setPosition(rewindTime) def AutoRepeat(self, checked): ''' Button AutoRepeat Video ''' if checked: self.playlist.setPlaybackMode(QMediaPlaylist.Loop) else: self.playlist.setPlaybackMode(QMediaPlaylist.Sequential) return def showVolumeTip(self, _): ''' Volume Slider Tooltip Trick ''' self.style = self.volumeSlider.style() self.opt = QStyleOptionSlider() self.volumeSlider.initStyleOption(self.opt) rectHandle = self.style.subControlRect(self.style.CC_Slider, self.opt, self.style.SC_SliderHandle) self.tip_offset = QPoint(5, 15) pos_local = rectHandle.topLeft() + self.tip_offset pos_global = self.volumeSlider.mapToGlobal(pos_local) QToolTip.showText(pos_global, str(self.volumeSlider.value()) + " %", self) def showMoveTip(self, currentInfo): ''' Player Silder Move Tooptip Trick ''' self.style = self.sliderDuration.style() self.opt = QStyleOptionSlider() self.sliderDuration.initStyleOption(self.opt) rectHandle = self.style.subControlRect(self.style.CC_Slider, self.opt, self.style.SC_SliderHandle) self.tip_offset = QPoint(5, 15) pos_local = rectHandle.topLeft() + self.tip_offset pos_global = self.sliderDuration.mapToGlobal(pos_local) tStr = _seconds_to_time(currentInfo) QToolTip.showText(pos_global, tStr, self) def durationChanged(self, duration): ''' Duration video change signal ''' duration /= 1000 self.duration = duration self.sliderDuration.setMaximum(duration) def positionChanged(self, progress): ''' Current Video position change ''' progress /= 1000 if not self.sliderDuration.isSliderDown(): self.sliderDuration.setValue(progress) self.updateDurationInfo(progress) def updateDurationInfo(self, currentInfo): ''' Update labels duration Info and CallBack Metadata ''' duration = self.duration self.currentInfo = currentInfo if currentInfo or duration: totalTime = _seconds_to_time(duration) currentTime = _seconds_to_time(currentInfo) tStr = currentTime + " / " + totalTime nextTime = currentInfo + self.pass_time currentTimeInfo = _seconds_to_time_frac(currentInfo) nextTimeInfo = _seconds_to_time_frac(nextTime) # Metadata CallBack self.callBackMetadata(currentTimeInfo, nextTimeInfo) else: tStr = "" self.labelDuration.setText(tStr) def handleCursor(self, status): ''' Change cursor ''' if status in (QMediaPlayer.LoadingMedia, QMediaPlayer.BufferingMedia, QMediaPlayer.StalledMedia): self.setCursor(Qt.BusyCursor) else: self.unsetCursor() def statusChanged(self, status): ''' Signal Status video change ''' self.handleCursor(status) if status == QMediaPlayer.LoadingMedia: self.videoAvailableChanged(False) elif status == QMediaPlayer.StalledMedia: self.videoAvailableChanged(False) if status == QMediaPlayer.EndOfMedia: self.videoAvailableChanged(True) elif status == QMediaPlayer.InvalidMedia: qgsu.showUserAndLogMessage(QCoreApplication.translate( "QgsFmvPlayer", self.player.errorString()), level=QGis.Warning) self.videoAvailableChanged(False) else: self.videoAvailableChanged(True) def playFile(self, videoPath): ''' Play file from path ''' try: RemoveVideoLayers() RemoveGroupByName() self.fileName = videoPath self.playlist = QMediaPlaylist() url = QUrl.fromLocalFile(videoPath) self.playlist.addMedia(QMediaContent(url)) self.playlist.setPlaybackMode(QMediaPlaylist.Sequential) self.player.setPlaylist(self.playlist) self.setWindowTitle("Playing : " + os.path.basename(os.path.normpath(videoPath))) if self.HasMetadata(videoPath): CreateVideoLayers() self.clearMetadata() self.lb_cursor_coord.setText( "<span style='font-size:10pt; font-weight:bold;'>Lon :</span>" + "<span style='font-size:9pt; font-weight:normal;'>Null</span>" + "<span style='font-size:10pt; font-weight:bold;'> Lat :</span>" + "<span style='font-size:9pt; font-weight:normal;'>Null</span>" ) else: self.btn_GeoReferencing.setEnabled(False) self.HasFileAudio = True if not self.HasAudio(videoPath): self.actionAudio.setEnabled(False) self.actionSave_Audio.setEnabled(False) self.HasFileAudio = False self.playClicked(True) except Exception as e: qgsu.showUserAndLogMessage(QCoreApplication.translate( "QgsFmvPlayer", 'Open Video File : '), str(e), level=QGis.Warning) def ReciconUpdate(self, frame): self.btn_Rec.setIcon(QIcon(self.RecGIF.currentPixmap())) def RecordVideo(self, value): ''' Cut Video ''' currentTime = _seconds_to_time(self.currentInfo) if value is False: self.endRecord = currentTime _, file_extension = os.path.splitext(self.fileName) out, _ = QFileDialog.getSaveFileName(self, "Save As", "", file_extension) if not out: self.RecGIF.frameChanged.disconnect(self.ReciconUpdate) self.RecGIF.stop() self.btn_Rec.setIcon(QIcon(":/imgFMV/images/record.png")) return False lfn = out.lower() if not lfn.endswith((file_extension)): out += file_extension p = _spawn([ '-i', self.fileName, '-ss', self.startRecord, '-to', self.endRecord, '-c', 'copy', out ]) p.communicate() qgsu.showUserAndLogMessage( QCoreApplication.translate("QgsFmvPlayer", "Save file succesfully!")) self.RecGIF.frameChanged.disconnect(self.ReciconUpdate) self.RecGIF.stop() self.btn_Rec.setIcon(QIcon(":/imgFMV/images/record.png")) else: self.startRecord = currentTime self.RecGIF.frameChanged.connect(self.ReciconUpdate) self.RecGIF.start() return def videoAvailableChanged(self, available): ''' Buttons for video available ''' # self.btn_Color.setEnabled(available) self.btn_CaptureFrame.setEnabled(available) self.gb_PlayerControls.setEnabled(available) return def toggleGroup(self, state): ''' Toggle GroupBox ''' sender = self.sender() if state: sender.setFixedHeight(sender.sizeHint().height()) else: sender.setFixedHeight(15) def playClicked(self, state): ''' Stop and Play video ''' if self.playerState in (QMediaPlayer.StoppedState, QMediaPlayer.PausedState): self.btn_play.setIcon(QIcon(":/imgFMV/images/pause.png")) self.player.play() elif self.playerState == QMediaPlayer.PlayingState: self.btn_play.setIcon(QIcon(":/imgFMV/images/play-arrow.png")) self.player.pause() def seek(self, seconds): '''Slider Move''' self.player.setPosition(seconds * 1000) self.showMoveTip(seconds) def convertVideo(self): '''Convert Video To Other Format ''' if not self.KillAllProcessors(): return sel = "mp4 Files (*.mp4)" out, _ = QFileDialog.getSaveFileName( self, "Save Video as...", None, "ogg files (*.ogg);;avi Files (*.avi);;mkv Files (*.mkv);;webm Files (*.webm);;flv Files (*.flv);;mov Files (*.mov);;mp4 Files (*.mp4);;mpg Files (*.mpg);;mp3 Files (*.mp3)", sel) if not out: return False lfn = out.lower() if not lfn.endswith(('.ogg', '.avi', '.mkv', '.webm', '.flv', '.mov', '.mp4', '.mp3', '.mpg')): # The default. out += '.mp4' try: self.VPConverter = Converter() self.VPTConverter = QThread() self.VPConverter.moveToThread(self.VPTConverter) self.VPConverter.finished.connect(self.QThreadFinished) self.VPConverter.error.connect(self.QThreadError) self.VPConverter.progress.connect( self.progressBarProcessor.setValue) self.VPTConverter.start(QThread.LowPriority) # TODO : Make Correct format Conversion and embebed metadata info = self.VPConverter.probeInfo(self.fileName) if info is not None: if self.HasFileAudio: audio_codec = info.audio.codec audio_samplerate = info.audio.audio_samplerate audio_channels = info.audio.audio_channels video_codec = info.video.codec video_width = info.video.video_width video_height = info.video.video_height video_fps = info.video.video_fps _, out_ext = os.path.splitext(out) if self.HasFileAudio: options = { 'format': out_ext[1:], 'audio': { 'codec': audio_codec, 'samplerate': audio_samplerate, 'channels': audio_channels }, 'video': { 'codec': video_codec, 'width': video_width, 'height': video_height, 'fps': video_fps } } else: options = { 'format': out_ext[1:], 'video': { 'codec': video_codec, 'width': video_width, 'height': video_height, 'fps': video_fps } } QMetaObject.invokeMethod(self.VPConverter, 'convert', Qt.QueuedConnection, Q_ARG(str, self.fileName), Q_ARG(str, out), Q_ARG(dict, options), Q_ARG(bool, False)) except Exception as e: qgsu.showUserAndLogMessage( QCoreApplication.translate("QgsFmvPlayer", "Error converting video ")) self.QThreadFinished("convert", "Closing convert") def ShowPlot(self, bitrate_data, frame_count, output=None): ''' Show plot,because show not work using threading ''' matplot.figure().canvas.set_window_title(self.fileName) matplot.title("Stream Bitrate vs Time") matplot.xlabel("Time (sec)") matplot.ylabel("Frame Bitrate (kbit/s)") matplot.grid(True) # map frame type to color frame_type_color = { # audio 'A': 'yellow', # video 'I': 'red', 'P': 'green', 'B': 'blue' } global_peak_bitrate = 0.0 global_mean_bitrate = 0.0 # render charts in order of expected decreasing size for frame_type in ['I', 'P', 'B', 'A']: # skip frame type if missing if frame_type not in bitrate_data: continue # convert list of tuples to numpy 2d array frame_list = bitrate_data[frame_type] frame_array = numpy.array(frame_list) # update global peak bitrate peak_bitrate = frame_array.max(0)[1] if peak_bitrate > global_peak_bitrate: global_peak_bitrate = peak_bitrate # update global mean bitrate (using piecewise mean) mean_bitrate = frame_array.mean(0)[1] global_mean_bitrate += mean_bitrate * \ (len(frame_list) / frame_count) # plot chart using gnuplot-like impulses matplot.vlines(frame_array[:, 0], [0], frame_array[:, 1], color=frame_type_color[frame_type], label="{} Frames".format(frame_type)) self.progressBarProcessor.setValue(90) # calculate peak line position (left 15%, above line) peak_text_x = matplot.xlim()[1] * 0.15 peak_text_y = global_peak_bitrate + \ ((matplot.ylim()[1] - matplot.ylim()[0]) * 0.015) peak_text = "peak ({:.0f})".format(global_peak_bitrate) # draw peak as think black line w/ text matplot.axhline(global_peak_bitrate, linewidth=2, color='black') matplot.text(peak_text_x, peak_text_y, peak_text, horizontalalignment='center', fontweight='bold', color='black') # calculate mean line position (right 85%, above line) mean_text_x = matplot.xlim()[1] * 0.85 mean_text_y = global_mean_bitrate + \ ((matplot.ylim()[1] - matplot.ylim()[0]) * 0.015) mean_text = "mean ({:.0f})".format(global_mean_bitrate) # draw mean as think black line w/ text matplot.axhline(global_mean_bitrate, linewidth=2, color='black') matplot.text(mean_text_x, mean_text_y, mean_text, horizontalalignment='center', fontweight='bold', color='black') matplot.legend() if output != "": matplot.savefig(output) else: matplot.show() self.progressBarProcessor.setValue(100) def CreateBitratePlot(self): ''' Create video Plot Bitrate Thread ''' if not self.KillAllProcessors(): return try: self.VPBitratePlot = CreatePlotsBitrate() self.VPTBitratePlot = QThread() self.VPBitratePlot.moveToThread(self.VPTBitratePlot) self.VPBitratePlot.finished.connect(self.QThreadFinished) self.VPBitratePlot.return_fig.connect(self.ShowPlot) self.VPBitratePlot.error.connect(self.QThreadError) self.VPBitratePlot.progress.connect( self.progressBarProcessor.setValue) self.VPTBitratePlot.start(QThread.LowPriority) sender = self.sender().objectName() if sender == "actionAudio": QMetaObject.invokeMethod(self.VPBitratePlot, 'CreatePlot', Qt.QueuedConnection, Q_ARG(str, self.fileName), Q_ARG(str, None), Q_ARG(str, 'audio')) elif sender == "actionVideo": QMetaObject.invokeMethod(self.VPBitratePlot, 'CreatePlot', Qt.QueuedConnection, Q_ARG(str, self.fileName), Q_ARG(str, None), Q_ARG(str, 'video')) elif sender == "actionSave_Audio": selfilter = "Portable Network Graphics (*.png)" fileaudio, _ = QFileDialog.getSaveFileName( self, "Save Audio Bitrate Plot", "", "EPS Encapsulated Postscript (*.eps);;" "PGF code for LaTex (*.pgf);;" "Portable document format(*pdf);;" "Portable Network Graphics (*.png);;" "Postscript (*.ps);;" "Raw RGBA bitmap (*.raw*.rgba);;" "Scalable vector graphics (*.svg*.svgz)", selfilter) if fileaudio == "": return QMetaObject.invokeMethod(self.VPBitratePlot, 'CreatePlot', Qt.QueuedConnection, Q_ARG(str, self.fileName), Q_ARG(str, fileaudio), Q_ARG(str, 'audio')) elif sender == "actionSave_Video": selfilter = "Portable Network Graphics (*.png)" filevideo, _ = QFileDialog.getSaveFileName( self, "Save Video Bitrate Plot", "", "EPS Encapsulated Postscript (*.eps);;" "PGF code for LaTex (*.pgf);;" "Portable document format(*pdf);;" "Portable Network Graphics (*.png);;" "Postscript (*.ps);;" "Raw RGBA bitmap (*.raw*.rgba);;" "Scalable vector graphics (*.svg*.svgz)", selfilter) if filevideo == "": return QMetaObject.invokeMethod(self.VPBitratePlot, 'CreatePlot', Qt.QueuedConnection, Q_ARG(str, self.fileName), Q_ARG(str, filevideo), Q_ARG(str, 'video')) except Exception as e: qgsu.showUserAndLogMessage( QCoreApplication.translate("QgsFmvPlayer", "Failed creating Plot Bitrate")) def ExtractAllFrames(self): """ Extract All Video Frames Thread """ if not self.KillAllProcessors(): return options = QFileDialog.DontResolveSymlinks | QFileDialog.ShowDirsOnly directory = QFileDialog.getExistingDirectory( self, QCoreApplication.translate("QgsFmvPlayer", "Save images"), '', options=options) if directory: self.VPExtractFrames = ExtractFramesProcessor() self.VPTExtractAllFrames = QThread() self.VPExtractFrames.moveToThread(self.VPTExtractAllFrames) self.VPExtractFrames.finished.connect(self.QThreadFinished) self.VPExtractFrames.error.connect(self.QThreadError) self.VPExtractFrames.progress.connect( self.progressBarProcessor.setValue) self.VPTExtractAllFrames.start(QThread.LowPriority) QMetaObject.invokeMethod(self.VPExtractFrames, 'ExtractFrames', Qt.QueuedConnection, Q_ARG(str, directory), Q_ARG(str, self.fileName)) return def ExtractCurrentFrame(self): """ Extract Current Frame Thread """ image = self.videoWidget.GetCurrentFrame() out_image, _ = QFileDialog.getSaveFileName( self, "Save Current Frame", "", "Image File (*.png *.jpg *.bmp *.tiff)") if out_image == "": return if out_image: t = threading.Thread(target=self.SaveCapture, args=( image, out_image, )) t.start() return def SaveCapture(self, image, output): ''' Save Current Image ''' image.save(output) QApplication.processEvents() return def QThreadFinished(self, process, msg, outjson=None): ''' Finish Threads ''' if process == "ExtractFramesProcessor": self.VPExtractFrames.deleteLater() self.VPTExtractAllFrames.terminate() self.VPTExtractAllFrames.deleteLater() elif process == "CreatePlotsBitrate": self.VPBitratePlot.deleteLater() self.VPTBitratePlot.terminate() self.VPTBitratePlot.deleteLater() elif process == "convert": self.VPConverter.deleteLater() self.VPTConverter.terminate() self.VPTConverter.deleteLater() elif process == "probeToJson": self.VPProbeToJson.deleteLater() self.VPTProbeToJson.terminate() self.VPTProbeToJson.deleteLater() elif process == "probeShow": self.VPProbe.deleteLater() self.VPTProbe.terminate() self.VPTProbe.deleteLater() self.showVideoInfoDialog(outjson) QApplication.processEvents() self.progressBarProcessor.setValue(0) return def QThreadError(self, processor, e, exception_string): """ Threads Errors""" qgsu.showUserAndLogMessage(QCoreApplication.translate( "QgsFmvPlayer", processor), 'Failed!\n'.format(exception_string), level=QGis.Warning) self.QThreadFinished(processor, "Closing Processor") return def OpenQgsFmvMetadata(self): """ Open Metadata Dock """ if self.metadataDlg is None: self.metadataDlg = QgsFmvMetadata(parent=self, player=self) self.addDockWidget(Qt.RightDockWidgetArea, self.metadataDlg) self.metadataDlg.show() else: self.metadataDlg.show() return def KillAllProcessors(self): """Kill All Processors""" """ Extract all frames Processors """ try: if self.VPTExtractAllFrames.isRunning(): ret = qgsu.CustomMessage( QCoreApplication.translate( "QgsFmvPlayer", "HEY...Active background process!"), QCoreApplication.translate("QgsFmvPlayer", "Do you really want close?")) if ret == QMessageBox.Yes: self.QThreadFinished("ExtractFramesProcessor", "Closing Extract Frames Processor") else: return False except: None """ Bitrates Processors""" try: if self.VPTBitratePlot.isRunning(): ret = qgsu.CustomMessage( QCoreApplication.translate( "QgsFmvPlayer", "HEY...Active background process!"), QCoreApplication.translate("QgsFmvPlayer", "Do you really want close?")) if ret == QMessageBox.Yes: self.QThreadFinished("CreatePlotsBitrate", "Closing Plot Bitrate") else: return False except: None """ Converter Processors """ try: if self.VPTConverter.isRunning(): ret = qgsu.CustomMessage( QCoreApplication.translate( "QgsFmvPlayer", "HEY...Active background process!"), QCoreApplication.translate("QgsFmvPlayer", "Do you really want close?")) if ret == QMessageBox.Yes: self.QThreadFinished("convert", "Closing convert") else: return False except: None """ probeToJson Processors """ try: if self.VPTProbeToJson.isRunning(): ret = qgsu.CustomMessage( QCoreApplication.translate( "QgsFmvPlayer", "HEY...Active background process!"), QCoreApplication.translate("QgsFmvPlayer", "Do you really want close?")) if ret == QMessageBox.Yes: self.QThreadFinished("probeToJson", "Closing Info to Json") else: return False except: None """ probeShow Processors """ try: if self.VPTProbe.isRunning(): ret = qgsu.CustomMessage( QCoreApplication.translate( "QgsFmvPlayer", "HEY...Active background process!"), QCoreApplication.translate("QgsFmvPlayer", "Do you really want close?")) if ret == QMessageBox.Yes: self.QThreadFinished("probeShow", "Closing Show Video Info") else: return False except: None return True def showVideoInfoDialog(self, outjson): """ Show Video Information Dialog """ view = QTreeView() model = QJsonModel() view.setModel(model) model.loadJsonFromConsole(outjson) self.VideoInfoDialog = QDialog(self) self.VideoInfoDialog.setWindowTitle("Video Information : " + self.fileName) self.VideoInfoDialog.setWindowIcon( QIcon(":/imgFMV/images/video_information.png")) self.verticalLayout = QVBoxLayout(self.VideoInfoDialog) self.verticalLayout.addWidget(view) view.expandAll() view.header().setSectionResizeMode(QHeaderView.ResizeToContents) self.VideoInfoDialog.setWindowFlags(Qt.Window | Qt.WindowCloseButtonHint) self.VideoInfoDialog.setObjectName("VideoInfoDialog") self.VideoInfoDialog.resize(500, 400) self.VideoInfoDialog.show() def closeEvent(self, evt): """ Close Event """ if self.KillAllProcessors() is False: evt.ignore() return self.player.stop() self.parent._PlayerDlg = None self.parent.ToggleActiveFromTitle() RemoveVideoLayers() RemoveGroupByName() # Restore Filters State self.videoWidget.RestoreFilters() # QApplication.processEvents() del self.player
class Listener(QObject): finished = pyqtSignal() update = pyqtSignal(GridContainerUpdate) resize = pyqtSignal(GreetingData) status = pyqtSignal(str) tournament = pyqtSignal(str) def __init__(self, player, address, port, main, name=None): super().__init__() self.player: UIPlayer = player self.address = address self.port = port self.main = main self.name = name self.snakes = [] self.thread = QThread() # move the Worker object to the Thread object # "push" self from the current thread to this thread self.moveToThread(self.thread) # Connect Worker Signals to the Thread slots self.finished.connect(self.thread.quit) # Connect Thread started signal to Worker operational slot method self.thread.started.connect(self.run) def start(self): # Start the thread self.thread.start() print("Thread_Start") def stop(self): self.thread.terminate() @pyqtSlot() def run(self): print("Thread_Running") client = Client(self.address, self.port) client.connect() while True: command = client.get_command() if command.comm == ENetworkCommand.greeting and self.main: client.send_name(self.name) self.resize.emit(command.data) elif command.comm == ENetworkCommand.greeting: client.send_name(self.name) elif command.comm == ENetworkCommand.container_update: self.update.emit(command.data) self.snakes = command.data.snakes elif command.comm == ENetworkCommand.turn_count_start and self.main: self.player.window.startPlaning(command.data) self.status.emit("") elif command.comm == ENetworkCommand.call_for_plans: if self.main: self.player.window.endPlaningself() client.send_plans(self.player.movingPlans) elif command.comm == ENetworkCommand.game_end and self.main: send = F"Player {command.data.winner} won!" if command.data.has_next_game: send += "\nWaiting\nfor next game" self.status.emit(send) elif command.comm == ENetworkCommand.tournament_update: self.tournament.emit( Client.tournament_update_data_to_string(command.data))
class CutplanWidget(QWidget): newcutplans = pyqtSignal(bool) cploadfinish = pyqtSignal() def setupUi(self, Form, size=250): self.data = None self.addData = None self.host = "172.16.90.241" self.CPWidgets = [] self.CPImg = [] self.CPProgress = [] self.CPLabels = [] self.CPWidgetsH = [] self.anims = [] self.graphics = [] self.CPLoads = [] self.CPNum = 0 self.CPMinSize = 0 self.size = size self.speedsetting = 55 Form.setObjectName("Form") Form.setMaximumWidth(self.size) sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding) Form.setSizePolicy(sizePolicy) Form.setMinimumWidth(self.size) Form.setStyleSheet("background-color: rgb(255, 255, 255);") self.horizontalLayout = QVBoxLayout(Form) self.horizontalLayout.setContentsMargins(0, 0, 0, 0) self.horizontalLayout.setSpacing(15) self.horizontalLayout.setObjectName("horizontalLayout") # CPWidgets self.CPLayout = QVBoxLayout() self.CPLayout.setContentsMargins(0, 0, 0, 0) self.CPLayout.setObjectName("CPLayout") self.horizontalLayout.addLayout(self.CPLayout) self.AddButton = QPushButton(Form) sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.AddButton.sizePolicy().hasHeightForWidth()) self.AddButton.setSizePolicy(sizePolicy) self.AddButton.setMinimumSize(QSize(self.size, self.size)) self.AddButton.setStyleSheet( "background-color: rgba(255, 255, 255, 0);") self.AddButton.setText("") self.AddButton.setCursor(QCursor(Qt.PointingHandCursor)) icon1 = QIcon("images/add.png") self.AddButton.setIcon(icon1) self.AddButton.setIconSize(QSize(75, 75)) self.AddButton.setObjectName("AddButton") self.horizontalLayout.addWidget(self.AddButton) self.setupThread() # EVENTS # self.AddButton.clicked.connect(self.onClick2) self.retranslateUi(Form) QMetaObject.connectSlotsByName(Form) self.Form = Form def setupThread(self): self.thread = QThread() self.loader = CutplanLoader() self.loader.moveToThread(self.thread) self.loader.cutplanloaded.connect(self.AddOneCP) self.threadconnected = False def onClick2(self): if self.threadconnected: self.thread.started.disconnect() self.threadconnected = False def onClick(self): # Ping to see if can connect, use wait cursor QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) try: check_output("ping -n 1 -w 2000 " + self.host, shell=True) except CalledProcessError: QApplication.restoreOverrideCursor() self.ErrorBox("Network Error", "Connection Failed", "Check internet connection to Log Scanner Server.") return QApplication.restoreOverrideCursor() if self.threadconnected: self.thread.started.disconnect() self.threadconnected = False self.AddDialog = AddCutplanDialog() self.AddDialog.setupUi(self.addData, "support/cpquery.sql", self.host) self.AddDialog.show() self.AddDialog.buttonBox.accepted.connect(self.AddCP) def DeleteCP(self): for w in self.CPProgress: w.deleteLater() for w in self.CPLabels: w.deleteLater() for w in self.CPWidgetsH: w.deleteLater() for w in self.anims: w.deleteLater() for w in self.graphics: w.deleteLater() for w in self.CPLoads: w.deleteLater() for w in self.CPWidgets: widget = w.parent() self.CPLayout.removeWidget(widget) w.deleteLater() widget.deleteLater() widget = None self.CPWidgets = [] self.CPImg = [] self.CPProgress = [] self.CPLabels = [] self.CPWidgetsH = [] self.anims = [] self.graphics = [] self.CPLoads = [] self.data = None def AddCP(self, addData=None, errors=None): if addData is not None: # this happens when we're in popup read-only mode self.addData = addData if self.addData is None: self.newcutplans.emit(False) return self.DeleteCP() if self.addData.shape[0] == 0: # just return if no cutplans to add self.newcutplans.emit(False) return try: conn = sqlconnect(LogScanner) except sqlProgrammingError: conn = sqlconnect(LogScanner) sqlfile = "support\\cpquery2.sql" f = open(sqlfile, 'r') sqltext = f.read() sqltext = sqltext.replace("@CPID", "-1") self.data = read_sql(sqltext, conn) self.CPNum = self.addData.shape[0] self.CPErrors = [] QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) for id in range(self.CPNum): self.CPLoads.append(QLabel(self)) self.CPLoads[id].setAlignment(Qt.AlignCenter) self.CPLoads[id].setMinimumSize(QSize(self.size, self.size)) self.CPLoads[id].setMaximumSize(QSize(self.size, self.size)) movie = QMovie("images\\loading.gif") movie.setScaledSize(QSize(self.size, self.size)) self.CPLoads[id].setMovie(movie) movie.start() self.CPLayout.addWidget(self.CPLoads[id]) self.CPErrors.append(False) if errors is not None: e = [] for i in range(errors.shape[0]): e.append(list(errors.iloc[i])) for j in range(errors.shape[1]): if errors.iloc[i, j] > 0: self.CPErrors[i] = True break self.loader.errors = e self.loader.id = 0 self.loader.cutplanid = self.addData.ID[0] self.loader.logcount = self.addData['Log Count'][0] self.thread.quit() self.thread.wait() self.thread.started.connect(self.loader.loadCutplan) self.threadconnected = True self.thread.start() # self.SimSchedule = SimSchedule(self.data, "logs//") if addData is None: # readonly will skip this self.newcutplans.emit(True) QApplication.restoreOverrideCursor() def AddOneCP(self, id): imgfolder = "images\\cps\\" data = self.loader.data speed = self.loader.speeds[id] if speed > self.speedsetting: speed = self.speedsetting widget = QWidget(self) widget.setObjectName('CPW' + str(id + 1)) widget.setMinimumSize(QSize(self.size, self.size)) widget.setMaximumSize(QSize(self.size, self.size)) self.CPWidgetsH.append(QPushButton(widget)) self.CPWidgetsH[id].setStyleSheet( "background-color: rgba(255, 255, 255, 0);") self.CPWidgetsH[id].setText("") icon = QIcon(imgfolder + "CP" + str(id + 1) + "_h.png") self.CPWidgetsH[id].setIcon(icon) self.CPWidgetsH[id].setIconSize(QSize(self.size, self.size)) self.CPWidgetsH[id].setObjectName('CPH' + str(id + 1)) self.CPWidgetsH[id].setGeometry(QRect(0, 0, self.size, self.size)) self.CPWidgets.append(HoverButton(widget, id)) self.CPWidgets[id].setMinimumSize(QSize(self.size, self.size)) self.CPWidgets[id].setStyleSheet( "background-color: rgba(255, 255, 255, 0);") self.CPWidgets[id].setText("") icon = QIcon(imgfolder + "CP" + str(id + 1) + ".png") self.CPImg.append('test' + str(id + 1)) self.CPWidgets[id].setIcon(icon) self.CPWidgets[id].setIconSize(QSize(self.size, self.size)) self.CPWidgets[id].setObjectName('CP' + str(id + 1)) self.CPWidgets[id].setGeometry(QRect(0, 0, self.size, self.size)) self.graphics.append(QGraphicsOpacityEffect(widget)) self.CPWidgets[id].setGraphicsEffect(self.graphics[id]) self.graphics[id].setOpacity(1) self.anims.append(QPropertyAnimation(self.graphics[id], b"opacity")) self.anims[id].setDuration(400) self.anims[id].setEasingCurve(QEasingCurve.InOutSine) self.anims[id].finished.connect(self.uiUpdate) self.CPProgress.append(CPProgress(id, parent=widget)) self.CPProgress[id].setObjectName('CPP' + str(id + 1)) self.CPProgress[id].setGeometry(QRect(0, 0, self.size, self.size)) if self.CPErrors[id]: self.CPProgress[id].c = (255, 121, 121) self.CPProgress[id].setValue(0) else: self.CPProgress[id].c = (255, 255, 255) self.CPProgress[id].setValue(100) self.CPLabels.append(QLabel(widget)) self.CPLabels[id].setObjectName('CPL' + str(id + 1)) self.CPLabels[id].setGeometry(QRect(10, 200, 60, 30)) font = QFont() font.setPointSize(6) font.setBold(True) font.setFamily("Tahoma") self.CPLabels[id].setFont(font) self.CPLabels[id].setStyleSheet( "QLabel {\n" " color: white;\n" " background-color: rgb(0, 115, 119);\n" " border-radius: 15px;\n" "}") self.CPLabels[id].setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) group = data.Description[0][2:4] length = data.Description[0][5:7] des = length[0] + "." + length[1] + "m " + group desPD = self.loader.des if desPD.shape[0] > 0: des = desPD.Description[0] self.CPLabels[id].setText( des[5:10] + " cm\n{:.1f} m\n".format(float(des[0:3]) - 0.1) + "{0} m/min".format(speed)) self.CPLayout.insertWidget(id * 2, widget) self.CPLoads[id].setVisible(False) self.setMinimumSize(QSize(self.size * (self.CPNum + 1), self.size)) self.CPProgress[id].mouseHover.connect(self.onHover) self.data = self.data.append(data.iloc[0], ignore_index=True) else: des = des + "-" + str(int(group) + 1) des = des[5:10] + "cm\n{:.1f}m".format(float(des[0:3]) - 0.1) QApplication.restoreOverrideCursor() self.ErrorBox( "Input Error", "Log group " + des + " doesn't exist.", "Cutplan \"" + data.Description[0] + "\" was skipped.") QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) if id + 1 < self.CPNum: self.thread.quit() self.thread.wait() self.loader.id = id + 1 self.loader.cutplanid = self.addData.ID[id + 1] self.loader.logcount = self.addData['Log Count'][id + 1] self.thread.start() else: self.thread.quit() self.thread.terminate() self.loader.errors = None self.cploadfinish.emit() def onHover(self, hover, id): self.anims[id].stop() if hover: self.anims[id].setStartValue(self.graphics[id].opacity()) self.anims[id].setEndValue(0) # self.CPWidgets[id].setIcon(icon) else: self.anims[id].setStartValue(self.graphics[id].opacity()) self.anims[id].setEndValue(1) # self.CPWidgets[id].setIcon(icon) self.anims[id].start() # QAbstractAnimation.DeleteWhenStopped) def uiUpdate(self): for w in self.CPWidgets: w.update() def ErrorBox(self, errortxt, description, info): msgbox = QMessageBox(self) msgbox.setWindowTitle(errortxt) msgbox.setText(description) msgbox.setInformativeText(info) msgbox.setStandardButtons(QMessageBox.Ok) msgbox.setIcon(QMessageBox.Critical) msgbox.setStyleSheet("#qt_msgbox_label {font-weight: bold;}") msgbox.exec() def retranslateUi(self, Form): _translate = QCoreApplication.translate Form.setWindowTitle(_translate("Form", "Form"))
class MainWindow(QMainWindow): """...""" def __init__(self, config_, tools_): super(MainWindow, self).__init__() self._config = config_ self._tools = tools_ self._setup_sub_thread() self._setup_ui() self.show() def _setup_ui(self): self.setWindowTitle('Wechat Toolkit') self.setGeometry(100, 100, 600, 400) self._set_menu_bar() self._set_main_window() def _set_main_window(self): _tab = QTabWidget() self._info_window = self._set_tool_window() self._console_window = GuiUtils.console_window() _tab.addTab(self._info_window, 'Tools Settings') _tab.addTab(self._console_window, 'Console Window') _tab.currentChanged.connect(self._save_tool) self.setCentralWidget(_tab) def _set_tool_window(self): _window = QWidget() _win_layout = QHBoxLayout() _tool_list = QWidget() _vbox = QVBoxLayout() self._all_tools = QCheckBox('Wechat Tools') self._all_tools.stateChanged.connect(self._on_check_all) self._tool_list = QListWidget() self._tool_list.setSelectionMode(QAbstractItemView.SingleSelection) for _t in self._tools: _enable = self._config[_t['config_key']]['enable'] _check_state = Qt.Checked if _enable else Qt.Unchecked _item = QListWidgetItem() _item.setText(_t['type']) _item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) _item.setCheckState(_check_state) self._tool_list.addItem(_item) self._tool_list.itemClicked.connect(self._on_tool_clicked) _vbox.addWidget(self._all_tools) _vbox.addWidget(self._tool_list) _tool_list.setLayout(_vbox) _win_layout.addWidget(_tool_list) self._friend_filter = ChatSetter('Friend Chat') self._friend_filter.saved.connect(self._save_filter) _win_layout.addWidget(self._friend_filter) self._group_filter = ChatSetter('Group Chat') self._group_filter.saved.connect(self._save_filter) _win_layout.addWidget(self._group_filter) self._default_tool = 0 self._tool_list.setCurrentRow(self._default_tool) self._display_tool_setting(self._default_tool) self._tool_list.currentItem().setBackground(QColor(0, 105, 217)) self._tool_list.currentItem().setForeground(QColor('white')) _window.setLayout(_win_layout) return _window def _on_check_all(self): if self._all_tools.isChecked(): for _index in range(self._tool_list.count()): self._tool_list.item(_index).setCheckState(Qt.Checked) else: for _index in range(self._tool_list.count()): self._tool_list.item(_index).setCheckState(Qt.Unchecked) def _on_tool_clicked(self, item_): self._tool_list.setCurrentItem(item_) for _index in range(self._tool_list.count()): self._tool_list.item(_index).setBackground(QColor('white')) self._tool_list.item(_index).setForeground(QColor('black')) item_.setBackground(QColor(0, 105, 217)) item_.setForeground(QColor('white')) _index = self._tool_list.currentRow() self._display_tool_setting(_index) def _display_tool_setting(self, tool_index_): _config = self._config[self._tools[tool_index_]['config_key']] _friend_enable = _config['friend_enable'] _group_enable = _config['group_enable'] _friend_filter = _config['friend_filter'] _group_filter = _config['group_filter'] self._friend_filter.enable(_friend_enable) self._group_filter.enable(_group_enable) self._friend_filter.display_filter(_friend_filter) self._group_filter.display_filter(_group_filter) def _save_filter(self, name_, enable_, filter_): _index = self._tool_list.currentRow() _config = self._config[self._tools[_index]['config_key']] if name_ == 'Friend Chat': _config['friend_enable'] = enable_ _config['friend_filter'] = filter_ elif name_ == 'Group Chat': _config['group_enable'] = enable_ _config['group_filter'] = filter_ def _save_tool(self): for _index in range(self._tool_list.count()): self._config[self._tools[_index]['config_key']]['enable'] = \ self._tool_list.item(_index).checkState() == Qt.Checked def _set_menu_bar(self): self._menu_bar = self.menuBar() self._menu_bar.setNativeMenuBar(False) _menu = { 'Main.Run': (self._run, 'Ctrl+R'), 'Main.Stop': (self._stop, 'Ctrl+T'), 'Main.Account': (self._test_action, 'Ctrl+A'), 'Main.About': (self._test_action, ''), 'Main.Quit': (self._quit, 'Ctrl+Q'), 'View.Show Log.Main': (self._test_action, ''), 'View.Show Log.Recall-Blocker': (self._test_action, ''), 'View.Show Log.Auto-Replier': (self._test_action, ''), 'View.Show Log.Auto-Repeater': (self._test_action, ''), 'View.Show Log.Combined': (self._test_action, ''), 'View.Clean Log': (self._clean_log, ''), 'Settings.Edit Settings': (self._test_action, 'Ctrl+Alt+S'), 'Settings.Import Settings': (self._test_action, 'Ctrl+Alt+I'), 'Settings.Export Settings': (self._test_action, 'Ctrl+Alt+E'), 'Help.Help': (self._test_action, '') } for _menu_name, _menu_params in _menu.items(): self._add_menu(_menu_name, _menu_params) _stop = self._get_menu_wgt('Main.Stop') _stop.setDisabled(True) def _add_menu(self, menu_name_, menu_params_, menu_=None): _menu = menu_ if menu_ else self._menu_bar _menu_group = menu_name_.split(Separator.OBJ) if len(_menu_group) == 1: _menu_btn = QAction(menu_name_, self) _menu_btn.triggered.connect(menu_params_[0]) _menu_btn.setShortcut(menu_params_[1]) GuiUtils.add_widget(_menu, _menu_btn, menu_name_, WidgetType.BTN) _menu.addAction(_menu_btn) else: _sub_menu_name = _menu_group.pop(0) _sub_menu = GuiUtils.get_widget_by_name( _menu, _sub_menu_name) or _menu.addMenu(_sub_menu_name) GuiUtils.add_widget(_menu, _sub_menu, _sub_menu_name, WidgetType.MENU) _rest_menu_name = Separator.OBJ.join(_menu_group) self._add_menu(_rest_menu_name, menu_params_, _sub_menu) def _get_menu_wgt(self, menu_name_, menu_=None): _menu = menu_ if menu_ else self._menu_bar _menu_group = menu_name_.split(Separator.OBJ) if len(_menu_group) == 1: return GuiUtils.get_widget_by_name(_menu, menu_name_) else: _sub_menu_name = _menu_group.pop(0) _sub_menu = GuiUtils.get_widget_by_name(_menu, _sub_menu_name) _rest_menu_name = Separator.OBJ.join(_menu_group) return self._get_menu_wgt(_rest_menu_name, _sub_menu) def _test_action(self): GuiUtils.info_dialog(self, 'Test', 'No integrated yet.') def _setup_sub_thread(self): self._sub_thread = QThread() self._wechat_tools = WechatThread(config_=self._config) self._wechat_tools.moveToThread(self._sub_thread) self._sub_thread.started.connect(self._wechat_tools.run) def _run(self): self._save_tool() self._sub_thread.start() self._get_menu_wgt('Main.Run').setDisabled(True) self._get_menu_wgt('Main.Stop').setDisabled(False) def _stop(self): self._wechat_tools.stop() self._sub_thread.terminate() self._sub_thread.wait() self._get_menu_wgt('Main.Run').setDisabled(False) self._get_menu_wgt('Main.Stop').setDisabled(True) def _quit(self): self.close() def closeEvent(self, event): """...""" if GuiUtils.question_dialog(self, 'Quit', 'Are you sure to quit wechat tools?'): event.accept() else: event.ignore() def _clean_log(self): self._console_window.text.clear()
class Sweep2D(BaseSweep, QObject): """ A 2-D Sweep of QCoDeS Parameters. This class runs by setting an outside parameter, then running an inner Sweep1D object. The inner sweep handles all data saving and communications through the Thread objects. Attributes --------- in_params: List defining the inner sweep [parameter, start, stop, step]. out_params: List defining the outer sweep [parameter, start, stop, step]. inter_delay: Time (in seconds) to wait between data points on inner sweep. outer_delay: Time (in seconds) to wait between data points on outer sweep. save_data: Flag used to determine if the data should be saved or not. plot_data: Flag to determine whether or not to live-plot data. complete_func: Sets a function to be executed upon completion of the outer sweep. update_func: Sets a function to be executed upon completion of the inner sweep. plot_bin: Defaults to 1. Controls amount of data stored in the data_queue list in the Plotter Thread. runner: Assigns the Runner Thread. plotter: Assigns the Plotter Thread. back_multiplier: Factor to scale the step size after flipping directions. heatmap_plotter: Uses color to represent values of a third parameter plotted against two sweeping parameters. Methods --------- follow_param(*p) Saves parameters to be tracked, for both saving and plotting data. follow_srs(self, l, name, gain=1.0) Adds an SRS lock-in to Sweep1D to ensure that the range is kept correctly. _create_measurement() Creates the measurement object for the sweep. start(ramp_to_start=True, persist_data=None) Extends the start() function of BaseSweep. stop() Stops the sweeping of both the inner and outer sweep. resume() Resumes the inner and outer sweeps. update_values() Updates plots and heatmap based on data from the inner and outer sweeps. get_param_setpoint() Obtains the current value of the setpoint. set_update_rule(func) Sets a function to be called upon completion of each inner sweep. send_updates(no_sp=False) Passed in Sweep2D. kill() Ends all threads and closes any active plots. ramp_to(value, start_on_finish=False, multiplier=1) Ramps the set_param to a given value, at a rate specified by multiplier. ramp_to_zero() done_ramping(start_on_finish=False) """ add_heatmap_lines = pyqtSignal(list) clear_heatmap_plot = pyqtSignal() def __init__(self, in_params, out_params, outer_delay=1, update_func=None, *args, **kwargs): """ Initializes the sweep. The inner sweep parameters('in_params') and outer sweep parameters ('out_params') MUST be a list, conforming to the following standard: [ <QCoDeS Parameter>, <start value>, <stop value>, <step size> ] Parameters --------- in_params: A list conforming to above standard for the inner sweep. out_params: A list conforming to above standard for the outer sweep. inter_delay: Time (in seconds) to wait between data points on inner sweep. outer_delay: Time (in seconds) to wait between data points on outer sweep. save_data: Flag used to determine if the data should be saved or not. plot_data: Flag to determine whether or not to live-plot data. complete_func: Sets a function to be executed upon completion of the outer sweep. update_func: Sets a function to be executed upon completion of the inner sweep. plot_bin: Defaults to 1. Controls amount of data stored in the data_queue list in the Plotter Thread. runner: Assigns the Runner Thread. plotter: Assigns the Plotter Thread. back_multiplier: Factor to scale the step size after flipping directions. heatmap_plotter: Uses color to represent values of a third parameter plotted against two sweeping parameters. """ # Ensure that the inputs were passed (at least somewhat) correctly if len(in_params) != 4 or len(out_params) != 4: raise TypeError('For 2D Sweep, must pass list of 4 object for each sweep parameter, \ in order: [ <QCoDeS Parameter>, <start value>, <stop value>, <step size> ]') # Save our input variables self.in_param = in_params[0] self.in_start = in_params[1] self.in_stop = in_params[2] self.in_step = in_params[3] # Ensure that the step has the right sign if (self.in_stop - self.in_start) > 0: self.in_step = abs(self.in_step) else: self.in_step = (-1) * abs(self.in_step) self.set_param = out_params[0] self.out_start = out_params[1] self.out_stop = out_params[2] self.out_step = out_params[3] self.out_setpoint = self.out_start if (self.out_stop - self.out_start) > 0: self.out_step = abs(self.out_step) else: self.out_step = (-1) * abs(self.out_step) # Initialize the BaseSweep QObject.__init__(self) BaseSweep.__init__(self, set_param=self.set_param, *args, **kwargs) # Create the inner sweep object self.in_sweep = Sweep1D(self.in_param, self.in_start, self.in_stop, self.in_step, bidirectional=True, inter_delay=self.inter_delay, save_data=self.save_data, x_axis_time=0, plot_data=self.plot_data, back_multiplier=self.back_multiplier) # We set our outer sweep parameter as a follow param for the inner sweep, so that # it is always read and saved with the rest of our data self.in_sweep.follow_param(self.set_param) # Our update_values() function iterates the outer sweep, so when the inner sweep # is done, call that function automatically self.in_sweep.set_complete_func(self.update_values) self.outer_delay = outer_delay # Flags for ramping self.inner_ramp = False self.outer_ramp = False self.ramp_sweep = None # Set the function to call when the inner sweep finishes if update_func is None: self.update_rule = self.no_change # Initialize our heatmap plotting thread self.heatmap_plotter = None def __str__(self): return f"2D Sweep of {self.set_param.label} from {self.out_start} to {self.out_stop} with step {self.out_step}," \ f"while sweeping {self.in_param.label} from {self.in_start} to {self.in_stop} with step {self.in_step}." def __repr__(self): return f"Sweep2D([{self.set_param.label}, {self.out_start}, {self.out_stop}, {self.out_step}], " \ f"[{self.in_param.label}, {self.in_start}, {self.in_stop}, {self.in_step}])" def follow_param(self, *p): """ Saves parameters to be tracked, for both saving and plotting data. Since the data saving is always handled by the inner Sweep1D object, all parameters are registered in the inner Sweep1D object. The parameters must be followed before '_create_measurement()' is called. Parameters --------- *p: Variable number of arguments, each of which must be a QCoDeS Parameter, or a list of QCoDeS Parameters, for the sweep to follow. """ for param in p: if isinstance(param, list): for l in param: self.in_sweep._params.append(l) else: self.in_sweep._params.append(param) self._params = self.in_sweep._params def follow_srs(self, l, name, gain=1.0): """ Adds an SRS lock-in to Sweep1D to ensure that the range is kept correctly. Parameters --------- l: The lock-in instrument. name: The user-defined name of the instrument. gain: The current gain value. """ self.in_sweep.follow_srs((l, name, gain)) def _create_measurement(self): """ Creates the measurement object for the sweep. The measurement object is created through the inner Sweep1D object. Returns --------- self.meas: The QCoDeS Measurement object responsible for running the sweep. """ self.meas = self.in_sweep._create_measurement() return self.meas def start(self, ramp_to_start=True, persist_data=None): """ Extends the start() function of BaseSweep. The first outer sweep setpoint is set, and the inner sweep is started. Parameters --------- ramp_to_start: Sets a sweep to gently iterate the parameter to its starting value. persist_data: Sets the outer parameter for Sweep2D. """ if self.is_running: print("Can't start the sweep, we're already running!") return elif self.outer_ramp: print("Can't start the sweep, we're currently ramping the outer sweep parameter!") return if self.meas is None: self._create_measurement() if ramp_to_start: self.ramp_to(self.out_setpoint, start_on_finish=True) else: print( f'Starting the 2D Sweep. Ramping {self.set_param.label} to {self.out_stop} {self.set_param.unit}, ' f'while sweeping {self.in_param.label} between {self.in_start} {self.in_param.unit} and {self.in_stop} ' f'{self.in_param.unit}') self.set_param.set(self.out_setpoint) time.sleep(self.outer_delay) self.is_running = True if self.heatmap_plotter is None: # Initialize our heatmap self.heatmap_plotter = Heatmap(self) self.heatmap_thread = QThread() self.heatmap_plotter.moveToThread(self.heatmap_thread) self.heatmap_plotter.create_figs() self.add_heatmap_lines.connect(self.heatmap_plotter.add_lines) self.clear_heatmap_plot.connect(self.heatmap_plotter.clear) self.heatmap_thread.start() self.in_sweep.start(persist_data=(self.set_param, self.out_setpoint)) self.plotter = self.in_sweep.plotter self.runner = self.in_sweep.runner def stop(self): """ Stops the sweeping of both the inner and outer sweep. """ self.is_running = False self.in_sweep.stop() def resume(self): """ Resumes the inner and outer sweeps. """ self.is_running = True self.in_sweep.start(persist_data=(self.set_param, self.out_setpoint), ramp_to_start=False) def update_values(self): """ Updates plots and heatmap based on data from the inner and outer sweeps. This function is automatically called upon completion of the inner sweep. The outer parameter is iterated and the inner sweep is restarted. If the stop condition is reached, the completed signal is emitted and the sweeps are stopped. """ # If this function was called from a ramp down to 0, a special case of sweeping, deal with that # independently if self.in_sweep.is_ramping: # We are no longer ramping to zero self.inner_ramp = False # Check if our outer ramp to zero is still going, and if not, then officially end # our ramping to zero if not self.outer_ramp: self.is_running = False self.inner_sweep.is_running = False print("Done ramping both parameters to zero") # Stop the function from running any further, as we don't want to check anything else return # print("trying to update heatmap") # Update our heatmap! lines = self.in_sweep.plotter.axes[1].get_lines() self.add_heatmap_lines.emit(lines) # Check our update condition self.update_rule(self.in_sweep, lines) # self.in_sweep.ramp_to(self.in_sweep.begin, start_on_finish=False) # while self.in_sweep.is_ramping == True: # time.sleep(0.5) # If we aren't at the end, keep going if abs(self.out_setpoint - self.out_stop) - abs(self.out_step / 2) > abs(self.out_step) * 1e-4: self.out_setpoint = self.out_setpoint + self.out_step time.sleep(self.outer_delay) print(f"Setting {self.set_param.label} to {self.out_setpoint} {self.set_param.unit}") self.set_param.set(self.out_setpoint) time.sleep(self.outer_delay) # Reset our plots self.in_sweep.reset_plots() self.in_sweep.start(persist_data=(self.set_param, self.out_setpoint)) # If neither of the above are triggered, it means we are at the end of the sweep else: self.is_running = False print(f"Done with the sweep, {self.set_param.label}={self.out_setpoint}") self.in_sweep.kill() self.completed.emit() def get_param_setpoint(self): """ Obtains the current value of the setpoint. """ s = f"{self.set_param.label} = {self.set_param.get()} {self.set_param.unit} \ \n{self.inner_sweep.set_param.label} = {self.inner_sweep.set_param.get()} {self.inner_sweep.set_param.unit}" return s def set_update_rule(self, func): """ Sets a function to be called upon completion of each inner sweep. Parameters --------- func: The function handle desired to set the update function. """ self.update_rule = func def send_updates(self, no_sp=False): pass def kill(self): """ Ends all threads and closes any active plots. """ self.in_sweep.kill() # Gently shut down the heatmap if self.heatmap_plotter is not None: self.clear_heatmap_plot.emit() self.heatmap_thread.exit() if not self.heatmap_thread.wait(1000): self.heatmap_thread.terminate() print('forced heatmap to terminate') self.heatmap_plotter = None def ramp_to(self, value, start_on_finish=False, multiplier=1): """ Ramps the set_param to a given value, at a rate specified by the multiplier. Parameter --------- value: The setpoint for the sweep to ramp to. start_on_finish: Flag to determine whether to begin the sweep when ramping is finished. multiplier: The multiplier for the step size, to ramp quicker than the sweep speed. """ # Ensure we aren't currently running if self.outer_ramp: print(f"Currently ramping. Finish current ramp before starting another.") return if self.is_running: print(f"Already running. Stop the sweep before ramping.") return # Check if we are already at the value curr_value = self.set_param.get() if abs(value - curr_value) <= self.out_step / 2: # print(f"Already within {self.step} of the desired ramp value. Current value: {curr_value}, # ramp setpoint: {value}.\nSetting our setpoint directly to the ramp value.") self.set_param.set(value) self.done_ramping(start_on_finish=True) return # Create a new sweep to ramp our outer parameter to zero self.ramp_sweep = Sweep1D(self.set_param, curr_value, value, multiplier * self.out_step, inter_delay=self.inter_delay, complete_func=partial(self.done_ramping, start_on_finish), save_data=False, plot_data=True) self.is_running = False self.outer_ramp = True self.ramp_sweep.start(ramp_to_start=False) print(f'Ramping {self.set_param.label} to {value} . . . ') def ramp_to_zero(self): """Ramps the set_param to 0, at the same rate as already specified. """ print("Ramping both parameters to 0.") # Ramp our inner sweep parameter to zero self.inner_ramp = True self.in_sweep.ramp_to(0) # Check our step sign if self.out_setpoint > 0: self.out_step = (-1) * abs(self.out_step) else: self.out_step = abs(self.out_step) # Create a new sweep to ramp our outer parameter to zero zero_sweep = Sweep1D(self.set_param, self.set_param.get(), 0, self.step, inter_delay=self.inter_delay, complete_func=self.done_ramping) self.is_running = True self.outer_ramp = True zero_sweep.start() def done_ramping(self, start_on_finish=False): """ Alerts the sweep that the ramp is finished. Parameters --------- start_on_finish: Sweep will be called to start immediately after ramping when set to True. """ # Our outer parameter has finished ramping self.outer_ramp = False if self.ramp_sweep is not None: self.ramp_sweep.kill() self.ramp_sweep = None # Check if our inner parameter has finished while self.in_sweep.is_ramping: time.sleep(0.5) # If so, tell the system we are done self.is_running = False print("Done ramping!") if start_on_finish: self.start(ramp_to_start=False) def estimate_time(self, verbose=True): in_time = self.in_sweep.estimate_time() n_lines = abs((self.out_start-self.out_stop)/self.out_step)+1 out_time = self.outer_delay * self.out_step t_est = in_time*n_lines + out_time hours = int(t_est / 3600) minutes = int((t_est % 3600) / 60) seconds = t_est % 60 if verbose is True: print(f'Estimated time for {repr(self)} to run: {hours}h:{minutes:2.0f}m:{seconds:2.0f}s') return t_est
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 Tachy2Gis: """QGIS Plugin Implementation.""" # Custom methods go here: def vertexReceived(self, line): newVtx = T2G_Vertex.fromGSI(line) self.mapTool.addVertex(vtx=newVtx) ## Clears the map canvas and in turn the vertexList def clearCanvas(self): self.mapTool.clear() ## Opens the field dialog in preparation of dumping new vertices to the target layer def dump(self): # the input table of the dialog is updated targetLayer = self.dlg.sourceLayerComboBox.currentLayer() # if the target layer holds point geometries, only the currently selected vertex is dumped and # removed from the list project = QgsProject.instance() QgsExpressionContextUtils.setProjectVariable(project, 'maxWerteAktualisieren', 'False') #QgsMessageLog.logMessage('Test', 'T2G Archäologie', Qgis.Info) if targetLayer.geometryType() == QgsWkbTypes.PointGeometry: # Timmel for i in range(0, len(self.vertexList)): #QgsExpressionContextUtils.setProjectVariable(project, 'SignalGeometrieNeu', 'True') self.dlg.vertexTableView.selectRow(i) self.vertexList.dumpToFile(targetLayer, self.fieldDialog.fieldData) #QgsExpressionContextUtils.setProjectVariable(project, 'SignalGeometrieNeu', 'False') # self.mapTool.deleteVertex() # otherwise the list is cleared #self.mapTool.clear() else: #QgsExpressionContextUtils.setProjectVariable(project, 'SignalGeometrieNeu', 'True') self.vertexList.dumpToFile(targetLayer, self.fieldDialog.fieldData) #QgsExpressionContextUtils.setProjectVariable(project, 'SignalGeometrieNeu', 'False') # otherwise the list is cleared #self.mapTool.clear() #QgsExpressionContextUtils.setProjectVariable(QgsProject.instance(), 'SignalGeometrieNeu', 'False') targetLayer.commitChanges() # Attributdialog mit Filter zum schreiben öffnen # letzte id ermitteln idfeld = targetLayer.dataProvider().fieldNameIndex('id') if targetLayer.maximumValue(idfeld) == None: idmaxi = 0 else: idmaxi = targetLayer.maximumValue(idfeld) if targetLayer.geometryType() == QgsWkbTypes.PointGeometry: query = "id >= " + str(int(idmaxi) - len(self.vertexList) + 1) else: query = "id = " + str(int(idmaxi)) self.messpunktAdd() targetLayer.startEditing() targetLayer.fields().at( 0).constraints().constraintDescription(), 'desc' #self.iface.openFeatureForm(targetLayer, 3, True) box = self.iface.showAttributeTable(targetLayer, query) box.exec_() self.mapTool.clear() targetLayer.commitChanges() QgsExpressionContextUtils.setProjectVariable(project, 'maxWerteAktualisieren', 'True') self.iface.mapCanvas().refreshAllLayers() targetLayer.removeSelection() def messpunktAdd(self): #T2G_Vertex.messliste.clear() #T2G_Vertex.messliste.append({'pointId': 1, 'targetX': 1, 'targetY': 1, 'targetZ': 1}) #T2G_Vertex.messliste.append({'pointId': 2, 'targetX': 1, 'targetY': 1, 'targetZ': 1}) #T2G_Vertex.messliste.append({'pointId': 3, 'targetX': 1, 'targetY': 1, 'targetZ': 1}) layer = QgsProject.instance().mapLayersByName('Messpunkte')[0] layer.startEditing() for item in T2G_Vertex.messliste: ptnr = str(item['pointId']).lstrip("0") x = str(item['targetX']) y = str(item['targetY']) z = str(item['targetZ']) # Timmel Spiegelhöhe try: project = QgsProject.instance() reflH = QgsExpressionContextUtils.projectScope( project).variable('reflH') reflH = float(reflH) z = round(float(z) - reflH, 3) except: pass pt = QgsPoint(float(x), float(y), float(z)) attL = {'ptnr': ptnr, 'x': x, 'y': y, 'z': z, 'reflH': reflH} self.addPoint3D(layer, pt, attL) QgsMessageLog.logMessage( str(ptnr) + '|' + str(x) + '|' + str(y) + '|' + str(z), 'Messpunkte', Qgis.Info) layer.commitChanges() layer.removeSelection() def addPoint3D(self, layer, point, attListe): #layer.startEditing() feature = QgsFeature() fields = layer.fields() feature.setFields(fields) feature.setGeometry(QgsGeometry(point)) # Attribute layer.dataProvider().addFeatures([feature]) layer.updateExtents() features = [feature for feature in layer.getFeatures()] lastfeature = features[-1] for item in attListe: #QgsMessageLog.logMessage(str(item), 'T2G', Qgis.Info) fIndex = layer.dataProvider().fieldNameIndex(item) layer.changeAttributeValue(lastfeature.id(), fIndex, attListe[item]) T2G_VertexList.addStaticAttribut(self, layer, lastfeature) #layer.commitChanges() def snap(self): if self.dlg.checkBox.isChecked(): T2G_VertexList.snap = 1 else: T2G_VertexList.snap = 0 ## Restores the map tool to the one that was active before T2G was started # The pan tool is the default tool used by QGIS def restoreTool(self): if self.previousTool is None: self.previousTool = QgsMapToolPan(self.iface.mapCanvas()) self.iface.mapCanvas().setMapTool(self.previousTool) self.iface.actionSelectRectangle().trigger() def setActiveLayer(self): #Timmel if Qt is None: return activeLayer = self.dlg.sourceLayerComboBox.currentLayer() if activeLayer is None or activeLayer.type( ) == QgsMapLayer.RasterLayer: return self.iface.setActiveLayer(activeLayer) self.vertexList.updateAnchors(activeLayer) def targetChanged(self): targetLayer = self.fieldDialog.targetLayerComboBox.setLayer # Timmel self.mapTool.setGeometryType(targetLayer) def toggleEdit(self): iface.actionToggleEditing().trigger() def connectSerial(self): port = self.dlg.portComboBox.currentText() self.tachyReader.setPort(port) def setLog(self): logFileName = QFileDialog.getSaveFileName()[0] self.dlg.logFileEdit.setText(logFileName) self.tachyReader.setLogfile(logFileName) def dumpEnabled(self): verticesAvailable = (len(self.vertexList) > 0 ) # tim > durch >= ersetzt # Selecting a target layer while there are no vertices in the vertex list may cause segfaults. To avoid this, # the 'Dump' button is disabled as long there are none: self.dlg.dumpButton.setEnabled(verticesAvailable) # Interface code goes here: def setupControls(self): """This method connects all controls in the UI to their callbacks. It is called in ad_action""" portNames = [ port.portName() for port in QSerialPortInfo.availablePorts() ] self.dlg.portComboBox.addItems(portNames) self.dlg.portComboBox.currentIndexChanged.connect(self.connectSerial) self.dlg.logFileButton.clicked.connect(self.setLog) self.dlg.deleteAllButton.clicked.connect(self.clearCanvas) self.dlg.finished.connect(self.mapTool.clear) self.dlg.dumpButton.clicked.connect(self.dump) self.dlg.deleteVertexButton.clicked.connect(self.mapTool.deleteVertex) self.dlg.vertexTableView.setModel(self.vertexList) self.dlg.vertexTableView.horizontalHeader().setSectionResizeMode( 0, QHeaderView.Stretch) self.dlg.vertexTableView.setSelectionModel( QItemSelectionModel(self.vertexList)) self.dlg.vertexTableView.selectionModel().selectionChanged.connect( self.mapTool.selectVertex) self.dlg.finished.connect(self.restoreTool) self.dlg.accepted.connect(self.restoreTool) self.dlg.rejected.connect(self.restoreTool) self.dlg.sourceLayerComboBox.layerChanged.connect(self.setActiveLayer) self.dlg.sourceLayerComboBox.layerChanged.connect(self.mapTool.clear) self.fieldDialog.targetLayerComboBox.layerChanged.connect( self.targetChanged) self.vertexList.layoutChanged.connect(self.dumpEnabled) self.dlg.checkBox.stateChanged.connect(self.snap) ## 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. def __init__(self, iface): # 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', 'Tachy2Gis_{}.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('&Tachy2GIS') self.toolbar = self.iface.addToolBar('Tachy2Gis') self.toolbar.setObjectName('Tachy2Gis') ## From here: Own additions self.vertexList = T2G_VertexList() self.mapTool = T2G_VertexePickerTool(self) self.previousTool = None self.fieldDialog = FieldDialog(self.iface.activeLayer()) self.tachyReader = TachyReader(QSerialPort.Baud9600) self.pollingThread = QThread() self.tachyReader.moveToThread(self.pollingThread) self.pollingThread.start() self.tachyReader.lineReceived.connect(self.vertexReceived) self.tachyReader.beginListening() # 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('Tachy2Gis', 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 """ # Create the dialog (after translation) and keep reference self.dlg = Tachy2GisDialog( iface.mainWindow()) # Dialog im Vordergrund halten. self.setupControls() 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.iface.addToolBarIcon(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 = ':/plugins/Tachy2Gis/icon.png' self.add_action(icon_path, text=self.tr('Tachy2GIS'), callback=self.run, parent=self.iface.mainWindow()) def unload(self): """Removes the plugin menu item and icon from QGIS GUI.""" for action in self.actions: self.iface.removePluginMenu(self.tr('&Tachy2GIS'), action) self.iface.removeToolBarIcon(action) # remove the toolbar del self.toolbar if self.pollingThread.isRunning(): self.tachyReader.shutDown() self.pollingThread.terminate() self.pollingThread.wait() def run(self): """Run method that performs all the real work""" # Store the active map tool and switch to the T2G_VertexPickerTool self.previousTool = self.iface.mapCanvas().mapTool() self.iface.mapCanvas().setMapTool(self.mapTool) self.mapTool.alive = True #self.setActiveLayer() self.dlg.sourceLayerComboBox.setLayer(self.iface.activeLayer()) self.dlg.show() outLayerList = [] self.layerLine = QgsProject.instance().mapLayersByName('E_Line')[0] self.layerPoly = QgsProject.instance().mapLayersByName('E_Polygon')[0] self.layerPoint = QgsProject.instance().mapLayersByName('E_Point')[0] for lay in QgsProject.instance().mapLayers().values(): if lay == self.layerLine or lay == self.layerPoly or lay == self.layerPoint: QgsMessageLog.logMessage('gleich', 'T2G Archäologie', Qgis.Info) pass else: QgsMessageLog.logMessage(lay.name(), 'T2G Archäologie', Qgis.Info) outLayerList.append(lay) self.dlg.sourceLayerComboBox.setExceptedLayerList(outLayerList)
class T2G_VertexList(QAbstractTableModel): ## The color of unselected vertice VERTEX_COLOR = Qt.red ### Selected vertices get a different color SELECTED_COLOR = Qt.green signal_feature_dumped = pyqtSignal() ## Ctor # @param vertices the vertex list can be initialized with a list of vertices # @param parent included to match the ctor of QAbstractTableModel # @param args see above def __init__(self, vertices=[], parent=None, *args): QAbstractTableModel.__init__(self, parent, *args) self.columnCount = len(T2G_Vertex().fields()) self.vertices = vertices self.colors = [] self.shapes = [] self.anchorPoints = [] self.anchorIndex = QgsSpatialIndex() self.selected = None self.maxIndex = None self.updateThread = QThread() self.anchorUpdater = None self.layer = None ## Reimplemented from QAbstractTableModel def rowCount(self, *args, **kwargs): return len(self) ## Reimplemented from QAbstractTableModel def columnCount(self, *args, **kwargs): return self.columnCount ## Reimplemented from QAbstractTableModel def data(self, index, role): if Qt is None: return if not index.isValid(): return elif role != Qt.DisplayRole: return row = index.row() col = index.column() vertex = self.vertices[row] field = vertex.fields()[col] return field ## Reimplemented from QAbstractTableModel def headerData(self, section, orientation, role): if Qt is None: return if orientation == Qt.Horizontal and role == Qt.DisplayRole: headers = T2G_Vertex.HEADERS return headers[section] return QAbstractTableModel.headerData(self, section, orientation, role) ## this method updates anchor points for snapping # extracting all vertices from the geometries in a layer may take some time, # so the method reports its progress via a dialog that allows aborting the # operation when it takes too long. # @param layer: The currently active layer def updateAnchors(self, layer): # Snapping is driven by a spatial index of all distinct existing vertices # the index is 2D only, so 3-4D information has to be stored elsewhere # (self.anchorPoints in this case). # self.anchorPoints holds wkt representation of all vertices that can be # passed to the ctor of a new T2G_Vertex. self.anchorIndex = QgsSpatialIndex() self.anchorPoints = [] if layer is None: # empty or nonexisting layers leave us with an empty point list and index. return self.layer = layer # Initializing the progress dialog aud = AnchorUpdateDialog.AnchorUpdateDialog() aud.abortButton.clicked.connect(self.abortUpdate) aud.geometriesBar.setMaximum(layer.featureCount()) aud.geometriesBar.setValue(0) aud.anchorBar.setValue(0) # the layer is passed to the anchorUpdater and the updater is moved to # a new thread self.anchorUpdater = AnchorUpdater(layer=layer) self.anchorUpdater.moveToThread(self.updateThread) self.updateThread.start() self.anchorUpdater.signalAnchorCount.connect(aud.setAnchorCount) self.anchorUpdater.signalAnchorProgress.connect(aud.anchorProgress) self.anchorUpdater.signalGeometriesProgress.connect( aud.geometriesProgress) # the dialog is displayed and extraction is started aud.show() self.anchorUpdater.startExtraction() # the abort method of the updater clears its index and points list, so # we can just use them, even if we aborted. They will be empty in that # case. self.anchorIndex = self.anchorUpdater.anchorIndex self.anchorPoints = self.anchorUpdater.anchorPoints ## checks if there are anchors # @return bool def hasAnchors(self): return len(self.anchorPoints) > 0 ## Tells the update thread to stop and cleans up after it @pyqtSlot() def abortUpdate(self): if self.updateThread.isRunning(): # abortExtraction lets the process exit the extraction loop and # resets its anchorIndex and anchorPoints self.anchorUpdater.abortExtraction() self.updateThread.terminate() self.updateThread.wait() ## makes the vertexList look more like a regular list # @return an int representing the number of vertices on the list def __len__(self): return self.vertices.__len__() ## allows to access vertices by index: 'vertexList[2]' works as expected. # @param index the index of the requested vertex # @return the T2G_Vertex at 'index' def __getitem__(self, index): return self.vertices.__getitem__(index) ## Allows appending new vertices. Manually created vertices will be snapped # to the nearest anchor. # @param vertex the vertex to append, is assumed to be of type T2G_Vertex # @return the probably modified vertex def append(self, vertex): if vertex.source == T2G_Vertex.SOURCE_INTERNAL: anchorId = self.anchorIndex.nearestNeighbor( vertex.getQgsPointXY(), 1) # nearestNeighbour returns a list. It is not unpacked yet, because # doing so causes errors if it is empty, also index '0' is interpreted # as 'False' and gets ignored if anchorId: wkt = self.anchorPoints[anchorId[0]] vertex.setWkt(wkt) self.vertices.append(vertex) self.layoutChanged.emit() return vertex ## removes a vertex and emits a signal to update the tableView def deleteVertex(self, index): del self.vertices[index] self.layoutChanged.emit() def select(self, index): if index >= len(self): return self.selected = index def clearSelection(self): self.selected = None ## This method is used to get colors for vertex markers that let you # distinguish between selected and unselected vertices # @return a list of QColors def getColors(self): colors = [] for i, vertex in enumerate(self.vertices): if i == self.selected: colors.append(self.SELECTED_COLOR) else: colors.append(self.VERTEX_COLOR) return colors ## Deletes all vertices and emits a signal to update the tableView def clear(self): self.vertices = [] self.layoutChanged.emit() ## returns the coordinates of all vertices in a format that is useful for # shapefile.writer # @return a double nested list of coordinates. def getParts(self): return [[v.getCoords() for v in self.vertices]] ## Turns the vertices into geometry and writes them to a shapefile # @param targetLayer a vectordatalayer that is suspected to be based on a # shapefile (required to do so, actually) # @param fieldData the attributes of the new feature as list def dumpToFile(self, targetLayer, fieldData): # nonexistant and non-shapefile layers will be ignored if targetLayer is None: return if not targetLayer.dataProvider().name() == 'ogr': return # the absolute path to the shapefile is extracted from its URI dataUri = targetLayer.dataProvider().dataSourceUri() targetFileName = os.path.splitext(dataUri.split('|')[0])[0] add_shape(targetFileName, self.getParts(), fieldData) self.signal_feature_dumped.emit() def get_qgs_points(self): return [vertex.getQgsPointXY() for vertex in self.vertices]
class QtCodeGen(QtWidgets.QDialog): # Folgende Widgets stehen zur Verfügung: def __init__(self, kontaktdaten: dict, ROOT_PATH: str, parent = None): super().__init__(parent) uic.loadUi(os.path.join(PATH, "ui_qtcodegen.ui"), self) self.setupUi(self, ROOT_PATH) self.parent = parent self._hardClose = False # Attribute erstellen self.kontaktdaten = kontaktdaten self.ROOT_PATH = ROOT_PATH # std.out & error auf das Textfeld umleiten sys.stdout = EigenerStream(text_schreiben=self.update_ausgabe) sys.stderr = EigenerStream(text_schreiben=self.update_ausgabe) # Entsprechend Konfigurieren self.setup_thread() self.thread.start() # show gui self.show() def __del__(self): print("QtCodeGen destruct") def setupUi(self, QtCodeGen, ROOT_PATH): self.setObjectName("QtCodeGen") self.setWindowModality(QtCore.Qt.WindowModal) self.setModal(False) self.resize(700, 300) self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowContextHelpButtonHint); self.setWindowIcon(QIcon(os.path.join(ROOT_PATH, "images/spritze.ico"))) def setup_thread(self): """ Thread + Worker erstellen und Konfigurieren """ self.thread = QThread(parent=self) self.thread.setTerminationEnabled(True) self.worker = Worker(self.kontaktdaten, self.ROOT_PATH) # Worker und Thread verbinden self.worker.moveToThread(self.thread) # Signale setzen self.thread.started.connect(self.worker.code_gen) self.worker.signalShowInput.connect(self.showInputDlg) self.worker.signalShowDlg.connect(self.showDlg) def update_ausgabe(self, text): """ Fügt den übergeben Text dem textAusgabe hinzu Args: text (str): Text welcher hinzukommen soll """ # Austausch der Farbcodes / Ascii Zeichen aus der Shell listeCodes = ['\033[95m', '\033[91m', '\033[33m', '\x1b[0m', '\033[94m', '\033[32m', '\033[0m'] for farbcode in listeCodes: if farbcode in text: if farbcode == '\033[95m' or farbcode == '\033[91m': text = f"<div style='color:red'>{text}</div>" elif farbcode == '\033[33m': text = f"<div style='color:orange'>{text}</div>" elif farbcode == '\x1b[0m': text = f"<div>{text}</div>" elif farbcode == '\033[94m': text = f"<div style='color:blue'>{text}</div>" elif farbcode == '\033[32m': text = f"<div style='color:green'>{text}</div>" text = text.replace(farbcode, '') cursor = self.textAusgabe.textCursor() cursor.movePosition(QtGui.QTextCursor.End) cursor.insertHtml(str(text)) cursor.insertText(str("\n")) self.textAusgabe.setTextCursor(cursor) self.textAusgabe.ensureCursorVisible() def showInputDlg(self, dlgType): if dlgType == "GEBURTSDATUM": while True: try: text, ok = QtWidgets.QInputDialog.getText(self, 'Geburtsdatum', 'Bitte trage nachfolgend dein Geburtsdatum im Format DD.MM.YYYY ein.\n' 'Beispiel: 02.03.1982\n') if ok: geburtsdatum = str(text) validate_datum(geburtsdatum) self.worker.signalUpdateData.emit("GEBURTSDATUM",geburtsdatum) break else: self.hardClose() break except ValidationError as exc: QtWidgets.QMessageBox.critical(self, "Geburtsdatum ungültiges Format", "Das Datum entspricht nicht dem richtigen Format (DD.MM.YYYY).") elif dlgType == "SMSCODE_OK": ret = QtWidgets.QMessageBox.information(self, "Erfolgreich", "Code erfolgreich generiert. Du kannst jetzt mit der Terminsuche fortfahren.",QMessageBox.StandardButton.Ok) if ret == QMessageBox.StandardButton.Ok: self.worker.signalUpdateData.emit("SMSCODE_OK","") self.hardClose() def showDlg(self, strMode, strTxt): if strMode == "MISSING_KONTAKT": ret = QtWidgets.QMessageBox.critical(self, "Kontaktdaten ungültig", "Die Kontakdaten sind nicht korrekt!.\n\nBitte Datei neu erstellen!", QMessageBox.StandardButton.Ok) if ret == QMessageBox.StandardButton.Ok: self.hardClose() elif strMode == "CRITICAL_CLOSE": ret = QtWidgets.QMessageBox.critical(self, "Error", strTxt, QMessageBox.StandardButton.Ok) if ret == QMessageBox.StandardButton.Ok: self.hardClose() # force to close the dialog without confirmation def hardClose(self): self._hardClose = True self.close() def closeEvent(self, event): """ Wird aufgerufen, wenn die Anwendung geschlossen wird """ if self.thread.isRunning(): if self._hardClose is False: res = QtWidgets.QMessageBox.warning(self, "Suche beenden", "Suche wirklich beenden?\n", (QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel)) if res != QMessageBox.StandardButton.Ok: event.ignore() return #exit #stop worker self.worker.stop() self.worker.deleteLater() #stop thread self.thread.quit() self.thread.wait(3000) self.thread.terminate() # Streams wieder korrigieren, damit kein Fehler kommt sys.stdout = sys.__stdout__ sys.stderr = sys.__stderr__ self.deleteLater() event.accept() @staticmethod def start_code_gen(kontaktdaten: dict, ROOT_PATH: str): app = QtWidgets.QApplication(list()) window = QtCodeGen(kontaktdaten, ROOT_PATH) app.exec_()
class MainWindow(QMainWindow, gui.Ui_MainWindow): def __init__(self): super(self.__class__, self).__init__() self.setupUi(self) self.thread = QThread() self.tests = [] self.filelist = [] self.filename = os.path.expanduser('~/Documents/FTPDownloaderDefault.pickle') if os.path.isfile(self.filename): with open(self.filename, 'rb') as f: self.tests, self.filelist, self.loadIP, self.loadPass, self.loadReq, self.loadUser = pickle.load(f) self.lineIP.setText(self.loadIP) self.lineUser.setText(self.loadUser) self.linePass.setText(self.loadPass) self.lineReq.setText(self.loadReq) self.load_archivos() self.load_casos() self.downloader = Downloader(self.lineIP.text(), self.lineUser.text(), self.linePass.text(), self.lineReq.text(), self.filelist, self.tests) self.downloader.moveToThread(self.thread) self.downloader.log.connect(self.print_log) self.downloader.progress.connect(self.descargado) self.thread.started.connect(self.downloader.prepara_descarga) self.downloader.finished.connect(self.terminado) self.actionNuevo.triggered.connect(self.reset_all) self.actionLoad.triggered.connect(self.load_template) self.actionSave.triggered.connect(self.save_as) self.actionCerrar.triggered.connect(sys.exit) self.pushAddPrueba.clicked.connect(self.add_caso) self.lineCaso.returnPressed.connect(self.add_caso) self.pushAddArchivo.clicked.connect(self.add_archivo) self.lineNombreMainframe.returnPressed.connect(self.add_archivo) self.pushCargarArchivos.clicked.connect(self.load_list) self.pushClearArchivos.clicked.connect(self.clear_archivos) self.pushClearPruebas.clicked.connect(self.clear_pruebas) self.pushDeletePrueba.clicked.connect(self.borrar_caso) self.pushDeleteArchivo.clicked.connect(self.borrar_archivo) self.pushRenamePrueba.clicked.connect(self.renombrar_caso) self.pushRenameArchivo.clicked.connect(self.renombrar_archivo) self.pushDownload.clicked.connect(self.start_downloads) self.pushStop.clicked.connect(self.stop_process) self.pushButton.clicked.connect(self.about) atexit.register(self.save_state) def save_as(self): filename = QFileDialog.getSaveFileName(QFileDialog(), 'Guardar como', os.path.expanduser('~/Documents/'), '*.pickle') if filename[0]: save_ip = self.lineIP.text() save_user = self.lineUser.text() save_pass = self.linePass.text() save_req = self.lineReq.text() with open(filename[0] + '.pickle', 'wb') as s: pickle.dump([self.tests, self.filelist, save_ip, save_pass, save_req, save_user], s) self.print_log('Se guardo la plantilla en {}'.format(filename[0])) def load_template(self): filename = QFileDialog.getOpenFileName(QFileDialog(), 'Abrir', os.path.expanduser('~/Documents/'), '*.pickle') if filename[0]: with open(filename[0], 'rb') as f: self.tests, self.filelist, self.loadIP, self.loadPass, self.loadReq, self.loadUser = pickle.load(f) self.lineIP.setText(self.loadIP) self.lineUser.setText(self.loadUser) self.linePass.setText(self.loadPass) self.lineReq.setText(self.loadReq) self.load_archivos() self.load_casos() self.print_log('Se cargo la planitlla {}'.format(filename[0])) def load_list(self): filename = QFileDialog.getOpenFileName(QFileDialog(), 'Abrir') if filename[0]: with open(filename[0], 'r') as f: files = f.read().splitlines() for i in files: self.filelist.append(('', i.upper(), False)) self.load_archivos() def print_log(self, message): log = '[{}] {}'.format(strftime("%H:%M:%S", localtime()), message) self.plainTextLog.appendPlainText(log) def load_casos(self): self.listPruebas.clear() for caso in self.tests: self.listPruebas.addItem(caso.capitalize()) def load_archivos(self): self.listArchivos.clear() for item in self.filelist: self.listArchivos.addItem( '{} - {} - {}'.format(item[0], item[1].upper(), 'Tx' if item[2] == True else 'Único')) def add_caso(self): test = self.lineCaso.text() if test: self.tests.append(test.capitalize()) self.lineCaso.clear() self.load_casos() else: self.print_log('Campo Caso vacio.') def add_archivo(self): mainframe = self.lineNombreMainframe.text() nombre = self.lineNombre.text() tx = True if self.checkTx.isChecked() else False if mainframe: if not nombre: self.print_log('Nombre vacio. Se nombrara como archivo en mainframe.') self.filelist.append((nombre, mainframe.upper(), tx)) self.lineNombreMainframe.clear() self.lineNombre.clear() self.load_archivos() else: self.print_log('Nombre en Mainframe vacio.') def clear_archivos(self): confirm = self.del_confirmation('Reset', 'Desea borrar todos los archivos?') if confirm == gui.QtWidgets.QMessageBox.Yes: self.filelist.clear() self.listArchivos.clear() self.load_archivos() def clear_pruebas(self): confirm = self.del_confirmation('Reset', 'Desea borrar todas las pruebas?') if confirm == gui.QtWidgets.QMessageBox.Yes: self.tests.clear() self.listPruebas.clear() self.load_casos() def borrar_caso(self): index = self.listPruebas.currentRow() if index == -1: self.print_log('Seleccionar item.') else: self.tests.pop() self.load_casos() def borrar_archivo(self): index = self.listArchivos.currentRow() if index == -1: self.print_log('Seleccionar item.') else: self.filelist.pop(index) self.load_archivos() def renombrar_caso(self): new_name = self.lineCaso.text() index = self.listPruebas.currentRow() if not new_name: self.print_log('Ingresar nombre nuevo') elif index == -1: self.print_log('Seleccionar item.') else: self.tests[index] = new_name self.lineCaso.clear() self.load_casos() def renombrar_archivo(self): index = self.listArchivos.currentRow() new_name = self.lineNombre.text() new_mainframe_name = self.lineNombreMainframe.text() new_state = True if self.checkTx.isChecked() else False if not new_mainframe_name: self.print_log('Ingresar nombre nuevo.') elif index == -1: self.print_log('Seleccionar item.') else: self.filelist[index] = (new_name, new_mainframe_name, new_state) self.lineNombre.clear() self.lineNombreMainframe.clear() self.load_archivos() def reset_all(self): confirm = self.del_confirmation('Reset', 'Se borrarán todos los datos,\n' 'excepto la info de login.\n' 'Desea continuar?') if confirm == QMessageBox.Yes: self.clear_archivos() self.clear_pruebas() self.lineReq.clear() self.lineCaso.clear() self.lineNombreMainframe.clear() self.lineNombre.clear() self.checkTx.setCheckState(0) self.plainTextLog.clear() def save_state(self): save_ip = self.lineIP.text() save_user = self.lineUser.text() save_pass = self.linePass.text() save_req = self.lineReq.text() with open(self.filename, 'wb') as s: pickle.dump([self.tests, self.filelist, save_ip, save_pass, save_req, save_user], s) def start_downloads(self): progress_max = 100 if not self.thread.isRunning(): if len(self.tests) == 0: progress_max = len(self.filelist) else: progress_max = len(self.tests) * len(self.filelist) self.progressBar.setMaximum(progress_max) print(progress_max) print(self.progressBar.value()) self.progressBar.setValue(0) self.thread.start() else: self.print_log('Hay una descarga en proceso. Cancelar o reintentar al finalizar.') def terminado(self): self.thread.terminate() self.progressBar.setValue(len(self.tests) * len(self.filelist)) def stop_process(self): if self.thread.isRunning(): self.downloader.cancelar.emit() else: self.print_log('No hay descargas en curso.') def descargado(self): self.progressBar.setValue(self.progressBar.value() + 1) print(self.progressBar.value()) def about(self): QMessageBox.information(self, 'About', ' \'FTPDownloader 2016\' \n Ignacio Freire', QMessageBox.Ok) def del_confirmation(self, title, message): choice = QMessageBox.question(self, title, message, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) return choice
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 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 SpaceMApp(QMainWindow, Ui_MainWindow): def __init__(self, parent=None): super(SpaceMApp, self).__init__(parent) self.step = 2 self.setupUi(self) self.tabWidget.setCurrentWidget( self.tabWidget.findChild(QWidget, 'global_vars')) self.setup_tabs() self.setup_workers() self.connect_callbacks() self.set_btns_static_state() def setup_workers(self): self.setup_full_thread() self.setup_am_thread() self.setup_fid_filter_thread() self.setup_reg_image_thread() self.setup_grab_ms_data_thread() self.setup_cellprof_thread() self.setup_cellprof_postproc_thread() self.setup_gen_csv_thread() def setup_tabs(self): gv() gsTab(self) amfTab(self) gmsTab(self) genCsvTab(self) def setup_full_thread(self): self.thread_full = QThread() self.worker = FullPipeWorker() self.worker.moveToThread(self.thread_full) self.thread_full.started.connect(self.worker.work_full) self.worker.finishPipeSig.connect(self.fullpipe_finished) self.worker.progressBarSig.connect(self.update_pb) self.worker.pipeStatusToLogger.connect(self.update_logger) def setup_am_thread(self): self.thread_0 = QThread() self.worker_0 = AMWorker() self.worker_0.moveToThread(self.thread_0) self.thread_0.started.connect(self.worker_0.work_1) self.worker_0.progressBarSig.connect(self.update_pb) self.worker_0.pipeStatusToLogger.connect(self.update_logger) self.worker_0.incrementStepSig.connect(self.increment_step) self.worker_0.changeTabSig.connect(self.update_tab) self.thread_1 = QThread() self.worker_1 = AMWorker() self.worker_1.moveToThread(self.thread_1) self.thread_1.started.connect(self.worker_1.work_2) self.worker_1.progressBarSig.connect(self.update_pb) self.worker_1.pipeStatusToLogger.connect(self.update_logger) self.worker_1.incrementStepSig.connect(self.increment_step) self.worker_1.changeTabSig.connect(self.update_tab) def setup_fid_filter_thread(self): self.thread_2 = QThread() self.worker_2 = FidFilterWorker() self.worker_2.moveToThread(self.thread_2) self.thread_2.started.connect(self.worker_2.work_1) self.worker_2.progressBarSig.connect(self.update_pb) self.worker_2.pipeStatusToLogger.connect(self.update_logger) self.worker_2.incrementStepSig.connect(self.increment_step) self.worker_2.changeTabSig.connect(self.update_tab) def setup_reg_image_thread(self): self.thread_3 = QThread() self.worker_3 = RegImageWorker() self.worker_3.moveToThread(self.thread_3) self.thread_3.started.connect(self.worker_3.work_1) self.worker_3.progressBarSig.connect(self.update_pb) self.worker_3.pipeStatusToLogger.connect(self.update_logger) self.worker_3.incrementStepSig.connect(self.increment_step) self.worker_3.changeTabSig.connect(self.update_tab) def setup_grab_ms_data_thread(self): self.thread_4 = QThread() self.worker_4 = GrabMSDataWorker() self.worker_4.moveToThread(self.thread_4) self.thread_4.started.connect(self.worker_4.work_1) self.worker_4.progressBarSig.connect(self.update_pb) self.worker_4.pipeStatusToLogger.connect(self.update_logger) self.worker_4.incrementStepSig.connect(self.increment_step) self.worker_4.changeTabSig.connect(self.update_tab) def setup_cellprof_thread(self): self.thread_5 = QThread() self.worker_5 = CPWorker() self.worker_5.moveToThread(self.thread_5) self.thread_5.started.connect(self.worker_5.work_1) self.worker_5.progressBarSig.connect(self.update_pb) self.worker_5.pipeStatusToLogger.connect(self.update_logger) self.worker_5.incrementStepSig.connect(self.increment_step) self.worker_5.changeTabSig.connect(self.update_tab) def setup_cellprof_postproc_thread(self): self.thread_6 = QThread() self.worker_6 = CPWorker() self.worker_6.moveToThread(self.thread_6) self.thread_6.started.connect(self.worker_6.work_2) self.worker_6.progressBarSig.connect(self.update_pb) self.worker_6.pipeStatusToLogger.connect(self.update_logger) self.worker_6.incrementStepSig.connect(self.increment_step) self.worker_6.changeTabSig.connect(self.update_tab) def setup_gen_csv_thread(self): self.thread_7 = QThread() self.worker_7 = GenCSVWorker() self.worker_7.moveToThread(self.thread_7) self.thread_7.started.connect(self.worker_7.work_1) self.worker_7.progressBarSig.connect(self.update_pb) self.worker_7.pipeStatusToLogger.connect(self.update_logger) self.worker_7.incrementStepSig.connect(self.increment_step) self.worker_7.changeTabSig.connect(self.update_tab) def set_btns_static_state(self): self.btnImprtSettings.setEnabled(True) self.btnSaveSettings.setEnabled(True) if self.step == 8: self.btnRepeatStep.setEnabled(False) else: self.btnRepeatStep.setEnabled(True) self.btnRunNextStep.setEnabled(True) self.btnStartAnalysis.setEnabled(True) def set_btns_running_state(self): self.btnImprtSettings.setEnabled(False) self.btnSaveSettings.setEnabled(False) self.btnRepeatStep.setEnabled(False) self.btnRunNextStep.setEnabled(False) self.btnStartAnalysis.setEnabled(False) def connect_callbacks(self): self.btnImprtSettings.clicked.connect(self.import_settings) self.btnSaveSettings.clicked.connect(self.save_settings) self.btnRunNextStep.clicked.connect(self.run_prev_or_next_step) self.btnRepeatStep.clicked.connect(self.repeat_step) self.btnStartAnalysis.clicked.connect(self.run_full_pipe_check) def import_settings(self): if os.path.exists('./configs/settings.json'): with open('./configs/settings.json') as f: settings = json.load(f) self.lineEditMainFolder.setText(settings['inp_path']) self.lineEditPythonPath.setText(settings['python_path']) self.lineEditCellProfiler.setText(settings['cellprofilerPath']) self.lineEditStitchedPreMImage.setText( settings['stitchedImgPreMPath']) self.lineEditStitchedPostMImage.setText( settings['stitchedImgPostMPath']) self.lineEditUdpFile.setText(settings['udpFile']) self.lineEditMetadata.setText(settings['microscopyMetadata']) self.tab_amf_FluoCh.setText(settings['postMaldiDapi']) self.tab_amf_ifftImgPath.setText( settings['manProcessedImgPath']) self.tab_gms_lineEditMSDs.setText(settings['MSDsName']) self.lineEditCPpipeFile.setText(settings['cpPipeLine']) self.tab_csvGen_lineEditCells.setText(settings['csvCellsPath']) else: QMessageBox.warning(self, "Warning", "No settings file found!") Exception('Settings file was not found or does not exist') def save_settings(self): if os.path.exists('./configs/settings.json'): with open('./configs/settings.json', 'r') as f: settings = json.load(f) settings['inp_path'] = global_vars.inpPath settings['python_path'] = global_vars.pythonPath settings['cellprofilerPath'] = global_vars.cellprofilerPath settings[ 'stitchedImgPreMPath'] = global_vars.stitchedImgPreMPath settings[ 'stitchedImgPostMPath'] = global_vars.stitchedImgPostMPath settings['udpFile'] = global_vars.udpFile settings['microscopyMetadata'] = global_vars.microscopyMetadata settings['MSDsName'] = global_vars.tab_gms_msDSName settings['cpPipeLine'] = global_vars.cpPipeLine settings['csvCellsPath'] = global_vars.tab_genCsv_csvFilePath with open('./configs/settings.json', 'w') as f: json.dump(settings, f) QMessageBox.information(self, "Info", "The new settings were saved") else: QMessageBox.warning(self, "Warning", "No settings file found!") Exception('Settings file was not found or does not exist') def increment_step(self): self.step += 1 print(self.step) def decrement_step(self): self.step -= 1 def repeat_step(self): self.decrement_step() self.run_prev_or_next_step() def run_prev_or_next_step(self): if self.validate_inputs(): if self.step == 0: self.set_btns_running_state() self.thread_0.start() elif self.step == 1: self.set_btns_running_state() self.thread_1.start() elif self.step == 2: self.set_btns_running_state() self.thread_2.start() elif self.step == 3: self.set_btns_running_state() self.thread_3.start() elif self.step == 4: self.set_btns_running_state() self.thread_4.start() elif self.step == 5: self.set_btns_running_state() self.thread_5.start() elif self.step == 6: self.set_btns_running_state() self.thread_6.start() elif self.step == 7: self.set_btns_running_state() self.thread_7.start() elif self.step == 8: self.set_btns_running_state() self.thread_8.start() def validate_inputs(self): if self.step == 0: check_line = global_vars.inpPath and global_vars.pythonPath and global_vars.stitchedImgPreMPath \ and global_vars.stitchedImgPostMPath and global_vars.udpFile and global_vars.microscopyMetadata if platform == "win32": check_line = check_line and global_vars.cellprofilerPath if check_line == '': QMessageBox.warning( self, "Warning", "Please check that all inputs are correctly entered and are " "not empty for general settings") return else: return True elif self.step == 1: if global_vars.tab_amf_postMaldiDapi == '' and self.tab_amf_cbMatrix.currentText() == 'DHB' or \ self.tab_amf_manFftRb.isChecked() and global_vars.tab_amf_ifftImage == '': QMessageBox.warning( self, "Warning", "Please check that all inputs are correctly entered and are " "not empty for general settings") return else: return True elif self.step == 2 or self.step == 3: check_line = global_vars.inpPath and global_vars.pythonPath and global_vars.stitchedImgPreMPath \ and global_vars.stitchedImgPostMPath if check_line == '': QMessageBox.warning( self, "Warning", "Please check that all inputs are correctly entered and are " "not empty for general settings") return else: return True elif self.step == 4: prev_checks = global_vars.inpPath and global_vars.pythonPath and global_vars.stitchedImgPreMPath \ and global_vars.stitchedImgPostMPath and global_vars.udpFile and global_vars.microscopyMetadata \ # and \ # (global_vars.tab_amf_postMaldiDapi == '' and self.tab_amf_cbMatrix.currentText() == 'DHB' or \ # self.tab_amf_manFftRb.isChecked() and global_vars.tab_amf_ifftImage == '') if global_vars.tab_gms_msDSName == '': QMessageBox.warning( self, "Warning", "Please make sure that your provided the name of the " "dataset") return elif prev_checks == '': QMessageBox.warning( self, "Warning", "Please make sure that all the paths from the previous steps " "are provided") return else: return True elif self.step == 5: if platform == "win32" and global_vars.cellprofilerPath == '': QMessageBox.warning( self, "Warning", "Please check that the path to CellProfler.exe is provided" ) return else: check_line = global_vars.inpPath and global_vars.pythonPath and global_vars.stitchedImgPreMPath \ and global_vars.stitchedImgPostMPath and global_vars.udpFile and global_vars.microscopyMetadata if check_line == '': QMessageBox.warning( self, "Warning", "Please check that all inputs are correctly entered and are " "not empty for general settings") return else: return True elif self.step == 6: check_line = global_vars.inpPath and global_vars.pythonPath and global_vars.stitchedImgPreMPath \ and global_vars.stitchedImgPostMPath and global_vars.udpFile and global_vars.microscopyMetadata and \ global_vars.tab_gms_msDSName if check_line == '': QMessageBox.warning( self, "Warning", "Please check that all inputs are correctly entered and are " "not empty for general settings") return else: return True elif self.step == 7: prev_checks = global_vars.inpPath and global_vars.pythonPath and global_vars.stitchedImgPreMPath \ and global_vars.stitchedImgPostMPath and global_vars.udpFile and global_vars.microscopyMetadata if global_vars.tab_genCsv_csvFilePath == '': QMessageBox.warning( self, "Warning", "Please make sure that your provided the csv file with features path" ) return elif global_vars.tab_gms_msDSName == '': QMessageBox.warning(self, "Warning", "Please provide dataset's metaspace name") return elif prev_checks == '': QMessageBox.warning( self, "Warning", "Please make sure that all the paths from the previous steps " "are provided") return else: return True '''Running threads of workers/Full pipeline''' def run_full_pipe(self): if self.validate_inputs(): self.set_btns_running_state() self.thread_full.start() def run_full_pipe_check(self): if self.step != 0 and self.step != 8: btnRunPipeReply = QMessageBox.question( self, "Warning", "You are in the middle of running pipeline step by step wise, " "running the full pipeline will start it from the first step again. " "Are you sure you want to proceed?") if btnRunPipeReply == QMessageBox.Yes: self.run_full_pipe() else: print('Running the full pipeline was aborted.') else: self.run_full_pipe() def fullpipe_finished(self): self.thread_full.terminate() self.set_btns_static_state() def update_tab(self): if self.step == 1: self.tabWidget.setCurrentWidget( self.tabWidget.findChild(QWidget, 'amf_tab')) self.set_btns_static_state() self.thread_0.terminate() elif self.step == 2: self.tabWidget.setCurrentWidget( self.tabWidget.findChild(QWidget, 'fids_tab')) self.set_btns_static_state() self.thread_1.terminate() elif self.step == 3: self.tabWidget.setCurrentWidget( self.tabWidget.findChild(QWidget, 'reg_tab')) self.set_btns_static_state() self.thread_2.terminate() elif self.step == 4: self.tabWidget.setCurrentWidget( self.tabWidget.findChild(QWidget, 'grab_ms_data_tab')) self.set_btns_static_state() self.thread_3.terminate() elif self.step == 5: self.tabWidget.setCurrentWidget( self.tabWidget.findChild(QWidget, 'cell_segm_tab')) self.set_btns_static_state() self.thread_4.terminate() elif self.step == 6: self.tabWidget.setCurrentWidget( self.tabWidget.findChild(QWidget, 'cell_segm_tab')) self.set_btns_static_state() self.thread_5.terminate() elif self.step == 7: self.tabWidget.setCurrentWidget( self.tabWidget.findChild(QWidget, 'csv_gen_tab')) self.set_btns_static_state() self.thread_6.terminate() elif self.step == 8: self.set_btns_static_state() self.thread_7.terminate() def update_pb(self, val): self.progressBar.setValue(val) def update_logger(self, val): logging.info(val) with open('./logs/spaceM.log', 'r') as f: self.LogsTextBrowser.setText(f.read())
class ChatClient(QObject): class Reader(QObject): onReceive = pyqtSignal(bytes) def __init__(self, connection): super().__init__() self.connection = connection self.is_open = True def read(self): buffer = b"" while self.is_open: try: buffer += self.connection.recv(4096) if buffer.endswith(MSG_TERMINATOR): buffer.strip(MSG_TERMINATOR) self.onReceive.emit(buffer) buffer = b"" except socket.timeout as e: print(e.args) buffer = b"" def __init__(self, intro_msg): """ Socket client that support async communication. :param sock: socket to ther other end of communication :param incoming_queue: deque that retrieves incoming messages :param outcoming_queue: deque that contains message to send to other end of communication """ super().__init__() self.connection = None self.reader = None self._read_thread = None self.intro_msg: IntroductionMessage = intro_msg @property def is_open(self): return True if self.connection else False @property def onReceive(self): if self.reader: return self.reader.onReceive else: raise Exception( "Establish connection before geting access to signal") def connect(self, address, port): self.connection = socket.create_connection((address, port), 10) self._read_thread = QThread(self) self.reader = self.Reader(self.connection) self.reader.moveToThread(self._read_thread) self._read_thread.started.connect(self.reader.read) self._read_thread.start() self.connection.send(bytes(self.intro_msg) + MSG_TERMINATOR) @pyqtSlot(bytes) def write(self, message: bytes): if self.is_open: obj_to_send = Serializable.from_bytes(message) if type(obj_to_send) is ChatMessage: obj_to_send.sender = self.intro_msg.nickname self.connection.send(bytes(obj_to_send) + MSG_TERMINATOR) else: raise Exception("Cannot write to closed connection") def close(self): if self.is_open: self._read_thread.terminate() self.connection.close() self.connection = None
class ExampleThread(Qt.QWidget): def __init__(self, parent=None): super(ExampleThread, self).__init__(parent) layout = Qt.QVBoxLayout(self) self.lbl = Qt.QLabel("Start") layout.addWidget(self.lbl) self.btnA = Qt.QPushButton("Запустить AThread(QThread)") layout.addWidget(self.btnA) self.btnB = Qt.QPushButton("Запустить SomeObject(QObject)") layout.addWidget(self.btnB) self.btnC = Qt.QPushButton("Запустить Worker(QRunnable)") layout.addWidget(self.btnC) self.progressBar = Qt.QProgressBar() self.progressBar.setProperty("value", 0) layout.addWidget(self.progressBar) self.setGeometry(550, 65, 300, 300) self.setWindowTitle('3 разных и простых способа работы с потоками.') self.btnA.clicked.connect(self.using_q_thread) self.btnB.clicked.connect(self.using_move_to_thread) self.btnC.clicked.connect(self.using_q_runnable) self.msg = MsgBoxAThread() self.thread = None self.msgSomeObject = MsgBoxSomeObject() self.objThread = None self.counter = 0 self.timer = Qt.QTimer() self.timer.setInterval(1000) # -------- timeout -------> def recurring_timer(self): self.timer.timeout.connect(self.recurring_timer) self.timer.start() self.threadpool = QThreadPool() print("Max потоков, кот. будут использоваться=`%d`" % self.threadpool.maxThreadCount()) self.msgWorker = MsgBoxWorker() self.threadtest = QThread(self) self.idealthreadcount = self.threadtest.idealThreadCount() print("Ваша машина может обрабатывать `{}` потокa оптимально.".format(self.idealthreadcount)) def recurring_timer(self): self.counter += 1 self.lbl.setText("СЧЁТЧИК цикл GUI: %d" % self.counter) # ---- AThread(QThread) -----------# def using_q_thread(self): if self.thread is None: self.thread = AThread() self.thread.threadSignalAThread.connect(self.on_threadSignalAThread) self.thread.finished.connect(self.finishedAThread) self.thread.start() self.btnA.setText("Stop AThread(QThread)") else: self.thread.terminate() self.thread = None self.btnA.setText("Start AThread(QThread)") def finishedAThread(self): self.thread = None self.btnA.setText("Start AThread(QThread)") def on_threadSignalAThread(self, value): self.msg.label.setText(str(value)) # Восстанавливаем визуализацию потокового окна, если его закрыли. Поток работает. # .setVisible(true) или .show() устанавливает виджет в видимое состояние, # если видны все его родительские виджеты до окна. if not self.msg.isVisible(): self.msg.show() # --END-- AThread(QThread) -------------------# # ---- SomeObject(QObject) -------------------# def using_move_to_thread(self): if self.objThread is None: self.objThread = QThread() self.obj = SomeObject() self.obj.moveToThread(self.objThread) # Переместить в поток для выполнения self.obj.threadSignalSomeObject.connect(self.on_threadSignalSomeObject) self.obj.finishedSomeObject.connect(self.finishedSomeObject) self.objThread.started.connect(self.obj.long_running) self.objThread.start() self.btnB.setText("Wait SomeObject(QObject)") self.btnB.setEnabled(False) else: pass def finishedSomeObject(self): self.objThread.terminate() self.objThread.wait(1) self.objThread = None self.btnB.setEnabled(True) self.btnB.setText("Start SomeObject(QObject)") def on_threadSignalSomeObject(self, value): self.msgSomeObject.label.setText(str(value)) # Восстанавливаем визуализацию потокового окна, если его закрыли. Поток работает. if not self.msgSomeObject.isVisible(): self.msgSomeObject.show() # --END-- SomeObject(QObject) -------------------# # ---- Worker(QRunnable) ------------------------# def using_q_runnable(self): # Передайте функцию для выполнения # Любые другие аргументы, kwargs передаются функции run worker = Worker(self.execute_this_fn) worker.signals.result.connect(self.print_output) worker.signals.finish.connect(self.thread_complete) worker.signals.progress.connect(self.progress_fn) self.threadpool.start(worker) def progress_fn(self, n): self.progressBar.setValue(n) self.msgWorker.label.setText(str(n)) # Восстанавливаем визуализацию потокового окна, если его закрыли. Поток работает. if not self.msgWorker.isVisible(): self.msgWorker.show() def execute_this_fn(self, progress_callback): for n in range(0, 11): Qt.QThread.msleep(600) progress_callback.emit(n * 100 / 10) return "Готово." def print_output(self, s): print("\ndef print_output(self, s):", s) def thread_complete(self): print("\nTHREAD ЗАВЕРШЕН!, self->", self) # --END-- Worker QRunnable) -------------------# # ==============================================### # потоки или процессы должны быть завершены ### def closeEvent(self, event): reply = Qt.QMessageBox.question \ (self, 'Информация', "Вы уверены, что хотите закрыть приложение?", Qt.QMessageBox.Yes, Qt.QMessageBox.No) if reply == Qt.QMessageBox.Yes: if self.thread: self.thread.quit() del self.thread self.msg.close() if self.objThread: self.objThread.setTerminationEnabled(True) self.objThread.terminate() self.objThread.wait(1) self.msgSomeObject.close() # закрыть поток Worker(QRunnable) self.msgWorker.close() super(ExampleThread, self).closeEvent(event) else: event.ignore()
class WowFishingBotUI: def __init__(self): self.startStop_fishing_flag = False self.app = None self.window = None self.game_process_name = None self.window_name = None self.log_viewer = None self.auto_fish_toggle = None self.find_wow_button = None self.fish_button = None self.fish_key_edit = None self.fishing_wait_time_edit = None self.stop_fishing_label = None self.fishing_thread = None self.tries_count_label = None self.loot_coords_edit = None self.loot_delta_edit = None self.bait_mov_sensibility_edit = None self.binary_image_widget = None self.rgb_image_widget = None self.background_model = None self.score_signal_viewer = None self.slope_signal_viewer = None self.post_detection_sleep_edit = None self.slope_samples_edit = None self.create_ui() self.bot = WowFishingBot(self) self.start_ui() def create_ui(self): # create QT application and main window self.app = QApplication(["WowFishingBotUI"]) self.window = QMainWindow() self.window.setGeometry(1100, 50, 800, 900) self.window.setWindowTitle("WOW fishing bot") # create central widget and layout central_widget = QGroupBox() layout = QGridLayout() # CONTROLS AND TOGGLES # edit to change games process name self.game_process_name = LabeledLineEdit("WOW process name: ", "Wow.exe") layout.addWidget(self.game_process_name, 0, 0, 1, 1) # edit to change games window name self.window_name = LabeledLineEdit("WOW window name: ", "World of Warcraft") layout.addWidget(self.window_name, 0, 1, 1, 1) # fishing key self.fish_key_edit = LabeledLineEdit("Fishing key:", "0") layout.addWidget(self.fish_key_edit, 2, 0, 1, 1) # fishing wait time self.fishing_wait_time_edit = LabeledLineEdit("Fishing start delay", "4") layout.addWidget(self.fishing_wait_time_edit, 2, 1, 1, 1) # looting coordinates self.loot_coords_edit = LabeledLineEdit("Looting coords", "0.048 0.31") layout.addWidget(self.loot_coords_edit, 3, 0, 1, 1) # looting delta self.loot_delta_edit = LabeledLineEdit("Vertical looting delta", "0.03") layout.addWidget(self.loot_delta_edit, 3, 1, 1, 1) # bait movement sensibility (in standard deviations) self.bait_mov_sensibility_edit = LabeledLineEdit( "Bait movement sensibility", "4") layout.addWidget(self.bait_mov_sensibility_edit, 4, 0, 1, 1) # bait movement sensibility (in standard deviations) self.post_detection_sleep_edit = LabeledLineEdit( "Post-detection sleep time", "0.0") layout.addWidget(self.post_detection_sleep_edit, 4, 1, 1, 1) # bait movement sensibility (in standard deviations) self.slope_samples_edit = LabeledLineEdit("Slope estimation samples", "30") layout.addWidget(self.slope_samples_edit, 5, 0, 1, 1) # Fish! button self.fish_button = QPushButton("Fish") self.fish_button.clicked.connect(self.start_fishing) self.fish_button.setEnabled(False) layout.addWidget(self.fish_button, 6, 1, 1, 1) # button to try to find the process from the line edit self.find_wow_button = QPushButton("Find WOW!") self.find_wow_button.clicked.connect(self.find_wow) layout.addWidget(self.find_wow_button, 6, 0, 1, 1) # toggle for loop fishing self.auto_fish_toggle = QCheckBox("Auto pilot") self.auto_fish_toggle.setChecked(True) layout.addWidget(self.auto_fish_toggle, 7, 0, 1, 1) # warning label with hotkey to stop fishing self.stop_fishing_label = DynamicLabel("Jump to stop fishing") self.stop_fishing_label.setFont(QFont("Times", 12, QFont.Bold)) self.stop_fishing_label.setStyleSheet("color: red;") self.stop_fishing_label.setVisible(False) layout.addWidget(self.stop_fishing_label, 8, 0, 1, 3) # label with the number of captures self.tries_count_label = DynamicLabel("0 tries") self.tries_count_label.setFont(QFont("Times", 24, QFont.Bold)) self.tries_count_label.setStyleSheet("color: red;") layout.addWidget(self.tries_count_label, 9, 0, 1, 3) # LOG FROM BOT ACTIVITY self.log_viewer = Log() layout.addWidget(self.log_viewer, 12, 0, 3, 2) # image display self.binary_image_widget = QImshow() layout.addWidget(self.binary_image_widget, 15, 0, 5, 1) self.slope_signal_viewer = QSignalViewer(2, None) layout.addWidget(self.slope_signal_viewer, 15, 1, 5, 1) central_widget.setLayout(layout) self.window.setCentralWidget(central_widget) # register flag callback to let the backend know the UI has died self.app.aboutToQuit.connect(self.kill_bot) def start_ui(self): self.window.show() self.app.exec_() def _fish(self): while True: self.bot.fish_grid() if not self.auto_fish_toggle.isChecked(): break def start_fishing(self): # activate warning on how to stop fishing self.stop_fishing_label.visibility_emitter.emit(True) # give user time to place mouse on WOW's window self.log_viewer.emitter.emit("Place the cursor inside the WOW window") for i in reversed(range(int(self.fishing_wait_time_edit.edit.text()))): time.sleep(1) self.log_viewer.emitter.emit( "Fishing will start in {} ...".format(i)) # launch fishing thread in parallel self.fishing_thread = QThread() self.fishing_thread.run = self._fish self.fishing_thread.start() # watch if the user jumps to stop fishing while True: time.sleep(0.001) self.app.processEvents() if keyboard.is_pressed(" "): # kill fishing thread self.fishing_thread.terminate() # remove warning on how to sto fishing self.stop_fishing_label.visibility_emitter.emit(False) return def find_wow(self): process_running = utils.check_process( self.game_process_name.edit.text()) window = utils.get_window(self.window_name.edit.text()) # check Wow is running if process_running and window: self.bot.set_wow_frame(window) self.log_viewer.emitter.emit("Wow window at" + str(self.bot.frame)) self.fish_button.setEnabled(True) self.find_wow_button.setStyleSheet("background: green;") else: self.log_viewer.emitter.emit("Wow not found running") self.fish_button.setEnabled(False) self.find_wow_button.setStyleSheet("") def kill_bot(self): self.bot.dead_UI = True
class FixateGUI(QtWidgets.QMainWindow, layout.Ui_FixateUI): """ GUI Main window """ # QT Signals # These are the thread safe signals to update UI elements # Multiple Choices/ OK signal sig_choices_input = pyqtSignal(str, tuple) # Updates the test Information above the image sig_label_update = pyqtSignal(str, str) # Signal for the text user input sig_text_input = pyqtSignal(str) # Timer for abort cleanup. TODO Rethink? sig_timer = pyqtSignal() # Tree Events sig_tree_init = pyqtSignal(list) sig_tree_update = pyqtSignal(str, str) # Active Window sig_active_update = pyqtSignal(str) sig_active_clear = pyqtSignal() # History Window sig_history_update = pyqtSignal(str) sig_history_clear = pyqtSignal(str) # Error Window sig_error_update = pyqtSignal(str) sig_error_clear = pyqtSignal(str) # Image Window sig_image_update = pyqtSignal(str) sig_image_clear = pyqtSignal() # Progress Signals sig_indicator_start = pyqtSignal() sig_indicator_stop = pyqtSignal() sig_working = pyqtSignal() sig_progress = pyqtSignal() sig_finish = pyqtSignal() # Deprecated Replace with Active , History and Error Window signals output_signal = pyqtSignal(str, str) # Deprecated replace with Image Window signals update_image = pyqtSignal(str, bool) """Class Constructor and destructor""" def __init__(self, worker, application): super(FixateGUI, self).__init__(None) self.application = application self.register_events() self.setupUi(self) self.treeSet = False self.blocked = False self.closing = False # Extra GUI setup not supported in the designer self.TestTree.setColumnWidth(1, 90) self.TestTree.header().setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch) self.TestTree.header().setSectionResizeMode(1, QtWidgets.QHeaderView.Fixed) self.base_image = "" self.dialog = None self.image_scene = QtWidgets.QGraphicsScene() self.ImageView.set_scene(self.image_scene) self.ImageView.setScene(self.image_scene) self.working_indicator = QtGui.QMovie(QT_GUI_WORKING_INDICATOR) self.WorkingIndicator.setMovie(self.working_indicator) self.start_indicator() self.status_code = -1 # Default status code used to check for unusual exit # Timers and Threads self.input_queue = Queue() self.abort_timer = QtCore.QTimer(self) self.abort_timer.timeout.connect(self.abort_check) self.worker = SequencerThread(worker) self.worker_thread = QThread() self.worker.moveToThread(self.worker_thread) self.worker_thread.started.connect(self.worker.run_thread) self.user_action_queue = None self.abort_queue = None # UI Binds self.Button_1.clicked.connect(self.button_1_click) self.Button_2.clicked.connect(self.button_2_click) self.Button_3.clicked.connect(self.button_3_click) self.UserInputBox.submit.connect(self.text_input_submit) self.bind_qt_signals() sys.excepthook = exception_hook # TODO DEBUG REMOVE def run_sequencer(self): self.worker_thread.start() def closeEvent(self, event): """ This function overrides closeEvent from the MainWindow class, called in case of unusual termination""" event.ignore() self.hide() self.clean_up() def bind_qt_signals(self): """ Binds the qt signals to the appropriate handlers :return: """ # Signal Binds self.sig_finish.connect(self.clean_up) # Normal termination self.sig_choices_input.connect(self.get_input) self.sig_label_update.connect(self.display_test) self.sig_text_input.connect(self.open_text_input) self.sig_timer.connect(self.start_timer) self.sig_tree_init.connect(self.display_tree) self.sig_tree_update.connect(self.update_tree) self.sig_progress.connect(self.progress_update) # New Binds self.sig_indicator_start.connect(self._start_indicator) self.sig_indicator_stop.connect(self._stop_indicator) self.sig_active_update.connect(self._active_update) self.sig_active_clear.connect(self._active_clear) self.sig_history_update.connect(self.history_update) self.sig_history_clear.connect(self.history_clear) self.sig_error_update.connect(self.error_update) self.sig_error_clear.connect(self.error_clear) self.sig_image_update.connect(self._image_update) self.sig_image_clear.connect(self._image_clear) # Deprecated # self.update_image.connect(self.display_image) # self.output_signal.connect(self.display_output) # self.working.connect(self.start_indicator) """Pubsub handlers for setup and teardown These are run in the main thread""" def register_events(self): pub.subscribe(self._seq_abort, "Sequence_Abort") pub.subscribe(self._user_ok, 'UI_req') pub.subscribe(self._user_choices, "UI_req_choices") pub.subscribe(self._user_input, 'UI_req_input') pub.subscribe(self._user_display, 'UI_display') pub.subscribe(self._user_display_important, "UI_display_important") pub.subscribe(self._user_action, 'UI_action') pub.subscribe(self._completion_code, 'Finish') # Image Window pub.subscribe(self.image_update, "UI_image") pub.subscribe(self.image_clear, "UI_image_clear") pub.subscribe(self.image_clear, "UI_block_end") # Active Window pub.subscribe(self.active_clear, "UI_block_end") # Multi Window pub.subscribe(self._print_test_start, 'Test_Start') pub.subscribe(self._print_test_seq_start, 'TestList_Start') pub.subscribe(self._print_test_complete, 'Test_Complete') pub.subscribe(self._print_comparisons, 'Check') pub.subscribe(self._print_errors, "Test_Exception") pub.subscribe(self._print_sequence_end, "Sequence_Complete") pub.subscribe(self._print_test_skip, 'Test_Skip') pub.subscribe(self._print_test_retry, 'Test_Retry') # Error Window # Working Indicator pub.subscribe(self.start_indicator, 'Test_Start') pub.subscribe(self.start_indicator, 'UI_block_end') pub.subscribe(self.stop_indicator, 'UI_block_start') return def unregister_events(self): pub.unsubAll() return """Slot handlers for thread-gui interaction These are run in the main thread""" def start_timer(self): self.abort_timer.start(100) def abort_check(self): if self.abort_queue is None: return try: self.abort_queue.get_nowait() self.abort_queue = None self.button_reset(True) self.abort_timer.stop() except Empty: return def open_text_input(self, message): self.ActiveEvent.append(message) self.ActiveEvent.verticalScrollBar().setValue(self.ActiveEvent.verticalScrollBar().maximum()) self.Events.append(message) self.Events.verticalScrollBar().setValue(self.Events.verticalScrollBar().maximum()) self.UserInputBox.setPlaceholderText("Input:") self.UserInputBox.setEnabled(True) self.UserInputBox.setFocus() def start_indicator(self, **kwargs): self.sig_indicator_start.emit() def _start_indicator(self): self.WorkingIndicator.show() self.working_indicator.start() def stop_indicator(self): self.sig_indicator_stop.emit() def _stop_indicator(self): self.working_indicator.stop() self.WorkingIndicator.hide() def retrieve_packaged_data(self, path): try: return pkgutil.get_data("module.loaded_tests", path) except FileNotFoundError: return b"" def image_update(self, path): self.sig_image_update.emit(path) def _image_update(self, path): """ Adds an image to the image viewer. These images can be stacked with transparent layers to form overlays :param path: Relative path to image within the test scripts package :return: None """ image = QtGui.QPixmap() image.loadFromData(self.retrieve_packaged_data(path)) if image.isNull(): self.file_not_found(path) self.image_scene.addPixmap(image) self.ImageView.fitInView(0, 0, self.image_scene.width(), self.image_scene.height(), QtCore.Qt.KeepAspectRatio) def image_clear(self): self.sig_image_clear.emit() def _image_clear(self): self.image_scene.clear() def display_image(self, path="", overlay=False): if path == "" or not overlay: self.image_scene.clear() if overlay: image = QtGui.QPixmap() image.loadFromData(self.base_image) if image.isNull(): self.file_not_found(self.base_image) elif path == "": self.base_image = path return else: self.base_image = self.retrieve_packaged_data(path) image = QtGui.QPixmap() image.loadFromData(self.base_image) if image.isNull(): self.file_not_found(path) self.image_scene.addPixmap(image) self.ImageView.fitInView(0, 0, self.image_scene.width(), self.image_scene.height(), QtCore.Qt.KeepAspectRatio) return image = QtGui.QPixmap() image.loadFromData(self.retrieve_packaged_data(path)) if image.isNull(): self.file_not_found(path) self.image_scene.addPixmap(image) self.ImageView.fitInView(0, 0, self.image_scene.width(), self.image_scene.height(), QtCore.Qt.KeepAspectRatio) return def file_not_found(self, path): """ Display warning box for an invalid image path :param path: :return: """ self.dialog = QtWidgets.QMessageBox() self.dialog.setText("Warning: Image not Found") self.dialog.setInformativeText("Filename: {}".format(path)) self.dialog.setStandardButtons(QtWidgets.QMessageBox.Ok) self.dialog.setDefaultButton(QtWidgets.QMessageBox.Ok) self.dialog.setIcon(QtWidgets.QMessageBox.Warning) self.dialog.exec() def display_tree(self, tree): # Make sure this function is only run once if self.treeSet: return self.treeSet = True level_stack = [] for item in tree: # Check Level if item[0].count('.') + 1 <= len(level_stack): # Case 1: Going out one or more levels or same level for _ in range(len(level_stack) - item[0].count('.')): level_stack.pop() elif item[0].count('.') + 1 > len(level_stack): # Case 2: Going in one or more levels for index in range(item[0].count('.') + 1 - len(level_stack), 0, -1): split_index = item[0].split('.') if index > 1: # More than one level, append dummy items as required dummy = QtWidgets.QTreeWidgetItem() dummy.setText(0, '.'.join(split_index[:-(index - 1)])) dummy.setText(1, 'Queued') dummy.setTextAlignment(1, QtCore.Qt.AlignRight) level_stack.append(dummy.clone()) tree_item = QtWidgets.QTreeWidgetItem() tree_item.setText(0, item[0] + '. ' + item[1]) tree_item.setTextAlignment(1, QtCore.Qt.AlignRight) tree_item.setText(1, 'Queued') level_stack.append(tree_item.clone()) if len(level_stack) > 1: # Child Add level_stack[-2].addChild(level_stack[-1]) else: # Top Level self.TestTree.addTopLevelItem(level_stack[-1]) def update_tree(self, test_index, status): if len(test_index) == 0: return colours = get_status_colours(status) test_index = test_index.split('.') # Find the test in the tree current_test = self.TestTree.findItems(test_index[0], QtCore.Qt.MatchStartsWith, 0)[0] while len(test_index) > 1: test_index[0:2] = [''.join(test_index[0] + '.' + test_index[1])] for child_index in range(current_test.childCount()): if current_test.child(child_index).text(0).startswith(test_index[0]): current_test = current_test.child(child_index) break # Update the test if status not in ["Aborted"]: for i in range(2): current_test.setBackground(i, colours[0]) current_test.setForeground(i, colours[1]) current_test.setText(1, status) current_test.setExpanded(True) # In case of an abort, update all remaining tests else: self.active_update("Aborting, please wait...") sub_finish = False original_test = current_test while current_test is not None: if current_test.text(1) in ["Queued"]: for i in range(2): current_test.setBackground(i, colours[0]) current_test.setForeground(i, colours[1]) current_test.setText(1, status) current_test.setExpanded(False) if current_test.childCount() > 0 and not sub_finish: # Go in a level current_test = current_test.child(0) sub_finish = False elif current_test.parent() is not None: if current_test.parent().indexOfChild( current_test) >= current_test.parent().childCount() - 1: # Come out a level sub_finish = True current_test = current_test.parent() else: current_test = current_test.parent().child( current_test.parent().indexOfChild(current_test) + 1) # Same level sub_finish = False else: # Top level test, go to next test current_test = self.TestTree.topLevelItem(self.TestTree.indexOfTopLevelItem(current_test) + 1) sub_finish = False current_test = original_test # Check for last test in group while current_test.parent() is not None and (current_test.parent().indexOfChild( current_test) >= current_test.parent().childCount() - 1 or status in ["Aborted"]): parent_status = current_test.text(1) current_test = current_test.parent() for child_index in range(current_test.childCount()): # Check status of all child tests check_status = current_test.child(child_index).text(1) if list(STATUS_PRIORITY.keys()).index(check_status) < list(STATUS_PRIORITY.keys()).index(parent_status): parent_status = check_status colours = get_status_colours(parent_status) for i in range(2): current_test.setBackground(i, colours[0]) current_test.setForeground(i, colours[1]) current_test.setText(1, parent_status) if parent_status not in ["In Progress"]: current_test.setExpanded(False) def display_test(self, test_index, description): self.ActiveTest.setText("Test {}:".format(test_index)) self.TestDescription.setText("{}".format(description)) def active_update(self, msg, **kwargs): self.sig_active_update.emit(msg) def _active_update(self, message): self.ActiveEvent.append(message) self.ActiveEvent.verticalScrollBar().setValue(self.ActiveEvent.verticalScrollBar().maximum()) def active_clear(self, **kwargs): self.sig_active_clear.emit() def _active_clear(self): self.ActiveEvent.clear() self.ActiveEvent.verticalScrollBar().setValue(self.ActiveEvent.verticalScrollBar().maximum()) def history_update(self, message): self.Events.append(message) self.Events.verticalScrollBar().setValue(self.Events.verticalScrollBar().maximum()) def history_clear(self): self.Events.clear() self.Events.verticalScrollBar().setValue(self.Events.verticalScrollBar().maximum()) def error_update(self, message): self.Errors.append(message) self.Errors.verticalScrollBar().setValue(self.Errors.verticalScrollBar().maximum()) def error_clear(self): self.Errors.clear() self.Errors.verticalScrollBar().setValue(self.Errors.verticalScrollBar().maximum()) # def display_output(self, message, status): # self.Events.append(message) # self.Events.verticalScrollBar().setValue(self.Events.verticalScrollBar().maximum()) # # if status == "False": # Print errors # self.Errors.append(self.ActiveTest.text() + ' - ' + message[1:]) # self.Errors.verticalScrollBar().setValue(self.Errors.verticalScrollBar().maximum()) # # if status in ["Active", "False"]: # self.ActiveEvent.append(message) # self.ActiveEvent.verticalScrollBar().setValue(self.ActiveEvent.verticalScrollBar().maximum()) def progress_update(self): self.ActiveEvent.clear() self.ProgressBar.setValue(self.worker.worker.get_current_task()) if self.worker.worker.sequencer.tests_failed > 0 or self.worker.worker.sequencer.tests_errored > 0: self.ProgressBar.setStyleSheet(ERROR_STYLE) def get_input(self, message, choices): self.Events.append(message) self.ActiveEvent.append(message) self.Events.verticalScrollBar().setValue(self.Events.verticalScrollBar().maximum()) self.ActiveEvent.verticalScrollBar().setValue(self.ActiveEvent.verticalScrollBar().maximum()) if isinstance(choices, bool): pass elif len(choices) == 1: self.Button_2.setText(choices[0]) self.Button_2.setShortcut(QtGui.QKeySequence(choices[0][0:1])) self.Button_2.setEnabled(True) self.Button_2.setDefault(True) self.Button_2.setFocus() elif len(choices) == 2: self.Button_1.setText(choices[0]) self.Button_1.setShortcut(QtGui.QKeySequence(choices[0][0:1])) self.Button_1.setEnabled(True) self.Button_1.setDefault(True) self.Button_1.setFocus() self.Button_3.setText(choices[1]) self.Button_3.setShortcut(QtGui.QKeySequence(choices[1][0:1])) self.Button_3.setEnabled(True) else: self.Button_1.setText(choices[0]) self.Button_1.setShortcut(QtGui.QKeySequence(choices[0][0:1])) self.Button_1.setEnabled(True) self.Button_1.setDefault(True) self.Button_1.setFocus() self.Button_2.setText(choices[1]) self.Button_2.setShortcut(QtGui.QKeySequence(choices[1][0:1])) self.Button_2.setEnabled(True) self.Button_3.setText(choices[2]) self.Button_3.setShortcut(QtGui.QKeySequence(choices[2][0:1])) self.Button_3.setEnabled(True) def _seq_abort(self, exception=None): """ This function ensures that sequence aborting is handled correctly if the sequencer is blocked waiting for input """ # Release user input waiting loops if self.user_action_queue is not None: self.user_action_queue.put(False) self.user_action_queue = None if self.abort_queue is not None: self.abort_queue.put(True) self.abort_queue = None # Release sequence blocking calls if self.blocked: self.input_queue.put("ABORT_FORCE") def clean_up(self): """ This function is the second one called for normal termination, and the first one called for unusual termination. Check for abnormal termination, and stop the sequencer if required; then stop and delete the thread """ if self.worker_thread is None: # This function has already run, therefore main already has the status code return # The following actions must be done in a specific order, be careful when making changes to this section self.abort_timer.stop() self.closing = True if self.status_code == -1: # Unusual termination - The sequencer hasn't finished yet, stop it self.status_code = self.worker.worker.stop() self.unregister_events() # Prevent interruption by pubsub messages self.worker.deleteLater() # Schedule the thread worker for deletion self.worker = None # Remove the reference to allow the GC to clean up self.worker_thread.exit(self.status_code) # Exit the thread self.worker_thread.wait(2000) # 2 seconds for the thread to exit self.worker_thread.terminate() # Force quit the thread if it is still running, if so, this will throw a warning self.worker_thread.deleteLater() # Schedule the thread for deletion self.worker_thread = None # Remove the reference to allow the GC to clean up # Now close the GUI thread, return to the controller in main self.application.exit(self.status_code) """User IO handlers, emit signals to trigger main thread updates via slots. These are run in the sequencer thread""" def event_output(self, message, status="True"): self.output_signal.emit(message, str(status)) def gui_text_input(self, message): self.sig_text_input.emit(message) self.blocked = True result = self.input_queue.get(True) self.blocked = False self.sig_working.emit() return result def gui_choices(self, message, choices): self.sig_choices_input.emit(message, choices) self.blocked = True result = self.input_queue.get(True) self.blocked = False self.sig_working.emit() return result def gui_user_action_pass_fail(self, message, q, abort): """ Non blocking user call :param message: :param q: :param abort: :return: """ self.sig_choices_input.emit(message, ["PASS", "FAIL"]) self.sig_timer.emit() self.user_action_queue = q self.abort_queue = abort def gui_user_action_fail(self, message, q, abort): self.sig_choices_input.emit(message, ["FAIL"]) self.sig_timer.emit() self.user_action_queue = q self.abort_queue = abort def gui_user_input(self, message, choices=None, blocking=True): result = None if choices is not None: # Button Prompt if blocking: self.sig_choices_input.emit(message, choices) else: self.sig_choices_input.emit(message, (choices[0],)) self.sig_timer.emit() else: # Text Prompt self.sig_text_input.emit(message) if blocking: # Block sequencer until user responds self.blocked = True result = self.input_queue.get(True) self.blocked = False self.sig_working.emit() else: self.user_action_queue = choices[1] self.abort_queue = choices[2] return result """UI Event Handlers, process actions taken by the user on the GUI. These are run in the main thread """ def text_input_submit(self): self.input_queue.put(self.UserInputBox.toPlainText()) self.UserInputBox.clear() self.UserInputBox.setPlaceholderText("") self.UserInputBox.setEnabled(False) def button_1_click(self): self.input_queue.put(self.Button_1.text()) self.button_reset() def button_2_click(self): if self.user_action_queue is not None: self.user_action_queue.put(self.Button_2.text()) self.user_action_queue = None self.abort_timer.stop() self.abort_queue = None else: self.input_queue.put(self.Button_2.text()) self.button_reset() def button_3_click(self): self.input_queue.put(self.Button_3.text()) self.button_reset() def button_reset(self, fail_only=False): self.Button_2.setText("") self.Button_2.setEnabled(False) self.Button_2.setDefault(False) if not fail_only: self.Button_1.setText("") self.Button_3.setText("") self.Button_1.setEnabled(False) self.Button_3.setEnabled(False) self.Button_1.setDefault(False) self.Button_3.setDefault(False) """Thread listener, called from the sequencer thread""" def _completion_code(self, code): """This function is the first one called when the sequencer completes normally. Set the exit code, and signal the main thread.""" self.status_code = code self.sig_finish.emit() """UI Callables, called from the sequencer thread""" def reformat_text(self, text_str, first_line_fill="", subsequent_line_fill=""): lines = [] wrapper.initial_indent = first_line_fill wrapper.subsequent_indent = subsequent_line_fill for ind, line in enumerate(text_str.splitlines()): if ind != 0: wrapper.initial_indent = subsequent_line_fill lines.append(wrapper.fill(line)) return '\n'.join(lines) # def _image(self, path, overlay): # if self.closing: # return # self.update_image.emit(path, overlay) def _user_action(self, msg, q, abort): """ This is for tests that aren't entirely dependant on the automated system. This works by monitoring a queue to see if the test completed successfully. Also while doing this it is monitoring if the escape key is pressed to signal to the system that the test fails. Use this in situations where you want the user to do something (like press all the keys on a keypad) where the system is automatically monitoring for success but has no way of monitoring failure. :param msg: Information for the user :param q: The queue object to put false if the user fails the test :param abort: The queue object to abort this monitoring as the test has already passed. :return: None """ if self.closing: q.put(False) abort.put(True) return self.gui_user_input(self.reformat_text(msg), ("Fail", q, abort), False) def _user_ok(self, msg, q): """ This can be replaced anywhere in the project that needs to implement the user driver The result needs to be put in the queue with the first part of the tuple as 'Exception' or 'Result' and the second part is the exception object or response object :param msg: Message for the user to understand what to do :param q: The result queue of type queue.Queue :return: """ if self.closing: q.put("Result", None) return self.gui_user_input(msg, ("Continue",)) q.put("Result", None) def _user_choices(self, msg, q, choices, target, attempts=5): """ This can be replaced anywhere in the project that needs to implement the user driver Temporarily a simple input function. The result needs to be put in the queue with the first part of the tuple as 'Exception' or 'Result' and the second part is the exception object or response object This needs to be compatible with forced exit. Look to user action for how it handles a forced exit :param msg: Message for the user to understand what to input :param q: The result queue of type queue.Queue :param target: Optional Validation function to check if the user response is valid :param attempts: :return: """ if self.closing: q.put(("Result", "ABORT_FORCE")) return for _ in range(attempts): # This will change based on the interface ret_val = self.gui_user_input(self.reformat_text(msg), choices) ret_val = target(ret_val, choices) if ret_val: q.put(('Result', ret_val)) return q.put('Exception', UserInputError("Maximum number of attempts {} reached".format(attempts))) def _user_input(self, msg, q, target=None, attempts=5, kwargs=None): """ This can be replaced anywhere in the project that needs to implement the user driver Temporarily a simple input function. The result needs to be put in the queue with the first part of the tuple as 'Exception' or 'Result' and the second part is the exception object or response object This needs to be compatible with forced exit. Look to user action for how it handles a forced exit :param msg: Message for the user to understand what to input :param q: The result queue of type queue.Queue :param target: Optional Validation function to check if the user response is valid :param attempts: :param kwargs: :return: """ if self.closing: q.put(('Result', "ABORT_FORCE")) return msg = self.reformat_text(msg) wrapper.initial_indent = "" wrapper.subsequent_indent = "" for _ in range(attempts): # This will change based on the interface ret_val = self.gui_user_input(msg, None, True) if target is None or ret_val == "ABORT_FORCE": q.put(ret_val) return ret_val = target(ret_val, **kwargs) if ret_val: q.put(('Result', ret_val)) return q.put('Exception', UserInputError("Maximum number of attempts {} reached".format(attempts))) def _user_display(self, msg): """ :param msg: :return: """ if self.closing: return self.history_update(self.reformat_text(msg)) def _user_display_important(self, msg): """ :param msg: :return: """ if self.closing: return self.history_update("") self.history_update("!" * wrapper.width) self.active_update("!" * wrapper.width) self.history_update("") self.history_update(self.reformat_text(msg)) self.active_update(self.reformat_text(msg)) self.history_update("") self.history_update("!" * wrapper.width) self.active_update("!" * wrapper.width) def _print_sequence_end(self, status, passed, failed, error, skipped, sequence_status): if self.closing: return self.history_update("#" * wrapper.width) self.history_update(self.reformat_text("Sequence {}".format(sequence_status))) # self.history_update("Sequence {}".format(sequence_status)) post_sequence_info = [] if status == "PASSED": post_sequence_info.extend(RESOURCES["SEQUENCER"].context_data.get("_post_sequence_info_pass", [])) elif status == "FAILED" or status == "ERROR": post_sequence_info.extend(RESOURCES["SEQUENCER"].context_data.get("_post_sequence_info_fail", [])) post_sequence_info.extend(RESOURCES["SEQUENCER"].context_data.get("_post_sequence_info", [])) if post_sequence_info: self.history_update("-" * wrapper.width) self.history_update("IMPORTANT INFORMATION") for itm in post_sequence_info: self.history_update(self.reformat_text(itm)) self.history_update("-" * wrapper.width) self.history_update(self.reformat_text("Status: {}".format(status))) self.history_update("#" * wrapper.width) def _print_test_start(self, data, test_index): if self.closing: return self.sig_progress.emit() self.history_update("*" * wrapper.width) self.history_update(self.reformat_text("Test {}: {}".format(test_index, data.test_desc))) self.history_update("-" * wrapper.width) self.sig_label_update.emit(test_index, data.test_desc) self.sig_tree_update.emit(test_index, "In Progress") def _print_test_seq_start(self, data, test_index): if self.closing: return self.ProgressBar.setMaximum(self.worker.worker.get_task_count()) self.sig_tree_init.emit(self.worker.worker.get_test_tree()) self.sig_progress.emit() self._print_test_start(data, test_index) def _print_test_complete(self, data, test_index, status): if self.closing: return sequencer = RESOURCES["SEQUENCER"] self.history_update("-" * wrapper.width) self.history_update( self.reformat_text("Checks passed: {}, Checks failed: {}".format(sequencer.chk_pass, sequencer.chk_fail))) # self.history_update("Checks passed: {}, Checks failed: {}".format(sequencer.chk_pass, sequencer.chk_fail)) self.history_update(self.reformat_text("Test {}: {}".format(test_index, status.upper()))) # self.history_update("Test {}: {}".format(test_index, status.upper())) self.history_update("-" * wrapper.width) if status.upper() in ["ERROR", "SKIPPED"]: return if sequencer.chk_fail == 0: self.sig_tree_update.emit(test_index, "Passed") else: self.sig_tree_update.emit(test_index, "Failed") def _print_test_skip(self, data, test_index): if self.closing: return self.history_update("\nTest Marked as skip") self.sig_tree_update.emit(test_index, "Skipped") def _print_test_retry(self, data, test_index): if self.closing: return self.history_update(self.reformat_text("\nTest {}: Retry".format(test_index))) def _print_errors(self, exception, test_index): if self.closing: return if isinstance(exception, SequenceAbort): self.sig_tree_update.emit(test_index, "Aborted") status = True else: status = False self.sig_tree_update.emit(test_index, "Error") self.history_update("") self.history_update("!" * wrapper.width) self.active_update("!" * wrapper.width) self.history_update( self.reformat_text("Test {}: Exception Occurred, {} {}".format(test_index, type(exception), exception))) self.active_update( self.reformat_text("Test {}: Exception Occurred, {} {}".format(test_index, type(exception), exception))) self.history_update("!" * wrapper.width) self.active_update("!" * wrapper.width) # TODO self.history_update traceback into a debug log file if fixate.config.DEBUG: traceback.print_tb(exception.__traceback__, file=sys.stderr) def round_to_3_sig_figures(self, chk): """ Tries to round elements to 3 significant figures for formatting :param chk: :return: """ ret_dict = {} for element in ["_min", "_max", "test_val", "nominal", "tol"]: ret_dict[element] = getattr(chk, element, None) try: ret_dict[element] = "{:.3g}".format(ret_dict[element]) except: pass return ret_dict def _print_comparisons(self, passes, chk, chk_cnt, context): if passes: status = "PASS" else: status = "FAIL" format_dict = self.round_to_3_sig_figures(chk) if chk._min is not None and chk._max is not None: msg = self.reformat_text( "\nCheck {chk_cnt}: {status} when comparing {test_val} {comparison} {_min} - {_max} : " "{description}".format( status=status, comparison=chk.target.__name__[1:].replace('_', ' '), chk_cnt=chk_cnt, description=chk.description, **format_dict)) self.history_update(msg) if status == "FAIL": self.active_update(msg) elif chk.nominal is not None and chk.tol is not None: msg = self.reformat_text( "\nCheck {chk_cnt}: {status} when comparing {test_val} {comparison} {nominal} +- {tol}% : " "{description}".format( status=status, comparison=chk.target.__name__[1:].replace('_', ' '), chk_cnt=chk_cnt, description=chk.description, **format_dict)) self.history_update(msg) if status == "FAIL": self.active_update(msg) elif chk._min is not None or chk._max is not None or chk.nominal is not None: # Grabs the first value that isn't none. Nominal takes priority comp_val = next(format_dict[item] for item in ["nominal", "_min", "_max"] if format_dict[item] is not None) msg = self.reformat_text("\nCheck {chk_cnt}: {status} when comparing {test_val} {comparison} {comp_val} : " "{description}".format( status=status, comparison=chk.target.__name__[1:].replace('_', ' '), comp_val=comp_val, chk_cnt=chk_cnt, description=chk.description, **format_dict)) self.history_update(msg) if status == "FAIL": self.active_update(msg) else: if chk.test_val is not None: msg = self.reformat_text( "\nCheck {chk_cnt}: {status}: {test_val} : {description}".format( chk_cnt=chk_cnt, description=chk.description, status=status, **format_dict)) self.history_update(msg) if status == "FAIL": self.active_update(msg) else: msg = self.reformat_text( "\nCheck {chk_cnt} : {status}: {description}".format(description=chk.description, chk_cnt=chk_cnt, status=status)) self.history_update(msg) if status == "FAIL": self.active_update(msg)