class Logger: def __init__(self, textBrowser, textLabel): self.logBox = QTextBrowser() self.labelBox = QLabel() self.logBox = textBrowser self.labelBox = textLabel self._enabled = True def log(self, logLine, type=None, special=None): if not self._enabled: return string = "" if logLine is "": try: logLine = specialLogs[special][0] if type is None: type = specialLogs[special][1] except: print("An invalid special log has been provided: ", special) color = "black" if type is None: type = "INFO" string = string + type + ": " if type in typeColors.keys(): color = typeColors[type] string = string + logLine self.logBox.append(string) self.labelBox.setStyleSheet("color: " + color) self.labelBox.setAlignment(Qt.AlignHCenter) if type in typeColors.keys(): self.labelBox.setText(logLine) def saveLog(self, filePath): try: if filePath[-4:] != ".txt": filePath = filePath + ".txt" with open(filePath, mode='a') as outFile: outFile.write( "\nThe following log has been saved at {} \n".format( datetime.now())) outFile.write(self.logBox.toPlainText()) except IOError: self.log("An error was found. Please check the output file path!", type="ERROR") return -1 return 0 def clearLog(self): self.logBox.clear() self.labelBox.clear() def enableLogging(self): self._enabled = True def disableLogging(self): self._enabled = False
class mainWindow(QWidget): def __init__(self): super().__init__() self.activeImage = "" self.previewImage = "" self.n = 400 #Set image display max size #Top Row Setup self.hedder = QLabel("Welcome to the Image Restoration App") #Button Row Setup self.selectButton = QPushButton("Click to select an image.") self.selectButton.clicked.connect(self.imageSelect) self.option1 = QPushButton("Black and White to Color") self.option1.clicked.connect(self.bandwToColorFilter) self.option2 = QPushButton("Denoise Image") self.option2.clicked.connect(self.denoiseFilter) self.option3 = QPushButton("Sharpen Image") self.option3.clicked.connect(self.sharpenFilter) self.option4 = QPushButton("Resize Image") self.option4.clicked.connect(self.resizeFilter) self.resizePercent = QLineEdit(self) self.saveImage = QPushButton("Save Image") self.saveImage.clicked.connect(self.imageSave) self.saveLabel = QLabel("Save name ->") self.saveName = QLineEdit(self) self.optionsRow = QHBoxLayout() self.optionsRow.addWidget(self.selectButton) self.optionsRow.addWidget(self.option1) self.optionsRow.addWidget(self.option2) self.optionsRow.addWidget(self.option3) self.optionsRow.addWidget(self.option4) self.optionsRow.addWidget(self.resizePercent) self.optionsRow.addWidget(self.saveLabel) self.optionsRow.addWidget(self.saveName) self.optionsRow.addWidget(self.saveImage) #Image Display Setup self.activeImageDisplay = QLabel() self.previewImageDisplay = QLabel() self.imageDisplayRow = QHBoxLayout() self.imageDisplayRow.addWidget(self.activeImageDisplay) self.imageDisplayRow.addWidget(self.previewImageDisplay) #Main Layout Setup self.mainLayout = QVBoxLayout() self.mainLayout.addWidget(self.hedder) self.mainLayout.addLayout(self.optionsRow) self.mainLayout.addLayout(self.imageDisplayRow) self.setLayout(self.mainLayout) def imageSelect(self): #function for selecting image self.activeImage = easygui.fileopenbox() pixmap = QPixmap(self.activeImage) self.previewImage = "" self.previewImageDisplay.clear() if str(pixmap.size() ) == "PySide2.QtCore.QSize(0, 0)": #checks if image is valid self.activeImage = "" self.activeImageDisplay.clear() self.hedder.setText("Warning: Invalid image.") self.repaint() else: #if image is valid, sets and displays image pixmap = pixmap.scaled(self.n, self.n, Qt.KeepAspectRatio) self.activeImageDisplay.setPixmap(pixmap) self.hedder.setText("Image selected. Now, apply a filter.") self.previewImageDisplay.clear() self.repaint() def bandwToColorFilter(self): #code to run bandwToColor, save and display if self.previewImage != "": #checks if preview exists. If it does, it will be edited instead of original. self.previewImage = bandwToColor(self.previewImage) imPath = self.previewImage pixmap = QPixmap(imPath) pixmap = pixmap.scaled(self.n, self.n, Qt.KeepAspectRatio) self.previewImageDisplay.setPixmap(pixmap) self.hedder.setText( "Filter applied. Now, apply additional filters or save.") self.repaint() elif self.activeImage != "": #checks if image is ready self.previewImage = bandwToColor(self.activeImage) imPath = self.previewImage pixmap = QPixmap(imPath) pixmap = pixmap.scaled(self.n, self.n, Qt.KeepAspectRatio) self.previewImageDisplay.setPixmap(pixmap) self.hedder.setText( "Filter applied. Now, apply additional filters or save.") self.repaint() else: #if image is not ready, displays warning self.hedder.setText( "Warning: No image selected. Select an image before apllying filters." ) self.repaint() def denoiseFilter(self): #code to run denoise, save and display if self.previewImage != "": #checks if preview exists. If it does, it will be edited instead of original. self.previewImage = denoise(self.previewImage) imPath = self.previewImage imPath = self.previewImage pixmap = QPixmap(imPath) pixmap = pixmap.scaled(self.n, self.n, Qt.KeepAspectRatio) self.previewImageDisplay.setPixmap(pixmap) self.hedder.setText( "Filter applied. Now, apply additional filters or save.") self.repaint() elif self.activeImage != "": #checks if image is ready self.previewImage = denoise(self.activeImage) imPath = self.previewImage pixmap = QPixmap(imPath) pixmap = pixmap.scaled(self.n, self.n, Qt.KeepAspectRatio) self.previewImageDisplay.setPixmap(pixmap) self.hedder.setText( "Filter applied. Now, apply additional filters or save.") self.repaint() else: #if image is not ready, displays warning self.hedder.setText( "Warning: No image selected. Select an image before apllying filters." ) self.repaint() def sharpenFilter(self): #code to run sharpen, save and display if self.previewImage != "": #checks if preview exists. If it does, it will be edited instead of original. self.previewImage = sharpen(self.previewImage) imPath = self.previewImage imPath = self.previewImage pixmap = QPixmap(imPath) pixmap = pixmap.scaled(self.n, self.n, Qt.KeepAspectRatio) self.previewImageDisplay.setPixmap(pixmap) self.hedder.setText( "Filter applied. Now, apply additional filters or save.") self.repaint() elif self.activeImage != "": #checks if image is ready self.previewImage = sharpen(self.activeImage) imPath = self.previewImage pixmap = QPixmap(imPath) pixmap = pixmap.scaled(self.n, self.n, Qt.KeepAspectRatio) self.previewImageDisplay.setPixmap(pixmap) self.hedder.setText( "Filter applied. Now, apply additional filters or save.") self.repaint() else: #if image is not ready, displays warning self.hedder.setText( "Warning: No image selected. Select an image before apllying filters." ) self.repaint() def resizeFilter(self): #code to run resize, save and "display" if self.resizePercent.displayText().isnumeric() == False: self.hedder.setText( "Warning: You must imput an integer for the resize. Greater than 100 increases size, less than 100 decreases size." ) self.repaint() elif int(self.resizePercent.displayText()) < 0.0: self.hedder.setText( "Warning: Resize must be number greater than 0.") self.repaint() elif self.previewImage != "": #checks if preview exists. If it does, it will be edited instead of original. self.previewImage = resize(self.previewImage, int(self.resizePercent.displayText())) imPath = self.previewImage imPath = self.previewImage pixmap = QPixmap(imPath) pixmap = pixmap.scaled(self.n, self.n, Qt.KeepAspectRatio) self.previewImageDisplay.setPixmap(pixmap) self.hedder.setText( "Filter applied. Warning: Resize may not be visible in app. Now, apply additional filters or save." ) self.repaint() elif self.activeImage != "": #checks if image is ready self.previewImage = resize(self.activeImage, int(self.resizePercent.displayText())) imPath = self.previewImage pixmap = QPixmap(imPath) pixmap = pixmap.scaled(self.n, self.n, Qt.KeepAspectRatio) self.previewImageDisplay.setPixmap(pixmap) self.hedder.setText( "Filter applied. Warning: Resize may not be visible in app. Now, apply additional filters or save." ) self.repaint() else: #if image is not ready, displays warning self.hedder.setText( "Warning: No image selected. Select an image before apllying filters." ) self.repaint() def imageSave(self): #code to save image if self.previewImage == "": #checks if image is ready self.hedder.setText( "Warning: No image to save. Upload an image to edit or apply a filter." ) self.repaint() elif self.saveName.displayText() == "": #checks if name is valid self.hedder.setText( "Warning: No name for image. Add a name before hitting save.") self.repaint() else: im = Image.open(self.previewImage) if "." in self.saveName.displayText( ): #checks if save name as extension fileName = self.saveName.displayText() else: #if not, defaults to extension of original image extension = findExtension(self.activeImage) fileName = self.saveName.displayText() + extension im.save(fileName) self.hedder.setText( "Image saved! Remember to change the save name before saving more images." )
class SearchWindow(QWidget): def __init__(self, rboost): super().__init__() self.rboost = rboost self.layout = QVBoxLayout() self._add_input_layout() self._add_output_layout() self.table_view = None self._add_table_view_layout() self.setLayout(self.layout) def _add_input_layout(self): self.input_layout = QHBoxLayout() self._add_label() self._add_input_line() search_button = QPushButton('Search') search_button.clicked.connect(self.show_results) clear_button = QPushButton('Clear') clear_button.clicked.connect(self.clear_results) self.input_layout.addWidget(search_button) self.input_layout.addWidget(clear_button) self.layout.addLayout(self.input_layout) def _add_label(self): label = QLabel('Label:') label.setFont(QFont('Arial', 16)) label.setAlignment(Qt.AlignVCenter | Qt.AlignRight) self.input_layout.addWidget(label) def _add_input_line(self): self.layout = QVBoxLayout() self.input_line = QLineEdit() self.input_line.setFont(QFont('Arial', 20)) self.input_line.setMaximumWidth(800) self.input_layout.addWidget(self.input_line) def _add_output_layout(self): self.output_layout = QVBoxLayout() self.label_not_found = QLabel() self.label_not_found.setFont(QFont('Times', 12)) self.label_not_found.setStyleSheet('QLabel {color : red}') self.similar_labels = QLabel() self.similar_labels.setFont(QFont('Times', 12)) self.output_layout.addWidget(self.label_not_found) self.output_layout.addWidget(self.similar_labels) self.layout.addLayout(self.output_layout) def _add_table_view_layout(self, df=None): self.table_view_layout = QHBoxLayout() if self.table_view is not None: self.table_view_layout.removeWidget(self.table_view) self.table_view.deleteLater() self.table_view = self._create_table_view(df=df) self.table_view_layout.addWidget(self.table_view) self.layout.addLayout(self.table_view_layout) def _create_table_view(self, df): if df is None: df = pd.DataFrame() model = PandasModel(df) table_view = QTableView() table_view.setModel(model) table_view.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) return table_view def _get_dataframe(self, label): dframe = self.rboost.database.dataframe colname = self.rboost.documents_df_cols['keywords'] df = dframe[dframe[colname].apply(lambda keywords: label in keywords)] return df def _get_messages(self, label, df): msg1 = f'Label "{label}" not found in RBoost!' if df.empty else '' similar_labels = [ lab for lab in self.rboost.labnames if 0 < lev.distance(label, lab) < 3 ] msg2 = 'Similar labels found: "' + '", "'.join(similar_labels) + '"' \ if similar_labels else 'Similar labels found: None' return msg1, msg2 def show_results(self): label = self.input_line.text() df = self._get_dataframe(label=label) msg1, msg2 = self._get_messages(label=label, df=df) self.label_not_found.setText(msg1) self.similar_labels.setText(msg2) self._add_table_view_layout(df=df) self.setLayout(self.layout) def clear_results(self): self.input_line.clear() self.label_not_found.clear() self.similar_labels.clear() empty_df = pd.DataFrame() self._add_table_view_layout(df=empty_df) self.setLayout(self.layout)
class ApplicationWindow(QMainWindow): def __init__(self, inputfile, frame): super().__init__() self.title = "GIXStapose" if inputfile is None: print("No input provided, showing simple cubic example") path = Path(__file__).parent / "data/sc10.pdb" inputfile = str(path.resolve()) self.basename = os.path.basename(inputfile).split(".")[0] self.render_counter = 0 self.diffract_counter = 0 self.init_diffractometer(inputfile, frame) self.initUI() def init_diffractometer(self, inputfile, frame): compound = compound_load(inputfile, frame) self.scene = create_scene(compound) mode = self.scene.device.mode self.title = f"GIXStapose ({mode} mode)" self.d = Diffractometer() self.d.load_compound(compound) def initUI(self): self.setWindowTitle(self.title) # Menubar menubar = self.menuBar() filemenu = menubar.addMenu("File") render = QAction("Render Scene", self) export = QAction("Export Diffraction Pattern", self) print_camera = QAction("Print Camera", self) filemenu.addAction(render) filemenu.addAction(export) filemenu.addAction(print_camera) filemenu.triggered[QAction].connect(self.processtrigger) self.main = QWidget() self.setCentralWidget(self.main) # creates 'top' and 'bot' horizontalgroupbox grid layout objects self.createGridLayout() windowlayout = QVBoxLayout() windowlayout.addWidget(self.tophorizontalGroupBox) windowlayout.addWidget(self.bothorizontalGroupBox) self.main.setLayout(windowlayout) self.show() def createGridLayout(self): from fresnel import interact # Top grid with sceneview and diffraction pattern self.tophorizontalGroupBox = QGroupBox() toplayout = QGridLayout() # Add the SceneView widget self.view = interact.SceneView(self.scene) self.view.rendering.connect(self.update_camera) toplayout.addWidget(self.view, 0, 0) # Add the diffraction widget dynamic_canvas = FigureCanvas(Figure(figsize=(15, 15))) toplayout.addWidget(dynamic_canvas, 0, 1) self.diffract_ax = dynamic_canvas.figure.add_subplot(111) self.diffract_ax.axis("off") self.diffract_ax.set_axis_off() self.plot_diffract(self.view.scene.camera) self.tophorizontalGroupBox.setLayout(toplayout) # Bottom grid with camera, buttons, zoom, sigma self.bothorizontalGroupBox = QGroupBox() botlayout = QGridLayout() # Camera printout self.label = QLabel() self.label.setText(self.camera_text(self.view.scene.camera)) # widget, row, col, rowspan, colspan botlayout.addWidget(self.label, 0, 0, 2, 1) # Buttons self.button100 = QPushButton("100") self.button100.setMaximumSize(QSize(100,40)) botlayout.addWidget(self.button100, 0, 1, 2, 1) self.button110 = QPushButton("110") self.button110.setMaximumSize(QSize(100,40)) botlayout.addWidget(self.button110, 0, 2, 2, 1) self.button111 = QPushButton("111") self.button111.setMaximumSize(QSize(100,40)) botlayout.addWidget(self.button111, 0, 3, 2, 1) # Connect buttons to moving the camera # thanks to this wonderful answer https://stackoverflow.com/a/57167056/11969403 self.button100.clicked.connect(lambda: self.move_camera((1,0,0))) self.button110.clicked.connect(lambda: self.move_camera((1,1,0))) self.button111.clicked.connect(lambda: self.move_camera((1,1,1))) # Add space between buttons and slider botlayout.setColumnMinimumWidth(4,350) # Zoom slider zlabel = QLabel("Zoom") zlabel.setAlignment(Qt.AlignCenter) botlayout.addWidget(zlabel, 0, 5) self.zooms = [i for i in range(1, self.d.N) if self.d.N % i == 0] self.zoomslider = QSlider(Qt.Horizontal) self.zoomslider.setMinimum(0) self.zoomslider.setMaximum(len(self.zooms)-1) self.zoomslider.setValue(self.zooms.index(self.d.zoom)) self.zoomslider.valueChanged.connect(self.change_zoom) self.zoomslider.setMaximumSize(QSize(600,30)) botlayout.addWidget(self.zoomslider, 1, 5) botlayout.setColumnMinimumWidth(6,50) self.bothorizontalGroupBox.setLayout(botlayout) def processtrigger(self, q): if q.text() == "Render Scene": print("Rendering...") output = fresnel.pathtrace(self.view.scene, light_samples=40, w=600, h=600) filename = f"{self.basename}_scene{self.render_counter}.png" image = PIL.Image.fromarray(output[:], mode='RGBA') image.save(filename, dpi=(300, 300)) old_camera = self.view.scene.camera print(f"Rendered {filename}") self.render_counter += 1 elif q.text() == "Export Diffraction Pattern": print("Saving diffraction pattern...") filename = f"{self.basename}_dp{self.diffract_counter}.png" plt.imsave(filename, self.dp, cmap="jet") print(f"Diffraction pattern saved as {filename}") self.diffract_counter += 1 elif q.text() == "Print Camera": print("Current camera is:\n") print(self.camera_text(self.view.scene.camera)) def change_zoom(self): self.d.zoom = self.zooms[self.zoomslider.value()] self.plot_diffract(self.view.scene.camera) def camera_text(self, camera): """ Convert a fresnel.Camera object to a readable string """ pos = camera.position look = camera.look_at up = camera.up text = "".join( [ "Camera\n", f" position = [{pos[0]:.3f}, {pos[1]:.3f}, {pos[2]:.3f}],\n", f" look_at = [{look[0]:.3f}, {look[1]:.3f}, {look[2]:.3f}],\n", f" up = [{up[0]:.3f}, {up[1]:.3f}, {up[2]:.3f}],\n", f" height = {camera.height:.3f}", ] ) return text def update_camera(self, camera): self.label.clear() # display the camera value self.label.setText(self.camera_text(camera)) self.plot_diffract(camera) def plot_diffract(self, camera): self.diffract_ax.clear() self.diffract_ax.axis("off") # diffraction pattern self.dp = self.d.diffract_from_camera(camera) self.diffract_ax.imshow(self.dp, cmap="jet") self.diffract_ax.figure.canvas.draw() self.repaint() def move_camera(self, pos): self.view.scene.camera = camera_from_pos(pos) #self.repaint() self.view._start_rendering() self.view.update()
class MainWindow(QMainWindow): def __init__(self): super().__init__() self.users = None self.camera = None self.playing = False self.frames = None self.capturing = False self.capture_frame = None self.flag_recognize = False self.model = None # key:user_id, value:((x,y,w,h),name,color,welcome_msg) self.recognized_faces = {} self.lbl_viewer = None self.btn_open_camera = None self.btn_close_camera = None self.btn_open_video = None self.btn_close_video = None self.cb_recognize = None self.btn_sync_face = None self.btn_capture = None self.lbl_capture_pic = None self.btn_capture_save = None self.init_ui() self.train_model() if os.path.exists('faces{}tmp'.format(os.sep)): shutil.rmtree('faces{}tmp'.format(os.sep)) def init_ui(self): self.setFixedSize(Config.width, Config.height) qr = self.frameGeometry() cp = QDesktopWidget().availableGeometry().center() qr.moveCenter(cp) self.move(qr.topLeft()) self.setWindowIcon(QIcon('icons/icon.png')) self.setWindowTitle('客户端') menu_bar = self.menuBar() menu_bar.setNativeMenuBar(False) manage_menu = menu_bar.addMenu("信息管理") client_login_action = manage_menu.addAction("服务器连接") client_login_action.triggered.connect(self.manage_client_login) user_action = manage_menu.addAction("用户管理") user_action.triggered.connect(self.manage_user) setting_menu = menu_bar.addMenu("设置") server_action = setting_menu.addAction("服务器") server_action.triggered.connect(self.setting_server) dimension_action = setting_menu.addAction("窗口尺寸") dimension_action.triggered.connect(self.setting_dimension) help_menu = menu_bar.addMenu("帮助") client_register_action = help_menu.addAction("客户端注册") client_register_action.triggered.connect(self.help_client_register) status_bar = self.statusBar() status_bar.showMessage("欢迎使用客户端") self.lbl_viewer = QLabel(self) self.lbl_viewer.setGeometry(QRect(10, 26, Config.width - 130, Config.height - 60)) self.lbl_viewer.setText('没有图像') font = QFont() font.setPointSize(20) self.lbl_viewer.setFont(font) self.lbl_viewer.setAlignment(Qt.AlignCenter) self.lbl_viewer.setFrameShape(QFrame.StyledPanel) self.btn_open_camera = QPushButton(self) self.btn_open_camera.setGeometry(QRect(Config.width - 110, 10, 100, 26)) self.btn_open_camera.setText('打开摄像头') self.btn_open_camera.clicked.connect(self.btn_click) self.btn_close_camera = QPushButton(self) self.btn_close_camera.setGeometry(QRect(Config.width - 110, 46, 100, 26)) self.btn_close_camera.setText('关闭摄像头') self.btn_close_camera.setDisabled(True) self.btn_close_camera.clicked.connect(self.btn_click) self.btn_open_video = QPushButton(self) self.btn_open_video.setGeometry(QRect(Config.width - 110, 82, 100, 26)) self.btn_open_video.setText('播放视频') self.btn_open_video.clicked.connect(self.btn_click) self.btn_close_video = QPushButton(self) self.btn_close_video.setGeometry(QRect(Config.width - 110, 118, 100, 26)) self.btn_close_video.setText('停止播放') self.btn_close_video.setDisabled(True) self.btn_close_video.clicked.connect(self.btn_click) self.cb_recognize = QCheckBox(self) self.cb_recognize.setText('启动识别') self.cb_recognize.setDisabled(True) self.cb_recognize.setGeometry(QRect(Config.width - 108, 154, 100, 26)) self.cb_recognize.clicked.connect(self.cb_click) self.btn_sync_face = QPushButton(self) self.btn_sync_face.setGeometry(QRect(Config.width - 110, 190, 100, 26)) self.btn_sync_face.setText("同步数据") self.btn_sync_face.clicked.connect(self.btn_click) self.btn_capture = QPushButton(self) self.btn_capture.setGeometry(QRect(Config.width - 110, Config.height - 200, 100, 26)) self.btn_capture.setText('截屏') self.btn_capture.setDisabled(True) self.btn_capture.clicked.connect(self.btn_click) self.lbl_capture_pic = QLabel(self) self.lbl_capture_pic.setGeometry(QRect(Config.width - 110, Config.height - 160, 100, 100)) self.lbl_capture_pic.setAlignment(Qt.AlignCenter) self.lbl_capture_pic.setFrameShape(QFrame.StyledPanel) self.btn_capture_save = QPushButton(self) self.btn_capture_save.setGeometry(QRect(Config.width - 110, Config.height - 60, 100, 26)) self.btn_capture_save.setText('保存截图') self.btn_capture_save.setDisabled(True) self.btn_capture_save.clicked.connect(self.btn_click) def btn_click(self): btn = self.sender() if btn == self.btn_open_camera: self.btn_open_camera.setDisabled(True) self.btn_close_camera.setDisabled(False) self.btn_capture.setDisabled(False) self.cb_recognize.setDisabled(False) self.camera = cv2.VideoCapture(0) self.frames = collections.deque(maxlen=33) self.start_play() elif btn == self.btn_close_camera: self.stop_play() self.btn_open_camera.setDisabled(False) self.btn_close_camera.setDisabled(True) self.btn_capture.setDisabled(True) self.cb_recognize.setDisabled(True) elif btn == self.btn_sync_face: self.btn_sync_face.setDisabled(True) self.sync_data() elif btn == self.btn_capture: self.capturing = True self.btn_capture_save.setDisabled(False) elif btn == self.btn_capture_save: AddUserFace(self.capture_frame).exec_() self.train_model() def cb_click(self): cb = self.sender() if cb == self.cb_recognize: if cb.isChecked(): self.flag_recognize = True else: self.flag_recognize = False def start_play(self): self.playing = True play_thread = Thread(target=self.play) play_thread.start() recognize_thread = Thread(target=self.recognize) recognize_thread.start() def stop_play(self): self.playing = False def play(self): while self.camera.isOpened(): try: if not self.playing: break ret, frame = self.camera.read() if ret: if self.flag_recognize: self.frames.appendleft(frame.copy()) faces = self.recognized_faces.copy() for user_id in faces: face = faces[user_id] x, y, w, h = face[0] cv2.rectangle(frame, (x, y), (x + w, y + h), tuple(list(map(int, face[2].split(',')))), 1, cv2.LINE_AA) if Config.show_name: cv2.putText(frame, face[1], (x, y - 15), cv2.FONT_HERSHEY_SIMPLEX, 2, tuple(list(map(int, face[2].split(',')))), 2) if Config.show_match_result: cv2.putText(frame, 'Match degree:' + str(face[3]), (10, 40), cv2.FONT_HERSHEY_SIMPLEX, 2, tuple(list(map(int, face[2].split(',')))), 2) img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) image = QImage(img, img.shape[1], img.shape[0], img.shape[1] * 3, QImage.Format_RGB888) pix_map = QPixmap.fromImage(image) pix_map = pix_map.scaled(Config.width - 130, Config.height - 60, Qt.KeepAspectRatio) self.lbl_viewer.setPixmap(pix_map) # 保存截图 if self.capturing: self.capture_frame = frame.copy() pix_map = pix_map.scaled(100, 100, Qt.KeepAspectRatio) self.lbl_capture_pic.setPixmap(pix_map) self.capturing = False except Exception as e: print(e) self.lbl_viewer.clear() self.camera.release() def recognize(self): classifier = HaarcascadeDetective().get_face_classifier() while self.playing: try: if len(self.frames) == 0: time.sleep(0.05) continue if self.flag_recognize: frame = self.frames.pop() faces = classifier.get_faces_position(frame) self.recognized_faces.clear() for (x, y, w, h) in faces: face = frame[y:y + h, x:x + w] if self.model is not None: gray = cv2.cvtColor(face, cv2.COLOR_BGR2GRAY) params = self.model.predict(gray) user = self.find_user_by_id(params[0]) if user is not None: self.recognized_faces[user[1]] = ((x, y, w, h), user[2], user[3], int(params[1])) else: self.recognized_faces['-1'] = ((x, y, w, h), 'No this user', '255,0,0', 0) else: self.recognized_faces['-2'] = ((x, y, w, h), 'No model', '255,0,0', 0) except Exception as e: print(e) def sync_data(self): client = DbHelper.query_client() if client is None: Tool.show_msg_box('客户端尚未认证!') self.btn_sync_face.setDisabled(False) return shutil.rmtree('faces') os.mkdir('faces') DbHelper.delete_users() users = Tool.user_face_list(client[0]) if users is not None: for user in users: user_id = DbHelper.insert_user(user['rowId'], user['name'], user['color'], user['clientId']) faces = user['faces'] for face in faces: Tool.download_face(face['rowId'], str(user_id)) self.btn_sync_face.setDisabled(False) # 重新训练模型 self.train_model() def train_model(self): self.users = DbHelper.query_users() y, x = [], [] faces_dir = os.listdir('faces') for user_dir in faces_dir: if user_dir == 'tmp': continue faces = os.listdir('faces{}{}'.format(os.sep, user_dir)) for face in faces: y.append(int(user_dir)) im = cv2.imread('faces{}{}{}{}'.format(os.sep, user_dir, os.sep, face), 0) x.append(np.asarray(im, dtype=np.uint8)) if len(x) != 0 and len(y) != 0: self.model = cv2.face.LBPHFaceRecognizer_create() self.model.train(np.asarray(x), np.asarray(y, dtype=np.int64)) def find_user_by_id(self, user_id): for user in self.users: if user[0] == user_id: return user def closeEvent(self, *args, **kwargs): self.playing = False if os.path.exists('faces{}tmp'.format(os.sep)): shutil.rmtree('faces{}tmp'.format(os.sep)) @staticmethod def manage_client_login(): ClientLogin().exec_() @staticmethod def manage_user(): AddUser().exec_() @staticmethod def setting_server(): ServerSetting().exec_() @staticmethod def setting_dimension(): DimensionSetting().exec_() @staticmethod def help_client_register(): ClientRegister().exec_()
class Artigence(QMainWindow): def __init__(self): super(Artigence, self).__init__() # Basic Settings self.setGeometry(300, 200, 682, 422) self.setMinimumSize(QSize(682, 422)) self.setMaximumSize(QSize(682, 422)) self.setWindowIcon(QIcon("arti.PNG")) self.setWindowTitle("Artigence Home") # Color Scheme self.palette = QPalette() self.palette.setColor(self.palette.Window, QColor('#000000')) self.palette.setColor(self.palette.WindowText, QColor('#FFFFFF')) self.setPalette(self.palette) self.light_palette = QPalette() self.light_palette.setColor(self.light_palette.Window, QColor('#FFFFFF')) self.light_palette.setColor(self.light_palette.WindowText, QColor('#000000')) # Setting MenuBar self.menubar = QMenuBar(self) self.menubar.setGeometry(0, 0, 682, 21) self.date_menu = QMenu(self.menubar) self.date_menu.setTitle(str(datetime.now().strftime('%d-%m-%Y'))) self.theme_menu = QMenu(self.menubar) self.theme_menu.setTitle('Theme') self.dark_theme = QAction('Dark Theme') self.dark_theme.setShortcut(QKeySequence('Ctrl+Shift+D')) self.theme_menu.addAction(self.dark_theme) self.dark_theme.triggered.connect(lambda: self.dark()) self.light_theme = QAction('Light Theme') self.light_theme.setShortcut(QKeySequence('Ctrl+Shift+L')) self.theme_menu.addAction(self.light_theme) self.light_theme.triggered.connect(lambda: self.light()) self.app_menu = QMenu(self.menubar) self.app_menu.setTitle('Apps') self.calculator_menu = QAction('Calculator') self.calculator_menu.setShortcut(QKeySequence('Alt+C')) self.app_menu.addAction(self.calculator_menu) self.calculator_menu.triggered.connect(lambda: self.calculator_func()) self.game_menu = QAction('GameHub') self.game_menu.setShortcut(QKeySequence('Alt+G')) self.app_menu.addAction(self.game_menu) self.game_menu.triggered.connect(lambda: self.games_func()) self.music_menu = QAction('Muse (Music)') self.music_menu.setShortcut(QKeySequence('Alt+M')) self.app_menu.addAction(self.music_menu) self.music_menu.triggered.connect(lambda: self.music_func()) self.news_menu = QAction('News') self.news_menu.setShortcut(QKeySequence('Alt+E')) self.app_menu.addAction(self.news_menu) self.news_menu.triggered.connect(lambda: self.news_func()) self.notepad_menu = QAction('Notepad') self.notepad_menu.setShortcut(QKeySequence('Alt+N')) self.app_menu.addAction(self.notepad_menu) self.notepad_menu.triggered.connect(lambda: self.notepad_func()) self.pronunciator = QAction('Pronunciator') self.pronunciator.setShortcut(QKeySequence('Alt+P')) self.app_menu.addAction(self.pronunciator) self.pronunciator.triggered.connect(lambda: self.pronunciator_func()) self.translate_menu = QAction('Translate') self.translate_menu.setShortcut(QKeySequence('Alt+T')) self.app_menu.addAction(self.translate_menu) self.translate_menu.triggered.connect(lambda: self.translate_func()) self.weather_menu = QAction('Weather') self.weather_menu.setShortcut(QKeySequence('Alt+W')) self.app_menu.addAction(self.weather_menu) self.weather_menu.triggered.connect(lambda: self.weather_func()) self.setMenuBar(self.menubar) self.menubar.addAction(self.date_menu.menuAction()) self.menubar.addAction(self.theme_menu.menuAction()) self.menubar.addAction(self.app_menu.menuAction()) # Creating Widgets self.query = QLineEdit(self) self.query.setGeometry(QRect(20, 30, 451, 41)) self.query.setMinimumSize(QSize(451, 41)) self.query.setMaximumSize(QSize(451, 41)) self.query.setPlaceholderText("Enter your Query Here:") self.query.setFont(QFont('Roboto', 16)) self.query.setClearButtonEnabled(True) self.update = QPushButton(self) self.update.setGeometry(QRect(491, 30, 171, 41)) self.update.setMinimumSize(QSize(1, 1)) self.update.setMaximumSize(QSize(171, 51)) self.update.setText("What's New in the Updates?") self.update.setCursor(QCursor(Qt.PointingHandCursor)) self.suggestions = QLabel(self) self.suggestions.setGeometry(QRect(20, 220, 111, 31)) self.suggestions.setMinimumSize(QSize(111, 31)) self.suggestions.setMaximumSize(QSize(111, 31)) self.suggestions.setText("Suggestions:") self.suggestions.setFont(QFont('Roboto', 14)) self.chrome = QPushButton(self) self.chrome.setGeometry(QRect(20, 260, 91, 31)) self.chrome.setCursor(QCursor(Qt.PointingHandCursor)) self.chrome.setText('Open Chrome') self.games = QPushButton(self) self.games.setGeometry(QRect(420, 260, 91, 31)) self.games.setCursor(QCursor(Qt.PointingHandCursor)) self.games.setText('Games') self.cmd = QPushButton(self) self.cmd.setGeometry(QRect(160, 260, 91, 31)) self.cmd.setCursor(QCursor(Qt.PointingHandCursor)) self.cmd.setText('Open Cmd') self.joke = QPushButton(self) self.joke.setGeometry(QRect(160, 310, 91, 31)) self.joke.setCursor(QCursor(Qt.PointingHandCursor)) self.joke.setText('Joke Please!!') self.music = QPushButton(self) self.music.setGeometry(QRect(290, 260, 91, 31)) self.music.setCursor(QCursor(Qt.PointingHandCursor)) self.music.setText('Music') self.youtube = QPushButton(self) self.youtube.setGeometry(QRect(290, 310, 91, 31)) self.youtube.setCursor(QCursor(Qt.PointingHandCursor)) self.youtube.setText('Youtube') self.time = QPushButton(self) self.time.setGeometry(QRect(20, 310, 91, 31)) self.time.setCursor(QCursor(Qt.PointingHandCursor)) self.time.setText('Tell Time') self.weather = QPushButton(self) self.weather.setGeometry(QRect(420, 310, 91, 31)) self.weather.setCursor(QCursor(Qt.PointingHandCursor)) self.weather.setText('Weather') self.calculator = QPushButton(self) self.calculator.setGeometry(QRect(550, 260, 101, 31)) self.calculator.setCursor(QCursor(Qt.PointingHandCursor)) self.calculator.setText('Calculator') self.wikipedia = QPushButton(self) self.wikipedia.setGeometry(QRect(550, 310, 101, 31)) self.wikipedia.setCursor(QCursor(Qt.PointingHandCursor)) self.wikipedia.setText('India Wikipedia') self.news = QPushButton(self) self.news.setGeometry(QRect(20, 360, 91, 31)) self.news.setCursor(QCursor(Qt.PointingHandCursor)) self.news.setText('Latest News') self.meaning = QPushButton(self) self.meaning.setGeometry(QRect(420, 360, 231, 31)) self.meaning.setCursor(QCursor(Qt.PointingHandCursor)) self.meaning.setText('Meaning of Obsolete (or any word)') self.harry_potter = QPushButton(self) self.harry_potter.setGeometry(QRect(290, 360, 91, 31)) self.harry_potter.setCursor(QCursor(Qt.PointingHandCursor)) self.harry_potter.setText('Harry Potter') self.translate = QPushButton(self) self.translate.setGeometry(QRect(160, 360, 91, 31)) self.translate.setCursor(QCursor(Qt.PointingHandCursor)) self.translate.setText('Open Translate') self.line = QFrame(self) self.line.setGeometry(QRect(20, 200, 661, 16)) self.line.setFrameShape(QFrame.HLine) self.line.setFrameShadow(QFrame.Sunken) self.label = QLabel(self) self.label.setGeometry(QRect(20, 100, 631, 91)) self.label.setFont(QFont('Roboto', 12)) self.label.setTextFormat(Qt.AutoText) self.label.setWordWrap(True) self.wish() # Making the Widgets Functional self.query.returnPressed.connect(lambda: self.on_enter()) self.query.returnPressed.connect(lambda: self.clear_text()) self.update.clicked.connect(lambda: self.update_func()) self.music.clicked.connect(lambda: self.music_func()) self.games.clicked.connect(lambda: self.games_func()) self.calculator.clicked.connect(lambda: self.calculator_func()) self.weather.clicked.connect(lambda: self.weather_func()) self.news.clicked.connect(lambda: self.news_func()) self.translate.clicked.connect(lambda: self.translate_func()) self.time.clicked.connect(lambda: self.time_func()) self.joke.clicked.connect(lambda: self.joke_func()) self.youtube.clicked.connect(lambda: self.youtube_func()) self.wikipedia.clicked.connect(lambda: self.wikipedia_func()) self.chrome.clicked.connect(lambda: self.chrome_func()) self.cmd.clicked.connect(lambda: self.cmd_func()) self.meaning.clicked.connect(lambda: self.meaning_func()) self.harry_potter.clicked.connect(lambda: self.potter_func()) def pronunciator_func(self): self.speak('Opening Pronunciator') from pronunciator import Pronunciator self.pronunciator_win = Pronunciator() self.pronunciator_win.show() def pong_func(self): import pong def notepad_func(self): self.speak('Opening Notepad') from notepad import Notepad self.notepad_win = Notepad() self.notepad_win.show() def update_func(self): os.startfile('Each Version Updates.txt') def translate_func(self): self.speak( 'Opening Translate\nPlease Wait as opening Translate may take up to 4-5 seconds' ) from translate import Translate self.translate_win = Translate() self.translate_win.show() def games_func(self): self.speak('Opening GameHub') from games import GameHub self.game_win = GameHub() self.game_win.show() def weather_func(self): self.speak('Opening Weather.') from weather import Weather self.weather_win = Weather() self.weather_win.show() def music_func(self): self.speak('Opening Muse') from music import Music self.music_win = Music() self.music_win.show() def calculator_func(self): self.speak('Opening Calculator.') from calculator import Calculator self.calculator_win = Calculator() self.calculator_win.show() def news_func(self): self.speak('Opening News.') from news import News self.news_win = News() self.news_win.show() self.speak( 'Welcome to News.\nThese are the latest international headlines according to BBC News Network.' ) def chrome_func(self): try: chrome_path = 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe' os.startfile(chrome_path) self.speak('Opening Chrome.') except Exception: self.speak( 'No Google Chrome installation found on the host device.') def cmd_func(self): cmd_path = 'C:\\Windows\\system32\\cmd.exe' os.startfile(cmd_path) self.speak('Opening Command Prompt.') def time_func(self): question = 'time' app_id = 'LLQ4QY-A7K3LEL4T8' client = wolframalpha.Client(app_id) res = client.query(question) answer = next(res.results).text self.speak(answer) def joke_func(self): self.speak(pyjokes.get_joke()) def youtube_func(self): webbrowser.open('https://www.youtube.com') self.speak('Opening Youtube.') def wikipedia_func(self): try: self.speak('Searching Wikipedia. Please Wait...') query = 'India'.replace('wikipedia', '') result = wikipedia.summary(query, sentences=1) self.speak('According to Wikipedia...') self.speak(result) except Exception as e: self.speak(e) def meaning_func(self): question = 'obsolete' app_id = 'LLQ4QY-A7K3LEL4T8' client = wolframalpha.Client(app_id) res = client.query(question) answer = next(res.results).text self.speak(answer) def potter_func(self): new = 2 google_url = "http://google.com/?#q=" webbrowser.open(google_url + 'Harry Potter', new=new) def clear_text(self): self.query.clear() def on_enter(self): user_query = self.query.text().lower() if 'wikipedia' in user_query: try: self.speak('Searching Wikipedia. Please Wait...') user_query = user_query.replace('wikipedia', '') result = wikipedia.summary(user_query, sentences=1) self.speak('According to Wikipedia...') self.speak(result) except Exception as e: self.speak('Please try again later.') self.speak(e) elif 'youtube' in user_query: webbrowser.open('https://www.youtube.com') self.speak('Opening Youtube.') elif 'google' in user_query: webbrowser.open('https://www.google.com/') self.speak('Opening Google.') elif 'chrome' in user_query: # You'll have to download google chrome first on your desktop/pc. try: chrome_path = 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe' os.startfile(chrome_path) self.speak('Opening Chrome') except Exception: self.speak( 'No Google Chrome installation found on the host device.') elif 'cmd' in user_query: cmd_path = 'C:\\Windows\\system32\\cmd.exe' os.startfile(cmd_path) self.speak('Opening Command Prompt.') elif 'control panel' in user_query: cp_path = 'C:\\Windows\\system32\\control.exe' os.startfile(cp_path) self.speak('Opening Control Panel.') elif 'bye' in user_query or 'goodbye' in user_query or 'good night' in user_query or 'see you later' in user_query: self.speak(random.choice(self.bye)) sys.exit() elif 'hello' in user_query or 'hi' in user_query: self.speak(random.choice(self.hello)) elif 'joke' in user_query: self.speak(pyjokes.get_joke()) elif 'who are you' in user_query: self.speak('I am Artigence, your artificial intelligence.') elif 'map' in user_query or 'maps' in user_query: self.speak('Opening Google Maps.') webbrowser.open("https://www.google.com/maps") elif 'open calculator' in user_query or 'calculator' in user_query: self.calculator_func() elif 'news' in user_query: self.news_func() self.speak( 'Welcome to News.\nThese are the latest international headlines according to BBC News Network.' ) elif 'weather' in user_query: self.weather_func() elif 'games' in user_query: self.games_func() elif 'pronunciator' in user_query or 'pronounce' in user_query: self.pronunciator_func() elif 'translate' in user_query: self.translate_func() elif 'music' in user_query: self.music_func() elif 'notepad' in user_query: self.notepad_func() else: try: question = user_query app_id = 'LLQ4QY-A7K3LEL4T8' client = wolframalpha.Client(app_id) res = client.query(question) answer = next(res.results).text self.label.setText(answer) self.label.adjustSize() except: new = 2 google_url = "http://google.com/?#q=" query = user_query webbrowser.open(google_url + query, new=new) # The A.I. will speak through this function def speak(self, audio): self.engine = pyttsx3.init('sapi5') voices = self.engine.getProperty('voices') self.engine.setProperty('voice', voices[1].id) self.engine.setProperty('rate', 165) self.label.setText(audio) self.engine.say(audio) self.engine.runAndWait() self.label.clear() def wish(self): hour = int(datetime.now().hour) if 0 <= hour < 12: self.speak('Good Morning.') elif 12 <= hour < 18: self.speak('Good Afternoon.') else: self.speak('Good Evening.') self.speak('I am Artigence.') self.speak('How may I help you today') hello = ['Kon\'nichiwa', 'Ciao', 'Hola', 'Bonjour', 'Hello', 'Hi', 'Hiya'] bye = [ 'Adios', 'Goodbye', 'Bye-Bye', 'See you next time.', 'Artigence Out.', 'It was nice talking to you sir. Have a nice day.' ] def dark(self): self.setPalette(self.palette) def light(self): self.setPalette(self.light_palette)
class View(QMainWindow): def __init__(self, model, controller): super().__init__() self._model = model self._controller = controller self.segmentcursor = False self.togglecolors = {"#1f77b4": "m", "m": "#1f77b4"} ################################################################# # define GUI layout and connect input widgets to external slots # ################################################################# self.setWindowTitle("biopeaks") self.setGeometry(50, 50, 1750, 750) self.setWindowIcon(QIcon(":/python_icon.png")) # figure0 for signal self.figure0 = Figure() self.canvas0 = FigureCanvas(self.figure0) # Enforce minimum height, otherwise resizing with self.splitter causes # mpl to throw an error because figure is resized to height 0. The # widget can still be fully collapsed with self.splitter- self.canvas0.setMinimumHeight(1) # in pixels self.ax00 = self.figure0.add_subplot(1, 1, 1) self.ax00.set_frame_on(False) self.figure0.subplots_adjust(left=0.04, right=0.98, bottom=0.25) self.line00 = None self.scat = None self.segmentspan = None # figure1 for marker self.figure1 = Figure() self.canvas1 = FigureCanvas(self.figure1) self.canvas1.setMinimumHeight(1) self.ax10 = self.figure1.add_subplot(1, 1, 1, sharex=self.ax00) self.ax10.get_xaxis().set_visible(False) self.ax10.set_frame_on(False) self.figure1.subplots_adjust(left=0.04, right=0.98) self.line10 = None # figure2 for statistics self.figure2 = Figure() self.canvas2 = FigureCanvas(self.figure2) self.canvas2.setMinimumHeight(1) self.ax20 = self.figure2.add_subplot(3, 1, 1, sharex=self.ax00) self.ax20.get_xaxis().set_visible(False) self.ax20.set_frame_on(False) self.line20 = None self.ax21 = self.figure2.add_subplot(3, 1, 2, sharex=self.ax00) self.ax21.get_xaxis().set_visible(False) self.ax21.set_frame_on(False) self.line21 = None self.ax22 = self.figure2.add_subplot(3, 1, 3, sharex=self.ax00) self.ax22.get_xaxis().set_visible(False) self.ax22.set_frame_on(False) self.line22 = None self.figure2.subplots_adjust(left=0.04, right=0.98) # navigation bar self.navitools = CustomNavigationToolbar(self.canvas0, self) # peak editing self.editcheckbox = QCheckBox("editable", self) self.editcheckbox.stateChanged.connect(self._model.set_peakseditable) # peak saving batch self.savecheckbox = QCheckBox("save during batch processing", self) self.savecheckbox.stateChanged.connect(self._model.set_savebatchpeaks) # peak auto-correction batch self.correctcheckbox = QCheckBox("correct during batch processing", self) self.correctcheckbox.stateChanged.connect( self._model.set_correctbatchpeaks) # selecting stats for saving self.periodcheckbox = QCheckBox("period", self) self.periodcheckbox.stateChanged.connect( lambda: self.select_stats("period")) self.ratecheckbox = QCheckBox("rate", self) self.ratecheckbox.stateChanged.connect( lambda: self.select_stats("rate")) self.tidalampcheckbox = QCheckBox("tidal amplitude", self) self.tidalampcheckbox.stateChanged.connect( lambda: self.select_stats("tidalamp")) # channel selection self.sigchanmenulabel = QLabel("biosignal") self.sigchanmenu = QComboBox(self) self.sigchanmenu.addItem("A1") self.sigchanmenu.addItem("A2") self.sigchanmenu.addItem("A3") self.sigchanmenu.addItem("A4") self.sigchanmenu.addItem("A5") self.sigchanmenu.addItem("A6") self.sigchanmenu.currentTextChanged.connect(self._model.set_signalchan) # initialize with default value self._model.set_signalchan(self.sigchanmenu.currentText()) self.markerchanmenulabel = QLabel("marker") self.markerchanmenu = QComboBox(self) self.markerchanmenu.addItem("none") self.markerchanmenu.addItem("I1") self.markerchanmenu.addItem("I2") self.markerchanmenu.addItem("A1") self.markerchanmenu.addItem("A2") self.markerchanmenu.addItem("A3") self.markerchanmenu.addItem("A4") self.markerchanmenu.addItem("A5") self.markerchanmenu.addItem("A6") self.markerchanmenu.currentTextChanged.connect( self._model.set_markerchan) # initialize with default value self._model.set_markerchan(self.markerchanmenu.currentText()) # processing mode (batch or single file) self.batchmenulabel = QLabel("mode") self.batchmenu = QComboBox(self) self.batchmenu.addItem("single file") self.batchmenu.addItem("multiple files") self.batchmenu.currentTextChanged.connect(self._model.set_batchmode) self.batchmenu.currentTextChanged.connect(self.toggle_options) # initialize with default value self._model.set_batchmode(self.batchmenu.currentText()) self.toggle_options(self.batchmenu.currentText()) # modality selection self.modmenulabel = QLabel("modality") self.modmenu = QComboBox(self) self.modmenu.addItem("ECG") self.modmenu.addItem("PPG") self.modmenu.addItem("RESP") self.modmenu.currentTextChanged.connect(self._model.set_modality) self.modmenu.currentTextChanged.connect(self.toggle_options) # initialize with default value self._model.set_modality(self.modmenu.currentText()) self.toggle_options(self.modmenu.currentText()) # segment selection; this widget can be openend / set visible from # the menu and closed from within itself (see mapping of segmentermap); # it provides utilities to select a segment from the signal self.segmentermap = QSignalMapper(self) self.segmenter = QDockWidget("select a segment", self) # disable closing such that widget can only be closed by confirming # selection or custom button self.segmenter.setFeatures(QDockWidget.NoDockWidgetFeatures) # Limit number of decimals to four. regex = QRegExp("[0-9]*\.?[0-9]{4}") validator = QRegExpValidator(regex) self.startlabel = QLabel("start") self.startedit = QLineEdit() self.startedit.setValidator(validator) self.endlabel = QLabel("end") self.endedit = QLineEdit() self.endedit.setValidator(validator) segmentfromcursor = QAction(QIcon(":/mouse_icon.png"), "select with mouse", self) segmentfromcursor.triggered.connect(self.enable_segmentedit) self.startedit.addAction(segmentfromcursor, QLineEdit.TrailingPosition) self.endedit.addAction(segmentfromcursor, QLineEdit.TrailingPosition) self.previewedit = QPushButton("preview segment") lambdafn = lambda: self._model.set_segment( [self.startedit.text(), self.endedit.text()]) self.previewedit.clicked.connect(lambdafn) self.confirmedit = QPushButton("confirm segment") self.confirmedit.clicked.connect(self._controller.segment_signal) self.confirmedit.clicked.connect(self.segmentermap.map) self.segmentermap.setMapping(self.confirmedit, 0) self.abortedit = QPushButton("abort segmentation") self.abortedit.clicked.connect(self.segmentermap.map) # reset the segment to None self.segmentermap.setMapping(self.abortedit, 2) self.segmenterlayout = QFormLayout() self.segmenterlayout.addRow(self.startlabel, self.startedit) self.segmenterlayout.addRow(self.endlabel, self.endedit) self.segmenterlayout.addRow(self.previewedit) self.segmenterlayout.addRow(self.confirmedit) self.segmenterlayout.addRow(self.abortedit) self.segmenterwidget = QWidget() self.segmenterwidget.setLayout(self.segmenterlayout) self.segmenter.setWidget(self.segmenterwidget) self.segmenter.setVisible(False) self.segmenter.setAllowedAreas(Qt.RightDockWidgetArea) self.addDockWidget(Qt.RightDockWidgetArea, self.segmenter) # Set up dialog to gather user input for custom files. regex = QRegExp("[1-9][0-9]") validator = QRegExpValidator(regex) self.signallabel = QLabel("biosignal column") self.signaledit = QLineEdit() self.signaledit.setValidator(validator) self.markerlabel = QLabel("marker column") self.markeredit = QLineEdit() self.markeredit.setValidator(validator) regex = QRegExp("[0-9]{2}") validator = QRegExpValidator(regex) self.headerrowslabel = QLabel("number of header rows") self.headerrowsedit = QLineEdit() self.headerrowsedit.setValidator(validator) regex = QRegExp("[0-9]{5}") validator = QRegExpValidator(regex) self.sfreqlabel = QLabel("sampling rate") self.sfreqedit = QLineEdit() self.sfreqedit.setValidator(validator) self.separatorlabel = QLabel("column separator") self.separatormenu = QComboBox(self) self.separatormenu.addItem("comma") self.separatormenu.addItem("tab") self.separatormenu.addItem("colon") self.separatormenu.addItem("space") self.continuecustomfile = QPushButton("continue loading file") self.continuecustomfile.clicked.connect(self.set_customheader) self.customfiledialog = QDialog() self.customfiledialog.setWindowTitle("custom file info") self.customfiledialog.setWindowIcon(QIcon(":/file_icon.png")) self.customfiledialog.setWindowFlags( Qt.WindowCloseButtonHint ) # remove help button by only setting close button self.customfilelayout = QFormLayout() self.customfilelayout.addRow(self.signallabel, self.signaledit) self.customfilelayout.addRow(self.markerlabel, self.markeredit) self.customfilelayout.addRow(self.separatorlabel, self.separatormenu) self.customfilelayout.addRow(self.headerrowslabel, self.headerrowsedit) self.customfilelayout.addRow(self.sfreqlabel, self.sfreqedit) self.customfilelayout.addRow(self.continuecustomfile) self.customfiledialog.setLayout(self.customfilelayout) # set up menubar menubar = self.menuBar() # signal menu signalmenu = menubar.addMenu("biosignal") openSignal = signalmenu.addMenu("load") openEDF = QAction("EDF", self) openEDF.triggered.connect(lambda: self._model.set_filetype("EDF")) openEDF.triggered.connect(self._controller.get_fpaths) openSignal.addAction(openEDF) openOpenSignals = QAction("OpenSignals", self) openOpenSignals.triggered.connect( lambda: self._model.set_filetype("OpenSignals")) openOpenSignals.triggered.connect(self._controller.get_fpaths) openSignal.addAction(openOpenSignals) openCustom = QAction("Custom", self) openCustom.triggered.connect( lambda: self._model.set_filetype("Custom")) openCustom.triggered.connect(lambda: self.customfiledialog.exec_()) openSignal.addAction(openCustom) segmentSignal = QAction("select segment", self) segmentSignal.triggered.connect(self.segmentermap.map) self.segmentermap.setMapping(segmentSignal, 1) signalmenu.addAction(segmentSignal) self.segmentermap.mapped.connect(self.toggle_segmenter) saveSignal = QAction("save", self) saveSignal.triggered.connect(self._controller.get_wpathsignal) signalmenu.addAction(saveSignal) # peak menu peakmenu = menubar.addMenu("peaks") findPeaks = QAction("find", self) findPeaks.triggered.connect(self._controller.find_peaks) peakmenu.addAction(findPeaks) autocorrectPeaks = QAction("autocorrect", self) autocorrectPeaks.triggered.connect(self._controller.autocorrect_peaks) peakmenu.addAction(autocorrectPeaks) savePeaks = QAction("save", self) savePeaks.triggered.connect(self._controller.get_wpathpeaks) peakmenu.addAction(savePeaks) loadPeaks = QAction("load", self) loadPeaks.triggered.connect(self._controller.get_rpathpeaks) peakmenu.addAction(loadPeaks) # stats menu statsmenu = menubar.addMenu("statistics") calculateStats = QAction("calculate", self) calculateStats.triggered.connect(self._controller.calculate_stats) statsmenu.addAction(calculateStats) saveStats = QAction("save", self) saveStats.triggered.connect(self._controller.get_wpathstats) statsmenu.addAction(saveStats) # set up status bar to display error messages and current file path self.statusBar = QStatusBar() self.setStatusBar(self.statusBar) self.progressBar = QProgressBar(self) self.progressBar.setRange(0, 1) self.statusBar.addPermanentWidget(self.progressBar) self.currentFile = QLabel() self.statusBar.addPermanentWidget(self.currentFile) # set up the central widget containing the plot and navigationtoolbar self.centwidget = QWidget() self.setCentralWidget(self.centwidget) # connect canvas0 to keyboard and mouse input for peak editing; # only widgets (e.g. canvas) that currently have focus capture # keyboard input: "You must enable keyboard focus for a widget if # it processes keyboard events." self.canvas0.setFocusPolicy(Qt.ClickFocus) self.canvas0.setFocus() self.canvas0.mpl_connect("key_press_event", self._controller.edit_peaks) self.canvas0.mpl_connect("button_press_event", self.get_xcursor) # arrange the three figure canvases in splitter object self.splitter = QSplitter(Qt.Vertical) # setting opaque resizing to false is important, since resizing gets # very slow otherwise once axes are populated self.splitter.setOpaqueResize(False) self.splitter.addWidget(self.canvas0) self.splitter.addWidget(self.canvas1) self.splitter.addWidget(self.canvas2) self.splitter.setChildrenCollapsible(False) # define GUI layout self.vlayout0 = QVBoxLayout(self.centwidget) self.vlayout1 = QVBoxLayout() self.vlayoutA = QFormLayout() self.vlayoutB = QFormLayout() self.vlayoutC = QVBoxLayout() self.vlayoutD = QVBoxLayout() self.hlayout0 = QHBoxLayout() self.optionsgroupA = QGroupBox("processing options") self.vlayoutA.addRow(self.modmenulabel, self.modmenu) self.vlayoutA.addRow(self.batchmenulabel, self.batchmenu) self.optionsgroupA.setLayout(self.vlayoutA) self.optionsgroupB = QGroupBox("channels") self.vlayoutB.addRow(self.sigchanmenulabel, self.sigchanmenu) self.vlayoutB.addRow(self.markerchanmenulabel, self.markerchanmenu) self.optionsgroupB.setLayout(self.vlayoutB) self.optionsgroupC = QGroupBox("peaks") self.vlayoutC.addWidget(self.editcheckbox) self.vlayoutC.addWidget(self.savecheckbox) self.vlayoutC.addWidget(self.correctcheckbox) self.optionsgroupC.setLayout(self.vlayoutC) self.optionsgroupD = QGroupBox("select statistics for saving") self.vlayoutD.addWidget(self.periodcheckbox) self.vlayoutD.addWidget(self.ratecheckbox) self.vlayoutD.addWidget(self.tidalampcheckbox) self.optionsgroupD.setLayout(self.vlayoutD) self.vlayout1.addWidget(self.optionsgroupA) self.vlayout1.addWidget(self.optionsgroupB) self.vlayout1.addWidget(self.optionsgroupC) self.vlayout1.addWidget(self.optionsgroupD) self.optionsgroupwidget = QWidget() self.optionsgroupwidget.setLayout(self.vlayout1) self.optionsgroup = QDockWidget("configurations", self) self.optionsgroup.setAllowedAreas(Qt.LeftDockWidgetArea) self.toggleoptionsgroup = self.optionsgroup.toggleViewAction() self.toggleoptionsgroup.setText("show/hide configurations") menubar.addAction(self.toggleoptionsgroup) self.optionsgroup.setWidget(self.optionsgroupwidget) self.addDockWidget(Qt.LeftDockWidgetArea, self.optionsgroup) self.vlayout0.addWidget(self.splitter) self.hlayout0.addWidget(self.navitools) self.vlayout0.addLayout(self.hlayout0) ############################################## # connect output widgets to external signals # ############################################## self._model.signal_changed.connect(self.plot_signal) self._model.marker_changed.connect(self.plot_marker) self._model.peaks_changed.connect(self.plot_peaks) self._model.period_changed.connect(self.plot_period) self._model.rate_changed.connect(self.plot_rate) self._model.tidalamp_changed.connect(self.plot_tidalamp) self._model.path_changed.connect(self.display_path) self._model.segment_changed.connect(self.plot_segment) self._model.status_changed.connect(self.display_status) self._model.progress_changed.connect(self.display_progress) self._model.model_reset.connect(self.reset_plot) ########### # methods # ########### def plot_signal(self, value): self.ax00.clear() self.ax00.relim() # reset navitools history self.navitools.update() self.line00 = self.ax00.plot(self._model.sec, value, zorder=1) self.ax00.set_xlabel("seconds", fontsize="large", fontweight="heavy") self.canvas0.draw() # print("plot_signal listening") # print(self.ax0.collections, self.ax0.patches, self.ax0.artists) def plot_peaks(self, value): # self.scat is listed in ax.collections if self.ax00.collections: self.ax00.collections[0].remove() self.scat = self.ax00.scatter(self._model.sec[value], self._model.signal[value], c="m", zorder=2) self.canvas0.draw() # print("plot_peaks listening") # print(self.ax0.collections, self.ax0.patches, self.ax0.artists) def plot_segment(self, value): # If an invalid signal has been selected reset the segmenter interface. if value is None: self.toggle_segmenter(1) return if self.ax00.patches: # self.segementspan is listed in ax.patches self.ax00.patches[0].remove() self.segmentspan = self.ax00.axvspan(value[0], value[1], color="m", alpha=0.25) self.canvas0.draw() self.confirmedit.setEnabled(True) # print(self.ax0.collections, self.ax0.patches, self.ax0.artists) def plot_marker(self, value): self.ax10.clear() self.ax10.relim() self.line10 = self.ax10.plot(value[0], value[1]) self.canvas1.draw() # print("plot_marker listening") def plot_period(self, value): self.ax20.clear() self.ax20.relim() self.navitools.home() if self._model.savestats["period"]: self.line20 = self.ax20.plot(self._model.sec, value, c="m") else: self.line20 = self.ax20.plot(self._model.sec, value) self.ax20.set_ylim(bottom=min(value), top=max(value)) self.ax20.set_title("period", pad=0, fontweight="heavy") self.ax20.grid(True, axis="y") self.navitools.update() self.canvas2.draw() # print("plot_period listening") def plot_rate(self, value): self.ax21.clear() self.ax21.relim() self.navitools.home() if self._model.savestats["rate"]: self.line21 = self.ax21.plot(self._model.sec, value, c="m") else: self.line21 = self.ax21.plot(self._model.sec, value) self.ax21.set_ylim(bottom=min(value), top=max(value)) self.ax21.set_title("rate", pad=0, fontweight="heavy") self.ax21.grid(True, axis="y") self.navitools.update() self.canvas2.draw() # print("plot_rate listening") def plot_tidalamp(self, value): self.ax22.clear() self.ax22.relim() self.navitools.home() if self._model.savestats["tidalamp"]: self.line22 = self.ax22.plot(self._model.sec, value, c="m") else: self.line22 = self.ax22.plot(self._model.sec, value) self.ax22.set_ylim(bottom=min(value), top=max(value)) self.ax22.set_title("amplitude", pad=0, fontweight="heavy") self.ax22.grid(True, axis="y") self.navitools.update() self.canvas2.draw() # print("plot_tidalamp listening") def display_path(self, value): self.currentFile.setText(value) def display_status(self, status): # display status until new status is set self.statusBar.showMessage(status) def display_progress(self, value): # if value is 0, the progressbar indicates a busy state self.progressBar.setRange(0, value) def toggle_segmenter(self, value): if not self._model.loaded: return # Open segmenter when called from signalmenu or clear segmenter # upon selection of invalid segment. if value == 1: self.segmenter.setVisible(True) self.confirmedit.setEnabled(False) self.startedit.clear() self.endedit.clear() if self.ax00.patches: self.ax00.patches[0].remove() self.canvas0.draw() # Close segmenter after segment has been confirmed. elif value == 0: self.segmenter.setVisible(False) if self.ax00.patches: self.ax00.patches[0].remove() self.canvas0.draw() # Close segmenter after segmentation has been aborted (reset # segment). elif value == 2: self._model.set_segment([0, 0]) # This will reset the model to None self.segmenter.setVisible(False) if self.ax00.patches: self.ax00.patches[0].remove() self.canvas0.draw() def enable_segmentedit(self): # disable peak editing to avoid interference self.editcheckbox.setChecked(False) if self.startedit.hasFocus(): self.segmentcursor = "start" elif self.endedit.hasFocus(): self.segmentcursor = "end" def set_customheader(self): """Populate the customheader with inputs from the customfiledialog""" # Check if one of the mandatory fields is missing. mandatoryfields = self.signaledit.text() and self.headerrowsedit.text( ) and self.sfreqedit.text() if not mandatoryfields: self._model.status = ( "Please provide values for 'biosignal column'" ", 'number of header rows' and 'sampling" " rate'.") return seps = {"comma": ",", "tab": "\t", "colon": ":", "space": " "} self._model.customheader = dict.fromkeys( self._model.customheader, None ) # reset header here since it cannot be reset in controller.get_fpaths() self._model.customheader["signalidx"] = int(self.signaledit.text()) self._model.customheader["skiprows"] = int(self.headerrowsedit.text()) self._model.customheader["sfreq"] = int(self.sfreqedit.text()) self._model.customheader["separator"] = seps[ self.separatormenu.currentText()] if self.markeredit.text(): # not mandatory self._model.customheader["markeridx"] = int(self.markeredit.text()) self.customfiledialog.done(QDialog.Accepted) # close the dialog window self._controller.get_fpaths() # move on to file selection def get_xcursor(self, event): # event.button 1 corresponds to left mouse button if event.button != 1: return # limit number of decimal places to two if self.segmentcursor == "start": self.startedit.selectAll() self.startedit.insert("{:.2f}".format(event.xdata)) elif self.segmentcursor == "end": self.endedit.selectAll() self.endedit.insert("{:.2f}".format(event.xdata)) # disable segment cursor again after value has been set self.segmentcursor = False def select_stats(self, event): """ select or deselect statistics to be saved; toggle boolean with xor operator ^=, toggle color with dictionary """ self._model.savestats[event] ^= True line = None if event == "period": if self.line20: line = self.line20[0] elif event == "rate": if self.line21: line = self.line21[0] elif event == "tidalamp": if self.line22: line = self.line22[0] if line: line.set_color(self.togglecolors[line.get_color()]) self.canvas2.draw() def toggle_options(self, event): if event in ["ECG", "PPG"]: self.tidalampcheckbox.setEnabled(False) self.tidalampcheckbox.setChecked(False) self.ax22.set_visible(False) self.canvas2.draw() elif event == "RESP": self.tidalampcheckbox.setEnabled(True) self.ax22.set_visible(True) self.canvas2.draw() elif event == "multiple files": self.editcheckbox.setEnabled(False) self.editcheckbox.setChecked(False) self.savecheckbox.setEnabled(True) self.correctcheckbox.setEnabled(True) self.markerchanmenu.setEnabled(False) elif event == "single file": self.editcheckbox.setEnabled(True) self.markerchanmenu.setEnabled(True) self.savecheckbox.setEnabled(False) self.savecheckbox.setChecked(False) self.correctcheckbox.setEnabled(False) self.correctcheckbox.setChecked(False) def reset_plot(self): self.ax00.clear() self.ax00.relim() self.line00 = None self.scat = None self.segmentspan = None self.ax10.clear() self.ax10.relim() self.line10 = None self.ax20.clear() self.ax20.relim() self.line20 = None self.ax21.clear() self.ax21.relim() self.line21 = None self.ax22.clear() self.ax22.relim() self.line22 = None self.canvas0.draw() self.canvas1.draw() self.canvas2.draw() self.navitools.update() self.currentFile.clear()
class Form(QDialog): def __init__(self, parent=None): super(Form, self).__init__(parent) self.setWindowTitle( "Automation.... WITH KITTENS!!!! AHHH I LOVE KITTENS") # Create Widgets self.fileLoader = QPushButton("Load Files") self.stopButton = QPushButton("Stop") self.fileBox = QComboBox() # Holds name of files self.filePreview = QTextBrowser() # Preview Files self.loadingBar = QProgressBar(self) self.preview = QTextBrowser() self.ConfirmButton = QPushButton("Start") # Variable Creation Area self.pictureDic = {} # Holds the tings self.timerBool = bool # Create layout and add widgets layout = QVBoxLayout() # Design Area self.label2 = QLabel() self.label2.setText("Automating Kittens!") self.label2.setStyleSheet( "QLabel { background-color : black; color : white; font-size: 20px; text-align : " "center; }") self.label = QLabel() pixmap = QPixmap('pictures/helloKitten.png') w = 440 h = 200 pixmap = pixmap.scaled(w, h, aspectMode=Qt.IgnoreAspectRatio, mode=Qt.FastTransformation) self.timer = QBasicTimer() self.step = 0 self.label.setPixmap(pixmap) # Adding widgets to the layout layout.addWidget(self.label2) layout.addWidget(self.label) layout.addWidget(self.fileLoader) layout.addWidget(self.fileBox) layout.addWidget(self.filePreview) layout.addWidget(self.ConfirmButton) layout.addWidget(self.stopButton) self.loadingBar.setStyleSheet( "QProgressBar::chunk {background-color: red}") layout.addWidget(self.loadingBar) # Enable Minimize and maximize self.setWindowFlag(Qt.WindowMinimizeButtonHint, True) self.setWindowFlag(Qt.WindowMaximizeButtonHint, True) # Set layout self.setLayout(layout) p = self.palette() p.setColor(self.foregroundRole(), QColor(10, 10, 10, 127)) p.setColor(self.backgroundRole(), QColor(0, 0, 127, 127)) self.setPalette(p) self.setFixedWidth(450) self.setFixedHeight(700) # Connecting functions to buttons self.fileLoader.clicked.connect(self.loadFiles) self.ConfirmButton.clicked.connect(self.newStart) self.stopButton.clicked.connect(self.stop) self.fileBox.activated.connect(self.updatePreview) def start(self): print("Ready to Go") self.loadingBar.setStyleSheet( "QProgressBar::chunk {background-color: gold } QProgressBar {text-align: center}" ) if bool(self.pictureDic) is False: self.filePreview.append("**** Empty Files **** \n") self.filePreview.append("**** Please load Files **** \n") self.timer.stop() else: self.filePreview.append("**** Starting Picture Search **** \n") self.searchThread = threadedSearch.thread_with_exception( "Searching", self.pictureDic) self.searchThread.start() pixmap = QPixmap('pictures/pawwingKitten.jpg') w = 440 h = 200 pixmap = pixmap.scaled(w, h, aspectMode=Qt.IgnoreAspectRatio, mode=Qt.FastTransformation) self.label.setPixmap(pixmap) def loadFiles(self): print("We gone load them files doh") self.filePreview.append("**** Files Loaded **** \n") self.fileBox.clear() self.pictureDic.clear() with os.scandir('testpictures/') as files: for file in files: print(file.path) print(file.name) self.pictureDic.update({file.name: file.path}) self.fileBox.addItem(file.name) self.filePreview.append("{} \n".format(file.name)) self.filePreview.append("**** Files done loading **** \n") def newStart(self): pixmap = QPixmap('pictures/roaringKitten.jpg') w = 440 h = 200 pixmap = pixmap.scaled(w, h, aspectMode=Qt.IgnoreAspectRatio, mode=Qt.FastTransformation) self.label.setPixmap(pixmap) self.timerBool = True global stepper stepper = 0 self.loadingBar.setValue(0) self.loadingBar.setStyleSheet( "QProgressBar::chunk {background-color: red;} QProgressBar {text-align: center}" ) self.startTracking() def updatePreview(self): self.label.clear() location = self.pictureDic[self.fileBox.currentText()] pixmap = QPixmap('{}'.format(location)) w = 440 h = 200 pixmap = pixmap.scaled(w, h, aspectMode=Qt.IgnoreAspectRatio, mode=Qt.FastTransformation) self.timer = QBasicTimer() self.step = 0 self.label.setPixmap(pixmap) def startTracking(self): self.timer.start(80, self) def stop(self): self.timer.stop() try: self.searchThread.raise_exception() self.searchThread.join() self.filePreview.append("**** Sucessfully Killed thread **** \n") except: self.filePreview.append("**** No thread to kill! **** \n") pixmap = QPixmap('pictures/sadkitten.jpg') w = 440 h = 200 pixmap = pixmap.scaled(w, h, aspectMode=Qt.IgnoreAspectRatio, mode=Qt.FastTransformation) self.label.setPixmap(pixmap) self.loadingBar.setValue(0) def timerEvent(self, event): if self.timerBool is True: global stepper stepper += 1 self.loadingBar.setValue(stepper) if self.loadingBar.value() == 100: self.start() self.timerBool = False self.loadingBar.setStyleSheet( "QProgressBar::chunk {background-color: green;} QProgressBar {text-align: center}" ) else: try: printingMess = self.searchThread.getMessage() if printingMess != "": self.filePreview.append("{} \n".format(printingMess)) except: self.filePreview.append( " *** No information from Thread *** \n")
class ConfigDialog(QDialog): config_changed = Signal() def __init__(self, requester, config, parent=None): super().__init__(parent) self.requester = requester self.requester.pin_needed.connect(self.input_pin) self.requester.umi_made.connect(self.write_umi) self.requester.msg_passed.connect(self.error_msg) self.config = config self.lbl_id = QLabel('계정명') self.lbl_id.setAlignment(Qt.AlignCenter) self.lbl_pw = QLabel('비밀번호') self.lbl_pw.setAlignment(Qt.AlignCenter) self.lbl_umi = QLabel('umi 쿠키') self.lbl_umi.setAlignment(Qt.AlignCenter) self.lbl_ua = QLabel('유저 에이전트') self.lbl_ua.setAlignment(Qt.AlignCenter) self.lbl_delay = QLabel('저속 간격') self.lbl_delay.setAlignment(Qt.AlignCenter) self.lbl_msg = QLabel('') self.line_id = QLineEdit() self.line_pw = QLineEdit() self.line_pw.setEchoMode(QLineEdit.PasswordEchoOnEdit) self.line_umi = QLineEdit() self.line_umi.setPlaceholderText('로그인 시 자동 입력') self.line_ua = QLineEdit() self.line_delay = QDoubleSpinBox() self.line_delay.setMinimum(3) self.line_delay.setDecimals(1) self.line_delay.setSuffix('초') self.line_delay.setSingleStep(0.1) self.btn_save = NPButton('저장', 10, self) self.btn_save.clicked.connect(self.save) self.btn_cancel = NPButton('취소', 10, self) self.btn_cancel.clicked.connect(self.reject) self.btn_get_umi = NPButton('로그인', 10, self) self.btn_get_umi.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.MinimumExpanding) self.btn_get_umi.clicked.connect(self.get_umi) grid = QGridLayout() grid.addWidget(self.lbl_id, 0, 0, 1, 1) grid.addWidget(self.line_id, 0, 1, 1, 6) grid.addWidget(self.lbl_pw, 1, 0, 1, 1) grid.addWidget(self.line_pw, 1, 1, 1, 6) grid.addWidget(self.btn_get_umi, 0, 7, 2, 2) grid.addWidget(self.lbl_umi, 2, 0, 1, 1) grid.addWidget(self.line_umi, 2, 1, 1, 8) grid.addWidget(self.lbl_ua, 3, 0, 1, 1) grid.addWidget(self.line_ua, 3, 1, 1, 8) grid.addWidget(self.lbl_delay, 4, 0, 1, 1) grid.addWidget(self.line_delay, 4, 1, 1, 8) grid.addWidget(self.lbl_msg, 5, 0, 1, 4) grid.addWidget(self.btn_save, 5, 5, 1, 2) grid.addWidget(self.btn_cancel, 5, 7, 1, 2) self.setLayout(grid) self.input_dialog = InputDialog(self) self.input_dialog.input.setInputMask('999999') self.setWindowTitle('개인정보') self.setWindowIcon(QIcon('icon.png')) self.setStyleSheet('font: 10pt \'맑은 고딕\'') self.setWindowFlag(Qt.WindowContextHelpButtonHint, False) self.c_login, self.c_work = {}, {} @Slot(str) def error_msg(self, t): self.lbl_msg.setText(t) def load(self): self.line_id.setText(self.config.c['login']['ID']) self.line_pw.setText(self.config.c['login']['PW']) self.line_umi.setText(self.config.c['login']['UMI']) self.line_ua.setText(self.config.c['login']['UA']) self.line_ua.setCursorPosition(0) self.line_delay.setValue(float(self.config.c['work']['DELAY'])) self.lbl_msg.clear() ok = self.exec_() if ok == QDialog.Accepted: self.config_changed.emit() def save(self): self.config.save(login={ 'ID': self.line_id.text().strip(), 'PW': self.line_pw.text().strip(), 'UMI': self.line_umi.text().strip(), 'UA': self.line_ua.text().strip() }, delay=self.line_delay.value()) self.accept() def get_umi(self): self.lbl_msg.setText('로그인 시도...') self.line_umi.clear() self.input_dialog.input.clear() self.requester.init_login(self.line_id.text().strip(), self.line_pw.text().strip()) @Slot(str) def write_umi(self, umi): self.line_umi.setText(umi) @Slot(str) def input_pin(self, mail): pin, ok = self.input_dialog.get_text('로그인 PIN 입력', f'이메일({mail})로 전송된 PIN을 입력해주세요.') if ok: if pin: self.requester.typed_pin = pin else: self.requester.typed_pin = 'nothing' else: self.requester.typed_pin = 'deny'
class View(QWidget): def __init__(self, db): super().__init__() self.db = db self.pragmas = Pragmas.unchanged() self.dirty = False self.make_widgets() self.make_layout() self.make_connections() def make_widgets(self): self.userVersionSpinbox = QSpinBox() self.userVersionSpinbox.setRange(0, MAX_I32) # TODO make all widgets self.pathLabel = QLabel() self.configLabel = QLabel(Config.filename()) def make_layout(self): form = QFormLayout() form.addRow('User Version', self.userVersionSpinbox) # TODO add all widgets self._add_grouped(form, 'Database Path', self.pathLabel) self._add_grouped(form, 'Configuration File', self.configLabel) self.setLayout(form) def _add_grouped(self, form, title, widget): box = QGroupBox(title) vbox = QVBoxLayout() vbox.addWidget(widget) box.setLayout(vbox) form.addRow(box) def make_connections(self): self.userVersionSpinbox.valueChanged.connect(self.on_user_version) # TODO connect all widgets (excl. configLabel) def on_user_version(self): self.pragmas.user_version = self.userVersionSpinbox.value() self.dirty = True def refresh(self): self.clear() if bool(self.db): pragmas = self.db.pragmas() with BlockSignals(self): self.userVersionSpinbox.setValue(pragmas.user_version) # TODO refresh all widgets (excl. configLabel) path = str(pathlib.Path(self.db.filename).parent) if not path.endswith(('/', '\\')): path += os.sep self.pathLabel.setText(path) self.dirty = False def clear(self): with BlockSignals(self): self.userVersionSpinbox.setValue(0) # TODO clear all widgets (excl. configLabel) self.pathLabel.clear() self.dirty = False def save(self, *, closing=False): saved = False errors = False if self.dirty and bool(self.db): if errors := self.db.pragmas_save(self.pragmas): if not closing: error = '\n'.join(errors) QMessageBox.warning(self, f'Pragma error — {APPNAME}', f'Failed to save pragmas:\n{error}') else: saved = True self.dirty = False return saved
class AbrechnungenView(QWidget, ModifyInfo): def __init__(self, parent=None): QWidget.__init__(self, parent) ModifyInfo.__init__(self) #self.setWindowTitle( "Sonstige Ausgaben: Rechnungen, Abgaben, Gebühren etc." ) self._mainLayout = QtWidgets.QGridLayout(self) self._toolbarLayout = QHBoxLayout() self._btnSave = QPushButton(self) self._tvAbrechnungen = TableViewExt(self) self._buchungsdatumLayout = QHBoxLayout() self._sdAbrechnungsdatum = SmartDateEdit(self) self._btnAddDayToAbrechnungsdatum = QPushButton(self) self._btnClearAbrechnungsdatum = QPushButton(self) self._sdBuchungsdatum = SmartDateEdit(self) self._btnAddDay = QPushButton(self) self._btnClearBuchungsdatum = QPushButton(self) self._abrechnungInfoLayout = QHBoxLayout() self._name = QLabel(self, text="") self._cboAbrechnungsjahr = QtWidgets.QComboBox(self) self._feBetrag: FloatEdit = FloatEdit(self) self._teBemerkung = QTextEdit(self) self._btnOk = QPushButton(self) self._btnClear = QPushButton(self) # actions für ContextMenu #self._contextMenuActions:List[QAction] = None # Callbacks self._abrechnungsjahrChangedCallback = None self._saveActionCallback = None self._submitChangesCallback = None self._justEditing: XAbrechnung = None self._suspendCallbacks = False self._createGui() self.connectWidgetsToChangeSlot(self.onChange, self.onResetChangeFlag) def _createGui(self): self._assembleToolbar() self._mainLayout.addLayout(self._toolbarLayout, 0, 0, alignment=Qt.AlignLeft) self._toolbarLayout.addStretch(50) ### tableView tv = self._tvAbrechnungen tv.setSelectionBehavior(QAbstractItemView.SelectRows) tv.setAlternatingRowColors(True) tv.verticalHeader().setVisible(False) tv.horizontalHeader().setMinimumSectionSize(0) self._mainLayout.addWidget(tv, 1, 0, 1, 1) #Buchungsdatum self._assembleBuchungsdatum() self._mainLayout.addLayout(self._buchungsdatumLayout, 2, 0, alignment=Qt.AlignLeft) # Name, Abrechnungsjahr, Betrag, Bemerkung, Buttons self._assembleAbrechnungInfo() self._mainLayout.addLayout(self._abrechnungInfoLayout, 3, 0, alignment=Qt.AlignLeft) # self._assembleRechnungsdaten() # self._mainLayout.addLayout( self._editRechnungLineLayout, 4, 0, alignment=Qt.AlignLeft ) def _assembleToolbar(self): #### Combobox Buchungsjahr font = QFont("Arial", 14, weight=QFont.Bold) self._cboAbrechnungsjahr.setFont(font) self._cboAbrechnungsjahr.setToolTip( "Das hier eingestellte Jahr bestimmt die NK-Abrechnungen, die in der Tabelle angezeigt werden." ) self._cboAbrechnungsjahr.currentIndexChanged.connect( self.onAbrechnungsjahrChanged) self._toolbarLayout.addWidget(self._cboAbrechnungsjahr, stretch=0, alignment=Qt.AlignLeft) #### save button btn = self._btnSave btn.clicked.connect(self.onSave) btn.setFlat(True) btn.setEnabled(False) btn.setToolTip("Änderungen dieser View speichern") icon = QIcon("./images/save_30.png") btn.setIcon(icon) size = QSize(30, 30) btn.setFixedSize(size) iconsize = QSize(30, 30) btn.setIconSize(iconsize) self._toolbarLayout.addWidget(btn, stretch=0) def _assembleBuchungsdatum(self): lbl = QLabel(self, text="Abrechng.datum: ") lbl.setFixedWidth(130) self._buchungsdatumLayout.addWidget(lbl) self._sdAbrechnungsdatum.setFixedWidth(85) self._sdAbrechnungsdatum.setToolTip("Datum der Forderung. Mussfeld.") self._buchungsdatumLayout.addWidget(self._sdAbrechnungsdatum) size = QSize(25, 25) self._btnAddDayToAbrechnungsdatum.setIcon(QIcon("./images/plus.png")) self._btnAddDayToAbrechnungsdatum.setFixedSize(size) self._btnAddDayToAbrechnungsdatum.setToolTip( "Abrechnungsdatum um 1 Tag erhöhen") self._btnAddDayToAbrechnungsdatum.clicked.connect( self.onAddDayToAbrechnungsdatum) self._buchungsdatumLayout.addWidget(self._btnAddDayToAbrechnungsdatum) self._btnClearAbrechnungsdatum.setIcon(QIcon("./images/cancel.png")) self._btnClearAbrechnungsdatum.setFixedSize(size) self._btnClearAbrechnungsdatum.setToolTip("Forderungsdatum löschen") self._btnClearAbrechnungsdatum.clicked.connect( self.onClearForderungsdatum) self._buchungsdatumLayout.addWidget(self._btnClearAbrechnungsdatum) lbl = QLabel(self, text="Buchungsdatum: ") lbl.setFixedWidth(120) self._buchungsdatumLayout.addWidget(lbl) self._sdBuchungsdatum.setFixedWidth(85) self._sdBuchungsdatum.setToolTip( "Buchungsdatum. Kann leer bleiben, wenn Buchung noch nicht erfolgt ist" ) self._buchungsdatumLayout.addWidget(self._sdBuchungsdatum) size = QSize(25, 25) self._btnAddDay.setIcon(QIcon("./images/plus.png")) self._btnAddDay.setFixedSize(size) self._btnAddDay.setToolTip("Buchungsdatum um 1 Tag erhöhen") self._btnAddDay.clicked.connect(self.onAddDayToBuchungsdatum) self._buchungsdatumLayout.addWidget(self._btnAddDay) self._btnClearBuchungsdatum.setIcon(QIcon("./images/cancel.png")) self._btnClearBuchungsdatum.setFixedSize(size) self._btnClearBuchungsdatum.setToolTip("Buchungsdatum löschen") self._btnClearBuchungsdatum.clicked.connect(self.onClearBuchungsdatum) self._buchungsdatumLayout.addWidget(self._btnClearBuchungsdatum) def _assembleAbrechnungInfo(self): lbl = QLabel(self, text="Mieter/Verwalter: ") lbl.setFixedWidth(130) self._abrechnungInfoLayout.addWidget(lbl) self._name.setFrameShape(QFrame.Panel) self._name.setFrameShadow(QFrame.Sunken) self._name.setFixedWidth(150) self._name.setFixedHeight(25) self._abrechnungInfoLayout.addWidget(self._name) # Gutschrift/Nachzahlung lbl = QLabel(self, text="Betrag: ") self._abrechnungInfoLayout.addWidget(lbl) self._feBetrag.setAlignment(Qt.AlignRight) self._feBetrag.setToolTip("'+' für Einnahme, '-' für Ausgabe") self._feBetrag.setFixedWidth(60) self._abrechnungInfoLayout.addWidget(self._feBetrag) self._teBemerkung.setPlaceholderText("Bemerkung zur Zahlung") self._teBemerkung.setMaximumSize(QtCore.QSize(16777215, 50)) self._abrechnungInfoLayout.addWidget(self._teBemerkung, stretch=1) # Buttons vbox = QVBoxLayout() self._btnOk.setIcon(QIcon("./images/checked.png")) self._btnOk.setDefault(True) self._btnOk.setToolTip("Daten in Tabelle übernehmen (kein Speichern)") self._btnOk.clicked.connect(self.onOkEditFields) vbox.addWidget(self._btnOk) self._btnClear.setIcon(QIcon("./images/cancel.png")) self._btnClear.setToolTip("Änderungen verwerfen und Felder leeren") self._btnClear.clicked.connect(self.onClearEditFields) vbox.addWidget(self._btnClear) self._abrechnungInfoLayout.addLayout(vbox) def onAbrechnungsjahrChanged(self, newindex): """ Slot für die Änderung des Buchungsjahrs. :param newindex: :return: """ if self._abrechnungsjahrChangedCallback and not self._suspendCallbacks: jahr = int(self._cboAbrechnungsjahr.currentText()) self._abrechnungsjahrChangedCallback(jahr) def onAddDayToBuchungsdatum(self): val = self._sdBuchungsdatum.getDate() if val: dt = getQDateFromIsoString(val) dt = dt.addDays(1) self._sdBuchungsdatum.setDate(dt.year(), dt.month(), dt.day()) def onClearBuchungsdatum(self): self._sdBuchungsdatum.clear() def onAddDayToAbrechnungsdatum(self): val = self._sdAbrechnungsdatum.getDate() if val: dt = getQDateFromIsoString(val) dt = dt.addDays(1) self._sdAbrechnungsdatum.setDate(dt.year(), dt.month(), dt.day()) def onClearForderungsdatum(self): self._sdAbrechnungsdatum.clear() def setSaveButtonEnabled(self, enable: bool = True): self._btnSave.setEnabled(enable) def onSave(self): if self._saveActionCallback: self._saveActionCallback() def onOkEditFields(self, arg): """ OK gedrückt. Änderungen an Callback-Funktion melden. :param arg: :return: """ if self._submitChangesCallback: x: XAbrechnung = self._getEditedXAbrechnung() ok: bool = self._submitChangesCallback(x) if ok: self._tvAbrechnungen.clearSelection() def _getEditedXAbrechnung(self) -> XAbrechnung: if self._justEditing is None: self.showException("Interner Fehler", "AbrechnungenView._getXAbrechnung()", "XAbrechnung ist leer") return x: XAbrechnung = self._justEditing x.ab_datum = self._sdAbrechnungsdatum.getDate() x.buchungsdatum = self._sdBuchungsdatum.getDate() x.betrag = self._feBetrag.getFloatValue() x.bemerkung = self._teBemerkung.toPlainText() return x def onClearEditFields(self, arg): self.clearEditFields() self._tvAbrechnungen.clearSelection() def getAbrechnungenTableView(self) -> QTableView: return self._tvAbrechnungen def setAbrechnungenTableModel(self, tm: SonstAusTableModel): self._tvAbrechnungen.setModel(tm) self._tvAbrechnungen.resizeColumnsToContents() #self._tvAuszahlungen.setColumnWidth( 0, 10 ) #self._tvAuszahlungen.setColumnWidth( 1, 10 ) def setAbrechnungsjahre(self, jahre: List[int]): """ setzt die Liste der auswählbaren Jahre für die Buchungsjahr-Combobox :param jahre: :return: """ self._suspendCallbacks = True for jahr in jahre: self._cboAbrechnungsjahr.addItem(str(jahr)) self._suspendCallbacks = False def clearEditFields(self): self._suspendCallbacks = True self._sdAbrechnungsdatum.clear() self._sdBuchungsdatum.clear() self._name.clear() self._feBetrag.clear() self._teBemerkung.clear() self._justEditing = None self._suspendCallbacks = False def provideEditFields(self, x: XAbrechnung): self.clearEditFields() #self._suspendCallbacks = True self._justEditing = x if x.ab_datum: y, m, d = getDateParts(x.ab_datum) self._sdAbrechnungsdatum.setDate(y, m, d) if x.buchungsdatum: y, m, d = getDateParts(x.buchungsdatum) self._sdBuchungsdatum.setDate(y, m, d) self._name.setText(x.getName()) if x.betrag == 0.0: self._feBetrag.setText("0") else: self._feBetrag.setText(str(x.betrag)) self._teBemerkung.setText(x.bemerkung) self._suspendCallbacks = False def getModel(self) -> IccTableModel: return self._tvAbrechnungen.model() def showException(self, title: str, exception: str, moretext: str = None): # todo: show Qt-Errordialog msgbox = QtWidgets.QMessageBox() msgbox.setWindowTitle(title) msgbox.setIcon(QMessageBox.Critical) msgbox.setText(exception) if moretext: msgbox.setInformativeText(moretext) msgbox.exec_() ################# SET CALLBACKS ############################ def setAbrechnungsjahrChangedCallback(self, cbfnc) -> None: """ Die callback-Funktion muss als Argument das neu eingestellte Jahr als integer akzeptieren :param cbfnc: :return: """ self._abrechnungsjahrChangedCallback = cbfnc def setSubmitChangesCallback(self, cbfnc): """ sets the one and only callback when the user hits the OK button in the edit fields area. The given callback function has to accept the edited XAbrechnung object, :param cbfnc: :return: """ self._submitChangesCallback = cbfnc def setSaveActionCallback(self, cbfnc) -> None: """ Die callback-FUnktion braucht keine Parameter empfangen. :param cbfnc: :return: """ self._saveActionCallback = cbfnc def onChange(self): self._btnSave.setEnabled(True) def onResetChangeFlag(self): self._btnSave.setEnabled(False)
class PlainTextFindReplaceDialog(QDialog): """ Modeless, stay-above-parent dialog that supports find and replace. Allows for searching case (in)sensitively, and whole-word. Find triggered by Enter / Shift+Enter, or corresponding button (Next / Previous), or if Replace clicked before Next / Previous. Find wraps. Highlights all matches, operating on the closest-to-user's-cursor selection first, in the user-selected direction (Next / Previous). Highlighting / found cursors retained on navigation back to text editor, and cleared on re-find/replace if user modified the document. Presents an info label (e.g. "x of y", "No matches found", ...) While no members have a leading underscore, the only explicit public interface is the static method `find_all`. I couldn't find a reason to need to interface with anything in here, so I didn't differentiate everything as "private". """ def __init__(self, plain_text_edit: QPlainTextEdit, parent=None): """ Sets up the dialog. :param plain_text_edit: Text Editor to operate on. :param parent: QWidget parent """ super().__init__(parent=parent, f=Qt.Tool) self.plain_text_edit = plain_text_edit self.cursors_needed = True self.find_flags = QTextDocument.FindFlags() self.found_cursors: List[QTextCursor] = [] self.current_cursor = QTextCursor() # UI layout = QVBoxLayout() find_and_replace_layout = QGridLayout() layout.addLayout( find_and_replace_layout ) # if QGL is sub-layout, add to parent layout before doing stuff. self.find_line_edit = QLineEdit() find_label = QLabel("&Find") find_label.setBuddy(self.find_line_edit) options_layout = QHBoxLayout() self.match_case_check_box = QCheckBox("Match Case") self.whole_word_check_box = QCheckBox("Whole Word") options_layout.addWidget(self.match_case_check_box) options_layout.addWidget(self.whole_word_check_box) options_layout.addStretch() self.found_info_label = QLabel() self.replace_line_edit = QLineEdit() replace_label = QLabel("&Replace") replace_label.setBuddy(self.replace_line_edit) find_and_replace_layout.addWidget(find_label, 0, 0) find_and_replace_layout.addWidget(self.find_line_edit, 0, 1) find_and_replace_layout.addLayout(options_layout, 1, 1) find_and_replace_layout.setRowMinimumHeight(2, 20) find_and_replace_layout.addWidget(self.found_info_label, 2, 1) find_and_replace_layout.addWidget(replace_label, 3, 0) find_and_replace_layout.addWidget(self.replace_line_edit, 3, 1) self.btn_box = QDialogButtonBox(QDialogButtonBox.Close) self.find_next_btn = QPushButton("Next") self.find_next_btn.setEnabled(False) self.find_prev_btn = QPushButton("Previous") self.find_prev_btn.setEnabled(False) self.replace_btn = QPushButton("Replace") self.replace_btn.setEnabled(False) self.replace_all_btn = QPushButton("Replace All") self.replace_all_btn.setEnabled(False) self.btn_box.addButton(self.replace_btn, QDialogButtonBox.ActionRole) self.btn_box.addButton(self.replace_all_btn, QDialogButtonBox.ActionRole) self.btn_box.addButton(self.find_prev_btn, QDialogButtonBox.ActionRole) self.btn_box.addButton(self.find_next_btn, QDialogButtonBox.ActionRole) layout.addWidget(self.btn_box) self.setLayout(layout) # End UI self.btn_box.rejected.connect(self.reject) self.find_line_edit.textEdited.connect(self.handle_text_edited) self.find_next_btn.clicked.connect(self.next) self.find_prev_btn.clicked.connect(self.prev) self.replace_btn.clicked.connect(self.replace) self.replace_all_btn.clicked.connect(self.replace_all) self.plain_text_edit.document().contentsChanged.connect( self.set_cursors_needed_true) self.whole_word_check_box.stateChanged.connect( self.toggle_whole_word_flag) self.match_case_check_box.stateChanged.connect( self.toggle_match_case_flag) # SLOTS def next(self): """ Finds all matches to the user's search. First highlight after cursor. Consecutive calls advance through matches. If there are matches or not, it says so. The user's cursor advances along with the current selection. Loops back to start after the last match. :return: Side effect: Highlights all matches, differentiating (maybe moving fwd) the current selection. """ if self.cursors_needed: self.init_find() if not self.found_cursors: self.found_info_label.setText("No matches found") self.found_info_label.repaint() return if self.current_cursor >= self.found_cursors[ -1]: # loop back to start. cursor equality based on position. self.current_cursor = self.found_cursors[0] else: for cur in self.found_cursors: if cur > self.current_cursor: # next in order self.current_cursor = cur break self.update_visuals() def prev(self): """ Finds all matches to user's search. First highlight before cursor. Consecutive calls retreat through matches. If there are matches or not, it says so. The user's cursor moves along with the current selection. Loops back to end after the last (first in doc) match. :return: Side effect: Highlights all matches, differentiating (maybe moving back) the current selection. """ if self.cursors_needed: self.init_find() if not self.found_cursors: self.found_info_label.setText("No matches found") self.found_info_label.repaint() return if self.current_cursor <= self.found_cursors[0]: # loop back to end. self.current_cursor = self.found_cursors[-1] else: for cur in reversed(self.found_cursors): if cur < self.current_cursor: # prev in order self.current_cursor = cur break self.update_visuals() def replace(self): """ Replaces the word under focus by `next`. Replaces with the Replace line edit's text, and advances to next word. If no word under focus via this dialog, calls `next`. :return: Side effect: replaces word in text edit """ if self.cursors_needed: self.next() return if not self.found_cursors: return self.plain_text_edit.document().contentsChanged.disconnect( self.set_cursors_needed_true) # don't dup work. self.current_cursor.insertText(self.replace_line_edit.text()) self.plain_text_edit.document().contentsChanged.connect( self.set_cursors_needed_true) self.found_cursors.remove(self.current_cursor) self.next() def replace_all(self): """ Replaces all instances of Find's text with Replace's text. :return: Side effect: replaces words in text edit. Indicates success to user via info label on dialog. """ if self.cursors_needed: self.init_find() for cur in self.found_cursors: cur.insertText(self.replace_line_edit.text()) self.found_info_label.setText("Made {} replacements".format( len(self.found_cursors))) self.found_info_label.repaint() def handle_text_edited(self, text): """ Modifies button states, clears info text, and sets self.cursors_needed to True. :param text: The find_line_edit's text. :return: Side effect: btn enabled / default """ self.found_info_label.clear() self.cursors_needed = True find_enabled = text != "" self.find_next_btn.setEnabled(find_enabled) self.find_prev_btn.setEnabled(find_enabled) self.replace_btn.setEnabled(find_enabled) self.replace_all_btn.setEnabled(find_enabled) self.find_next_btn.setDefault(find_enabled) self.btn_box.button( QDialogButtonBox.Close).setDefault(not find_enabled) def set_cursors_needed_true(self): self.cursors_needed = True def toggle_match_case_flag(self, state: int): self.found_info_label.clear( ) # User will be performing a new search upon toggle, so want this reset. self.cursors_needed = True if state == Qt.Unchecked: self.find_flags &= ~QTextDocument.FindCaseSensitively elif state == Qt.Checked: self.find_flags |= QTextDocument.FindCaseSensitively def toggle_whole_word_flag(self, state: int): self.found_info_label.clear( ) # User will be performing a new search upon toggle, so want this reset. self.cursors_needed = True if state == Qt.Unchecked: self.find_flags &= ~QTextDocument.FindWholeWords elif state == Qt.Checked: self.find_flags |= QTextDocument.FindWholeWords # END SLOTS def init_find(self): """Sets up internal state for the case when cursors are needed (e.g. first find, user modifies doc...)""" self.found_cursors = self.find_all(self.find_line_edit.text(), self.plain_text_edit.document(), self.find_flags) self.cursors_needed = False self.current_cursor = self.plain_text_edit.textCursor( ) # returns copy of self.plain_text_edit.setExtraSelections([]) def update_visuals(self): """ Moves text editor's cursor to match currently highlighted `find` word, performs `find` highlighting, indicates index on dialog. """ # x of y words indicator idx = self.found_cursors.index(self.current_cursor) + 1 self.found_info_label.setText("{} of {} matches".format( idx, len(self.found_cursors))) self.found_info_label.repaint() # move along text editor's viewport next_pte_cursor = QTextCursor(self.current_cursor) next_pte_cursor.clearSelection() self.plain_text_edit.setTextCursor(next_pte_cursor) #highlighting normal_color = QColor(Qt.yellow).lighter() current_color = QColor(Qt.magenta).lighter() extra_selections: List[QTextEdit.ExtraSelection] = [] for cur in self.found_cursors: selection = QTextEdit.ExtraSelection() selection.cursor = cur if cur == self.current_cursor: selection.format.setBackground(current_color) else: selection.format.setBackground(normal_color) extra_selections.append(selection) self.plain_text_edit.setExtraSelections(extra_selections) @staticmethod def find_all( text: str, document: QTextDocument, flags=QTextDocument.FindFlags() ) -> List[QTextCursor]: """ Finds all occurrences of `text` in `document`, in order. :param text: Text to find. :param document: Document to search. :param flags: Conditions to set on the search: none or (whole word and/or match case) :return: Ordered list of all found instances. """ cursor = QTextCursor(document) # default pos == 0 found: List[QTextCursor] = [] while True: cursor = document.find(text, cursor, flags) if cursor.isNull(): return found else: found.append(cursor) def done(self, arg__1: int): self.plain_text_edit.setExtraSelections([]) super().done(arg__1) def keyPressEvent(self, arg__1: QKeyEvent): # Shift+Enter triggers find previous, if the corresponding btn is enabled. if (arg__1.key() in [Qt.Key_Return, Qt.Key_Enter] and arg__1.modifiers() == Qt.ShiftModifier and self.find_prev_btn.isEnabled()): self.prev() else: super().keyPressEvent(arg__1)
class View(QMainWindow): """View component of the MVC application. Presents the state of the application as well as the available means of interaction. Receives updates about the state from the Model and informs Controller about user interactions. """ def __init__(self, model, controller): """Define GUI elements and their layout. Parameters ---------- model : QObject Model component of the MVC application. controller : QObject Controller component of the MVC application. """ super().__init__() self._model = model self._controller = controller self.segmentcursor = False self.togglecolors = {"#1f77b4": "m", "m": "#1f77b4"} self.setWindowTitle("biopeaks") self.setGeometry(50, 50, 1750, 750) self.setWindowIcon(QIcon(":/python_icon.png")) # Figure for biosignal. self.figure0 = Figure() self.canvas0 = FigureCanvas(self.figure0) # Enforce minimum height, otherwise resizing with self.splitter causes # mpl to throw an error because figure is resized to height 0. The # widget can still be fully collapsed with self.splitter. self.canvas0.setMinimumHeight(1) # in pixels self.ax00 = self.figure0.add_subplot(1, 1, 1) self.ax00.set_frame_on(False) self.figure0.subplots_adjust(left=0.04, right=0.98, bottom=0.25) self.line00 = None self.scat = None self.segmentspan = None # Figure for marker. self.figure1 = Figure() self.canvas1 = FigureCanvas(self.figure1) self.canvas1.setMinimumHeight(1) self.ax10 = self.figure1.add_subplot(1, 1, 1, sharex=self.ax00) self.ax10.get_xaxis().set_visible(False) self.ax10.set_frame_on(False) self.figure1.subplots_adjust(left=0.04, right=0.98) self.line10 = None # Figure for statistics. self.figure2 = Figure() self.canvas2 = FigureCanvas(self.figure2) self.canvas2.setMinimumHeight(1) self.ax20 = self.figure2.add_subplot(3, 1, 1, sharex=self.ax00) self.ax20.get_xaxis().set_visible(False) self.ax20.set_frame_on(False) self.line20 = None self.ax21 = self.figure2.add_subplot(3, 1, 2, sharex=self.ax00) self.ax21.get_xaxis().set_visible(False) self.ax21.set_frame_on(False) self.line21 = None self.ax22 = self.figure2.add_subplot(3, 1, 3, sharex=self.ax00) self.ax22.get_xaxis().set_visible(False) self.ax22.set_frame_on(False) self.line22 = None self.figure2.subplots_adjust(left=0.04, right=0.98) self.navitools = CustomNavigationToolbar(self.canvas0, self) # Peak editing. self.editcheckbox = QCheckBox("editable", self) self.editcheckbox.stateChanged.connect(self._model.set_peakseditable) # Peak saving during batch processing. self.savecheckbox = QCheckBox("save during batch processing", self) self.savecheckbox.stateChanged.connect(self._model.set_savebatchpeaks) # Peak auto-correction during batch processing. self.correctcheckbox = QCheckBox("correct during batch processing", self) self.correctcheckbox.stateChanged.connect( self._model.set_correctbatchpeaks) # Selection of stats for saving. self.periodcheckbox = QCheckBox("period", self) self.periodcheckbox.stateChanged.connect( lambda: self.select_stats("period")) self.ratecheckbox = QCheckBox("rate", self) self.ratecheckbox.stateChanged.connect( lambda: self.select_stats("rate")) self.tidalampcheckbox = QCheckBox("tidal amplitude", self) self.tidalampcheckbox.stateChanged.connect( lambda: self.select_stats("tidalamp")) # Channel selection. self.sigchanmenulabel = QLabel("biosignal") self.sigchanmenu = QComboBox(self) self.sigchanmenu.addItem("A1") self.sigchanmenu.addItem("A2") self.sigchanmenu.addItem("A3") self.sigchanmenu.addItem("A4") self.sigchanmenu.addItem("A5") self.sigchanmenu.addItem("A6") self.sigchanmenu.currentTextChanged.connect(self._model.set_signalchan) self._model.set_signalchan( self.sigchanmenu.currentText()) # initialize with default value self.markerchanmenulabel = QLabel("marker") self.markerchanmenu = QComboBox(self) self.markerchanmenu.addItem("none") self.markerchanmenu.addItem("I1") self.markerchanmenu.addItem("I2") self.markerchanmenu.addItem("A1") self.markerchanmenu.addItem("A2") self.markerchanmenu.addItem("A3") self.markerchanmenu.addItem("A4") self.markerchanmenu.addItem("A5") self.markerchanmenu.addItem("A6") self.markerchanmenu.currentTextChanged.connect( self._model.set_markerchan) self._model.set_markerchan(self.markerchanmenu.currentText()) # Processing mode. self.batchmenulabel = QLabel("mode") self.batchmenu = QComboBox(self) self.batchmenu.addItem("single file") self.batchmenu.addItem("multiple files") self.batchmenu.currentTextChanged.connect(self._model.set_batchmode) self.batchmenu.currentTextChanged.connect(self.toggle_options) self._model.set_batchmode(self.batchmenu.currentText()) self.toggle_options(self.batchmenu.currentText()) # Modality selection. self.modmenulabel = QLabel("modality") self.modmenu = QComboBox(self) self.modmenu.addItem("ECG") self.modmenu.addItem("PPG") self.modmenu.addItem("RESP") self.modmenu.currentTextChanged.connect(self._model.set_modality) self.modmenu.currentTextChanged.connect(self.toggle_options) self._model.set_modality(self.modmenu.currentText()) self.toggle_options(self.modmenu.currentText()) # Segment selection. This widget can be openend / set visible from # the menu and closed from within itself (see mapping of segmentermap). self.segmentermap = QSignalMapper(self) self.segmenter = QDockWidget("select a segment", self) self.segmenter.setFeatures( QDockWidget.NoDockWidgetFeatures ) # disable closing such that widget can only be closed by confirming selection or custom button regex = QRegExp( "[0-9]*\.?[0-9]{4}") # Limit number of decimals to four validator = QRegExpValidator(regex) self.startlabel = QLabel("start") self.startedit = QLineEdit() self.startedit.setValidator(validator) self.endlabel = QLabel("end") self.endedit = QLineEdit() self.endedit.setValidator(validator) segmentfromcursor = QAction(QIcon(":/mouse_icon.png"), "select with mouse", self) segmentfromcursor.triggered.connect(self.enable_segmentedit) self.startedit.addAction(segmentfromcursor, QLineEdit.TrailingPosition) self.endedit.addAction(segmentfromcursor, QLineEdit.TrailingPosition) self.previewedit = QPushButton("preview segment") lambdafn = lambda: self._model.set_segment( [self.startedit.text(), self.endedit.text()]) self.previewedit.clicked.connect(lambdafn) self.confirmedit = QPushButton("confirm segment") self.confirmedit.clicked.connect(self._controller.segment_dataset) self.confirmedit.clicked.connect(self.segmentermap.map) self.segmentermap.setMapping(self.confirmedit, 0) self.abortedit = QPushButton("abort segmentation") self.abortedit.clicked.connect(self.segmentermap.map) self.segmentermap.setMapping(self.abortedit, 2) # resets the segment to None self.segmenterlayout = QFormLayout() self.segmenterlayout.addRow(self.startlabel, self.startedit) self.segmenterlayout.addRow(self.endlabel, self.endedit) self.segmenterlayout.addRow(self.previewedit) self.segmenterlayout.addRow(self.confirmedit) self.segmenterlayout.addRow(self.abortedit) self.segmenterwidget = QWidget() self.segmenterwidget.setLayout(self.segmenterlayout) self.segmenter.setWidget(self.segmenterwidget) self.segmenter.setVisible(False) self.segmenter.setAllowedAreas(Qt.RightDockWidgetArea) self.addDockWidget(Qt.RightDockWidgetArea, self.segmenter) # Custom file dialog. regex = QRegExp("[1-9][0-9]") validator = QRegExpValidator(regex) self.signallabel = QLabel("biosignal column") self.signaledit = QLineEdit() self.signaledit.setValidator(validator) self.markerlabel = QLabel("marker column") self.markeredit = QLineEdit() self.markeredit.setValidator(validator) regex = QRegExp("[0-9]{2}") validator = QRegExpValidator(regex) self.headerrowslabel = QLabel("number of header rows") self.headerrowsedit = QLineEdit() self.headerrowsedit.setValidator(validator) regex = QRegExp("[0-9]{5}") validator = QRegExpValidator(regex) self.sfreqlabel = QLabel("sampling rate") self.sfreqedit = QLineEdit() self.sfreqedit.setValidator(validator) self.separatorlabel = QLabel("column separator") self.separatormenu = QComboBox(self) self.separatormenu.addItem("comma") self.separatormenu.addItem("tab") self.separatormenu.addItem("colon") self.separatormenu.addItem("space") self.continuecustomfile = QPushButton("continue loading file") self.continuecustomfile.clicked.connect(self.set_customheader) self.customfiledialog = QDialog() self.customfiledialog.setWindowTitle("custom file info") self.customfiledialog.setWindowIcon(QIcon(":/file_icon.png")) self.customfiledialog.setWindowFlags( Qt.WindowCloseButtonHint ) # remove help button by only setting close button self.customfilelayout = QFormLayout() self.customfilelayout.addRow(self.signallabel, self.signaledit) self.customfilelayout.addRow(self.markerlabel, self.markeredit) self.customfilelayout.addRow(self.separatorlabel, self.separatormenu) self.customfilelayout.addRow(self.headerrowslabel, self.headerrowsedit) self.customfilelayout.addRow(self.sfreqlabel, self.sfreqedit) self.customfilelayout.addRow(self.continuecustomfile) self.customfiledialog.setLayout(self.customfilelayout) # Layout. menubar = self.menuBar() signalmenu = menubar.addMenu("biosignal") openSignal = signalmenu.addMenu("load") openEDF = QAction("EDF", self) openEDF.triggered.connect(lambda: self._model.set_filetype("EDF")) openEDF.triggered.connect(self._controller.load_channels) openSignal.addAction(openEDF) openOpenSignals = QAction("OpenSignals", self) openOpenSignals.triggered.connect( lambda: self._model.set_filetype("OpenSignals")) openOpenSignals.triggered.connect(self._controller.load_channels) openSignal.addAction(openOpenSignals) openCustom = QAction("Custom", self) openCustom.triggered.connect( lambda: self._model.set_filetype("Custom")) openCustom.triggered.connect(lambda: self.customfiledialog.exec_()) openSignal.addAction(openCustom) segmentSignal = QAction("select segment", self) segmentSignal.triggered.connect(self.segmentermap.map) self.segmentermap.setMapping(segmentSignal, 1) signalmenu.addAction(segmentSignal) self.segmentermap.mapped.connect(self.toggle_segmenter) saveSignal = QAction("save", self) saveSignal.triggered.connect(self._controller.save_channels) signalmenu.addAction(saveSignal) peakmenu = menubar.addMenu("peaks") findPeaks = QAction("find", self) findPeaks.triggered.connect(self._controller.find_peaks) peakmenu.addAction(findPeaks) autocorrectPeaks = QAction("autocorrect", self) autocorrectPeaks.triggered.connect(self._controller.autocorrect_peaks) peakmenu.addAction(autocorrectPeaks) savePeaks = QAction("save", self) savePeaks.triggered.connect(self._controller.save_peaks) peakmenu.addAction(savePeaks) loadPeaks = QAction("load", self) loadPeaks.triggered.connect(self._controller.load_peaks) peakmenu.addAction(loadPeaks) statsmenu = menubar.addMenu("statistics") calculateStats = QAction("calculate", self) calculateStats.triggered.connect(self._controller.calculate_stats) statsmenu.addAction(calculateStats) saveStats = QAction("save", self) saveStats.triggered.connect(self._controller.save_stats) statsmenu.addAction(saveStats) self.statusBar = QStatusBar() self.setStatusBar(self.statusBar) self.progressBar = QProgressBar(self) self.progressBar.setRange(0, 1) self.statusBar.addPermanentWidget(self.progressBar) self.currentFile = QLabel() self.statusBar.addPermanentWidget(self.currentFile) self.centwidget = QWidget() # contains figures and navigationtoolbar self.setCentralWidget(self.centwidget) self.canvas0.setFocusPolicy( Qt.ClickFocus ) # only widgets (e.g. canvas) that currently have focus capture keyboard input self.canvas0.setFocus() self.canvas0.mpl_connect( "key_press_event", self._controller.edit_peaks ) # connect canvas to keyboard input for peak editing self.canvas0.mpl_connect( "button_press_event", self.get_xcursor) # connect canvas to mouse input for peak editing self.splitter = QSplitter( Qt.Vertical ) # arrange the three figure canvases in splitter object self.splitter.setOpaqueResize( False) # resizing gets very slow otherwise once axes are populated self.splitter.addWidget(self.canvas0) self.splitter.addWidget(self.canvas1) self.splitter.addWidget(self.canvas2) self.splitter.setChildrenCollapsible(False) self.vlayout0 = QVBoxLayout(self.centwidget) self.vlayout1 = QVBoxLayout() self.vlayoutA = QFormLayout() self.vlayoutB = QFormLayout() self.vlayoutC = QVBoxLayout() self.vlayoutD = QVBoxLayout() self.hlayout0 = QHBoxLayout() self.optionsgroupA = QGroupBox("processing options") self.vlayoutA.addRow(self.modmenulabel, self.modmenu) self.vlayoutA.addRow(self.batchmenulabel, self.batchmenu) self.optionsgroupA.setLayout(self.vlayoutA) self.optionsgroupB = QGroupBox("channels") self.vlayoutB.addRow(self.sigchanmenulabel, self.sigchanmenu) self.vlayoutB.addRow(self.markerchanmenulabel, self.markerchanmenu) self.optionsgroupB.setLayout(self.vlayoutB) self.optionsgroupC = QGroupBox("peaks") self.vlayoutC.addWidget(self.editcheckbox) self.vlayoutC.addWidget(self.savecheckbox) self.vlayoutC.addWidget(self.correctcheckbox) self.optionsgroupC.setLayout(self.vlayoutC) self.optionsgroupD = QGroupBox("select statistics for saving") self.vlayoutD.addWidget(self.periodcheckbox) self.vlayoutD.addWidget(self.ratecheckbox) self.vlayoutD.addWidget(self.tidalampcheckbox) self.optionsgroupD.setLayout(self.vlayoutD) self.vlayout1.addWidget(self.optionsgroupA) self.vlayout1.addWidget(self.optionsgroupB) self.vlayout1.addWidget(self.optionsgroupC) self.vlayout1.addWidget(self.optionsgroupD) self.optionsgroupwidget = QWidget() self.optionsgroupwidget.setLayout(self.vlayout1) self.optionsgroup = QDockWidget("configurations", self) self.optionsgroup.setAllowedAreas(Qt.LeftDockWidgetArea) self.toggleoptionsgroup = self.optionsgroup.toggleViewAction() self.toggleoptionsgroup.setText("show/hide configurations") menubar.addAction(self.toggleoptionsgroup) self.optionsgroup.setWidget(self.optionsgroupwidget) self.addDockWidget(Qt.LeftDockWidgetArea, self.optionsgroup) self.vlayout0.addWidget(self.splitter) self.hlayout0.addWidget(self.navitools) self.vlayout0.addLayout(self.hlayout0) # Subscribe to updates from the Model. self._model.signal_changed.connect(self.plot_signal) self._model.marker_changed.connect(self.plot_marker) self._model.peaks_changed.connect(self.plot_peaks) self._model.period_changed.connect(self.plot_period) self._model.rate_changed.connect(self.plot_rate) self._model.tidalamp_changed.connect(self.plot_tidalamp) self._model.path_changed.connect(self.display_path) self._model.segment_changed.connect(self.plot_segment) self._model.status_changed.connect(self.display_status) self._model.progress_changed.connect(self.display_progress) self._model.model_reset.connect(self.reset_plot) def plot_signal(self, signal): """Plot the biosignal. Receives updates in signal from Model. Parameters ---------- signal : ndarray of float Vector representing the biosignal. See Also -------- model.Model.signal """ self.ax00.clear() self.ax00.relim() self.navitools.update() # reset navitools history self.line00 = self.ax00.plot(self._model.sec, signal, zorder=1) self.ax00.set_xlabel("seconds", fontsize="large", fontweight="heavy") self.canvas0.draw() def plot_peaks(self, peaks): """Plot the extrema. Receives updates in peaks from Model. Parameters ---------- peaks : ndarray of int Vector representing the extrema. See Also -------- model.Model.peaks """ if self.ax00.collections: # self.scat is listed in ax.collections self.ax00.collections[0].remove() self.scat = self.ax00.scatter(self._model.sec[peaks], self._model.signal[peaks], c="m", zorder=2) self.canvas0.draw() def plot_segment(self, segment): """Show preview of segment. Receives updates in segment from Model. Parameters ---------- segment : list of float The start and end of the segment in seconds. See Also -------- model.Model.segment """ if segment is None: # if an invalid segment has been selected reset the segmenter interface self.toggle_segmenter(1) return if self.ax00.patches: # self.segementspan is listed in ax.patches self.ax00.patches[0].remove() self.segmentspan = self.ax00.axvspan(segment[0], segment[1], color="m", alpha=0.25) self.canvas0.draw() self.confirmedit.setEnabled(True) def plot_marker(self, marker): """Plot the marker channel. Receives updates in marker from Model. Parameters ---------- marker : list of ndarray Seconds element is vector representing the marker channel and first element is a vector representing the seconds associated with each sample in the marker channel. See Also -------- model.Model.marker """ self.ax10.clear() self.ax10.relim() self.line10 = self.ax10.plot(marker[0], marker[1]) self.canvas1.draw() def plot_period(self, period): """Plot instantaneous period. Receives updates in period from Model. Parameters ---------- period : ndarray of float Vector representing the instantaneous period. See Also -------- model.Model.periodintp """ self.ax20.clear() self.ax20.relim() self.navitools.home() if self._model.savestats["period"]: self.line20 = self.ax20.plot(self._model.sec, period, c="m") else: self.line20 = self.ax20.plot(self._model.sec, period) self.ax20.set_ylim(bottom=min(period), top=max(period)) self.ax20.set_title("period", pad=0, fontweight="heavy") self.ax20.grid(True, axis="y") self.navitools.update() self.canvas2.draw() def plot_rate(self, rate): """Plot instantaneous rate. Receives updates in rate from Model. Parameters ---------- rate : ndarray of float Vector representing the instantaneous rate. See Also -------- model.Model.rateintp """ self.ax21.clear() self.ax21.relim() self.navitools.home() if self._model.savestats["rate"]: self.line21 = self.ax21.plot(self._model.sec, rate, c="m") else: self.line21 = self.ax21.plot(self._model.sec, rate) self.ax21.set_ylim(bottom=min(rate), top=max(rate)) self.ax21.set_title("rate", pad=0, fontweight="heavy") self.ax21.grid(True, axis="y") self.navitools.update() self.canvas2.draw() def plot_tidalamp(self, tidalamp): """Plot instantaneous tidal amplitude. Receives updates in tidal amplitude from Model. Parameters ---------- tidalamp : ndarray of float Vector representing the instantaneous tidal amplitude. See Also -------- model.Model.tidalampintp """ self.ax22.clear() self.ax22.relim() self.navitools.home() if self._model.savestats["tidalamp"]: self.line22 = self.ax22.plot(self._model.sec, tidalamp, c="m") else: self.line22 = self.ax22.plot(self._model.sec, tidalamp) self.ax22.set_ylim(bottom=min(tidalamp), top=max(tidalamp)) self.ax22.set_title("amplitude", pad=0, fontweight="heavy") self.ax22.grid(True, axis="y") self.navitools.update() self.canvas2.draw() def display_path(self, path): """Display the path to the current dataset. Receives update in path from Model. Parameters ---------- path : str The path to the file containing the current dataset. See Also -------- model.Model.rpathsignal """ self.currentFile.setText(path) def display_status(self, status): """Display a status message. Receives updates in status message from Model. Parameters ---------- status : str A status message. See Also -------- model.Model.status """ self.statusBar.showMessage(status) def display_progress(self, progress): """Display task progress. Receives updates in progress from Model. Parameters ---------- progress : int Integer indicating the current task progress. See Also -------- model.Model.progress, controller.Worker, controller.threaded """ self.progressBar.setRange( 0, progress) # indicates busy state if progress is 0 def toggle_segmenter(self, visibility_state): """Toggle visibility of segmenter widget. Parameters ---------- visibility_state : int Update in state of the segmenter widget's visibility. """ if not self._model.loaded: return if visibility_state == 1: # open segmenter when called from signalmenu or clear segmenter upon selection of invalid segment self.segmenter.setVisible(True) self.confirmedit.setEnabled(False) self.startedit.clear() self.endedit.clear() elif visibility_state == 0: # close segmenter after segment has been confirmed self.segmenter.setVisible(False) elif visibility_state == 2: # close segmenter after segmentation has been aborted (reset segment) self._model.set_segment([0, 0]) self.segmenter.setVisible(False) if self.ax00.patches: self.ax00.patches[0].remove() self.canvas0.draw() def enable_segmentedit(self): """Associate cursor position with a specific segmenter text field. Regulate if cursor position is associated with editing the start or end of a segment. """ self.editcheckbox.setChecked( False) # disable peak editing to avoid interference if self.startedit.hasFocus(): self.segmentcursor = "start" elif self.endedit.hasFocus(): self.segmentcursor = "end" def get_xcursor(self, mouse_event): """Retrieve input to segmenter text fields from cursor position. Retrieve the start or end of a segment in seconds from the current cursor position. Parameters ---------- mouse_event : MouseEvent Event containing information about the current cursor position in data coordinates. See Also -------- matplotlib.backend_bases.MouseEvent """ if mouse_event.button != 1: # 1 = left mouse button return if self.segmentcursor == "start": self.startedit.selectAll() self.startedit.insert("{:.2f}".format( mouse_event.xdata)) # limit number of decimal places to two elif self.segmentcursor == "end": self.endedit.selectAll() self.endedit.insert("{:.2f}".format(mouse_event.xdata)) self.segmentcursor = False # disable segment cursor again after value has been set def set_customheader(self): """Populate the customheader with inputs from the customfiledialog.""" mandatoryfields = self.signaledit.text() and self.headerrowsedit.text( ) and self.sfreqedit.text( ) # check if one of the mandatory fields is missing if not mandatoryfields: self._model.status = ( "Please provide values for 'biosignal column'" ", 'number of header rows' and 'sampling" " rate'.") return seps = {"comma": ",", "tab": "\t", "colon": ":", "space": " "} self._model.customheader = dict.fromkeys( self._model.customheader, None ) # reset header here since it cannot be reset in controller.load_chanels self._model.customheader["signalidx"] = int(self.signaledit.text()) self._model.customheader["skiprows"] = int(self.headerrowsedit.text()) self._model.customheader["sfreq"] = int(self.sfreqedit.text()) self._model.customheader["separator"] = seps[ self.separatormenu.currentText()] if self.markeredit.text(): # not mandatory self._model.customheader["markeridx"] = int(self.markeredit.text()) self.customfiledialog.done(QDialog.Accepted) # close the dialog window self._controller.load_channels() # move on to file selection def select_stats(self, statistic): """Select statistics to be saved. Parameters ---------- statistic : str The selected statistic. """ self._model.savestats[ statistic] ^= True # toggle boolean with xor operator line = None if statistic == "period": if self.line20: line = self.line20[0] elif statistic == "rate": if self.line21: line = self.line21[0] elif statistic == "tidalamp": if self.line22: line = self.line22[0] if line: line.set_color(self.togglecolors[line.get_color()]) self.canvas2.draw() def toggle_options(self, state): """Toggle availability of configuration options. Based on current state. Parameters ---------- state : str The aspect of the current state to which the availability of configuration options needs to be adapted. """ if state in ["ECG", "PPG"]: self.tidalampcheckbox.setEnabled(False) self.tidalampcheckbox.setChecked(False) self.ax22.set_visible(False) self.canvas2.draw() elif state == "RESP": self.tidalampcheckbox.setEnabled(True) self.ax22.set_visible(True) self.canvas2.draw() elif state == "multiple files": self.editcheckbox.setEnabled(False) self.editcheckbox.setChecked(False) self.savecheckbox.setEnabled(True) self.correctcheckbox.setEnabled(True) self.markerchanmenu.setEnabled(False) elif state == "single file": self.editcheckbox.setEnabled(True) self.markerchanmenu.setEnabled(True) self.savecheckbox.setEnabled(False) self.savecheckbox.setChecked(False) self.correctcheckbox.setEnabled(False) self.correctcheckbox.setChecked(False) def reset_plot(self): """Reset plot elements associated with the current dataset.""" self.ax00.clear() self.ax00.relim() self.line00 = None self.scat = None self.segmentspan = None self.ax10.clear() self.ax10.relim() self.line10 = None self.ax20.clear() self.ax20.relim() self.line20 = None self.ax21.clear() self.ax21.relim() self.line21 = None self.ax22.clear() self.ax22.relim() self.line22 = None self.canvas0.draw() self.canvas1.draw() self.canvas2.draw() self.navitools.update() self.currentFile.clear()
class ColorChooser(QWidget): def __init__(self, default_color: str, show_pretty_name: bool = True): """ Color chooser widget :param default_color: current color to set :param show_pretty_name: shows the pretty name of the color next to its hexa value """ QWidget.__init__(self) # Convert 'regular' name colors into QColor to retrieve their hexa code self.color = QColor(default_color).name().lower() self.show_pretty_name = show_pretty_name # Widgets self.lab = QLabel() self.lab.setStyleSheet("color: black; font-style: italic;") self.btn = QPushButton(self.color.upper()) self.btn.clicked.connect(self.__change_color) # Layout layout = QHBoxLayout() layout.setMargin(0) layout.addWidget(self.btn) if self.show_pretty_name: layout.addWidget(self.lab) self.setLayout(layout) self.update_bg() def update_bg(self) -> None: """ Updates the background color given the button's name and looks for the color's display name """ self.btn.setStyleSheet(f"background: {self.btn.text()}; color: black;") if self.btn.text().lower() in COLOR_DICT: pretty_name = COLOR_DICT[self.btn.text().lower()] if self.show_pretty_name: self.lab.setText(pretty_name) else: self.btn.setToolTip(pretty_name) else: if self.show_pretty_name: self.lab.clear() else: self.btn.setToolTip(None) def __change_color(self) -> None: """ Displays the color chooser dialog to select a new color, then updates this widget """ dlg = QColorDialog(self.color) if dlg.exec_(): self.color = self.closest_color(dlg.currentColor().name()) self.btn.setText(self.color.upper()) self.update_bg() def get_color(self) -> str: """ Retrieves the selected color to HEX format """ return self.color def get_distance_color(self, c1, c2): """returns the distance between 2 colors""" def hex2dec(h): return int("0x" + h, 16) # normalize input format if c1[0] == "#": c1 = c1[1:] if c2[0] == "#": c2 = c2[1:] r1 = hex2dec(c1[0:2]) g1 = hex2dec(c1[2:4]) b1 = hex2dec(c1[4:6]) r2 = hex2dec(c2[0:2]) g2 = hex2dec(c2[2:4]) b2 = hex2dec(c2[4:6]) return (r2 - r1)**2 + (b2 - b1)**2 + (g2 - g1)**2 def closest_color(self, c): min_d = 1000000 for c1 in COLOR_DICT: d = self.get_distance_color(c, c1) if d == 0: return c1 if d < min_d: min_c = c1 min_d = d return min_c
class MainWindow(QMainWindow): def __init__(self): super().__init__() self.users = {} self.fps = 30 self.playing = False self.capturing = False self.capture_frame = None self.flag_recognize = False self.recognizing_frame = None self.recognized_faces = [] self.model = None self.setFixedSize(Config.width, Config.height) qr = self.frameGeometry() cp = QDesktopWidget().availableGeometry().center() qr.moveCenter(cp) self.move(qr.topLeft()) self.setWindowIcon(QIcon('icons/icon.png')) self.setWindowTitle('人工智障人脸识别客户端') self.lbl_viewer = QLabel(self) self.lbl_viewer.setGeometry( QRect(10, 26, Config.width - 130, Config.height - 60)) self.lbl_viewer.setText('没有图像') font = QFont() font.setPointSize(20) self.lbl_viewer.setFont(font) self.lbl_viewer.setAlignment(Qt.AlignCenter) self.lbl_viewer.setFrameShape(QFrame.StyledPanel) self.btn_open_camera = QPushButton(self) self.btn_open_camera.setGeometry(QRect(Config.width - 110, 10, 100, 26)) self.btn_open_camera.setText('打开摄像头') self.btn_open_camera.clicked.connect(self.btn_click) self.btn_close_camera = QPushButton(self) self.btn_close_camera.setGeometry( QRect(Config.width - 110, 46, 100, 26)) self.btn_close_camera.setText('关闭摄像头') self.btn_close_camera.setDisabled(True) self.btn_close_camera.clicked.connect(self.btn_click) self.btn_open_video = QPushButton(self) self.btn_open_video.setGeometry(QRect(Config.width - 110, 82, 100, 26)) self.btn_open_video.setText('播放视频') self.btn_open_video.clicked.connect(self.btn_click) self.btn_close_video = QPushButton(self) self.btn_close_video.setGeometry( QRect(Config.width - 110, 118, 100, 26)) self.btn_close_video.setText('停止播放') self.btn_close_video.setDisabled(True) self.btn_close_video.clicked.connect(self.btn_click) self.cb_recognize = QCheckBox(self) self.cb_recognize.setText('启动识别') self.cb_recognize.setDisabled(True) self.cb_recognize.setGeometry(QRect(Config.width - 108, 154, 100, 26)) self.cb_recognize.clicked.connect(self.change_recognize) self.cb_show_match_result = QCheckBox(self) self.cb_show_match_result.setText('显示匹配度') self.cb_show_match_result.setDisabled(True) self.cb_show_match_result.setGeometry( QRect(Config.width - 108, 192, 100, 26)) if Config.show_match_result: self.cb_show_match_result.setChecked(True) self.cb_show_match_result.clicked.connect( self.change_show_match_result) lbl_threshold = QLabel(self) lbl_threshold.setText("识别精度") lbl_threshold.setGeometry(QRect(Config.width - 108, 228, 60, 26)) self.sb_threshold = QSpinBox(self) self.sb_threshold.setValue(Config.threshold) self.sb_threshold.setMinimum(30) self.sb_threshold.setMaximum(100) self.sb_threshold.valueChanged.connect(self.change_threshold) self.sb_threshold.setGeometry(QRect(Config.width - 45, 228, 40, 26)) self.btn_capture = QPushButton(self) self.btn_capture.setGeometry( QRect(Config.width - 110, Config.height - 200, 100, 26)) self.btn_capture.setText('截屏') self.btn_capture.setDisabled(True) self.btn_capture.clicked.connect(self.btn_click) self.lbl_capture_pic = QLabel(self) self.lbl_capture_pic.setGeometry( QRect(Config.width - 110, Config.height - 160, 100, 100)) self.lbl_capture_pic.setAlignment(Qt.AlignCenter) self.lbl_capture_pic.setFrameShape(QFrame.StyledPanel) self.btn_capture_save = QPushButton(self) self.btn_capture_save.setGeometry( QRect(Config.width - 110, Config.height - 60, 100, 26)) self.btn_capture_save.setText('保存截图') self.btn_capture_save.setDisabled(True) self.btn_capture_save.clicked.connect(self.btn_click) self.train_model() def btn_click(self): btn = self.sender() if btn == self.btn_open_camera: self.btn_open_camera.setDisabled(True) self.btn_close_camera.setDisabled(False) self.btn_capture.setDisabled(False) self.cb_recognize.setDisabled(False) self.start_play(0) elif btn == self.btn_close_camera: self.stop_play() self.lbl_viewer.clear() self.btn_open_camera.setDisabled(False) self.btn_close_camera.setDisabled(True) self.btn_capture.setDisabled(True) self.cb_recognize.setDisabled(True) elif btn == self.btn_open_video: file_name = QFileDialog.getOpenFileName( self, "Open Video", "/", "Video Files (*.avi *.mp4 *.mpeg *.mov)") if file_name[0] != '': self.start_play(file_name[0]) self.btn_close_video.setDisabled(False) elif btn == self.btn_close_video: self.stop_play() self.lbl_viewer.clear() self.btn_open_camera.setDisabled(False) self.btn_close_camera.setDisabled(True) self.btn_capture.setDisabled(True) self.cb_recognize.setDisabled(True) elif btn == self.btn_capture: self.capturing = True self.btn_capture_save.setDisabled(False) elif btn == self.btn_capture_save: AddUserFace(self.capture_frame).exec_() self.train_model() def change_recognize(self): if self.sender().isChecked(): self.flag_recognize = True Thread(target=self.recognize).start() self.cb_show_match_result.setDisabled(False) else: self.flag_recognize = False self.cb_show_match_result.setDisabled(True) def change_show_match_result(self): Config.config_parser.set( "recognition", 'show_match_result', str(True if self.sender().isChecked() else False)) Config.show_match_result = True if self.sender().isChecked() else False Config.save() @staticmethod def change_threshold(value): Config.config_parser.set("recognition", 'threshold', str(value)) Config.threshold = value Config.save() def start_play(self, path): self.playing = True Thread(target=self.play, args=(path, )).start() def stop_play(self): self.lbl_viewer.clear() self.playing = False def play(self, path): video_capture = cv2.VideoCapture(path) fps = video_capture.get(cv2.CAP_PROP_FPS) while self.playing: start_time = time.time() ret, frame = video_capture.read() if ret: if self.flag_recognize: # 存放当前帧给识别线程 if self.recognizing_frame is None: self.recognizing_frame = frame.copy() # 显示已识别的人脸 faces = self.recognized_faces.copy() for face in faces: x, y, w, h = face['position'] cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 0, 0), 1, cv2.LINE_AA) cv2.putText( frame, face['name'] + ('(' + str(face['degree']) + ')' if Config.show_match_result else ''), (x, y - 15), cv2.FONT_HERSHEY_SIMPLEX, 2, (255, 0, 0), 2) img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) image = QImage(img, img.shape[1], img.shape[0], img.shape[1] * 3, QImage.Format_RGB888) pix_map = QPixmap.fromImage(image) pix_map = pix_map.scaled(Config.width - 130, Config.height - 60, Qt.KeepAspectRatio) self.lbl_viewer.setPixmap(pix_map) # 保存截图 if self.capturing: self.capture_frame = frame.copy() pix_map = pix_map.scaled(100, 100, Qt.KeepAspectRatio) self.lbl_capture_pic.setPixmap(pix_map) self.capturing = False # 根据FPS控制休眠 end_time = time.time() diff_time = end_time - start_time if diff_time < 1 / fps: time.sleep(1 / fps - diff_time) def recognize(self): while self.flag_recognize: try: if self.recognizing_frame is None: time.sleep(0.05) continue self.recognized_faces.clear() faces = HaarcascadeDetective.get_faces_position( self.recognizing_frame) for (x, y, w, h) in faces: recognized_face = {'position': (x, y, w, h)} face = self.recognizing_frame[y:y + h, x:x + w] if self.model is not None: gray = cv2.cvtColor(face, cv2.COLOR_BGR2GRAY) params = self.model.predict(gray) if params[1] <= Config.threshold: recognized_face['name'] = self.users[params[0]] recognized_face['degree'] = int(params[1]) self.recognized_faces.append(recognized_face) self.recognizing_frame = None except Exception as e: print(e) def train_model(self): users = DbHelper.query_users() for user in users: self.users[user[0]] = user[1] y, x = [], [] if not os.path.exists("faces"): return faces_dir = os.listdir('faces') for user_dir in faces_dir: faces = os.listdir('faces{}{}'.format(os.sep, user_dir)) for face in faces: y.append(int(user_dir)) im = cv2.imread( 'faces{}{}{}{}'.format(os.sep, user_dir, os.sep, face), 0) x.append(np.asarray(im, dtype=np.uint8)) if len(x) != 0 and len(y) != 0: self.model = cv2.face.LBPHFaceRecognizer_create() self.model.train(np.asarray(x), np.asarray(y, dtype=np.int64)) def closeEvent(self, *args, **kwargs): self.playing = False
lbl = QLabel(window) lbl.setGeometry(100, 120, 1536, 761) def showimage(): # lbl.clear() selectedtran = transistorlist.currentText() selectedtran = selectedtran + '.png' image = QtGui.QImage(selectedtran) pixmap = QtGui.QPixmap(image) lbl.setPixmap(pixmap.scaled(lbl.size(), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)) while 1: if transistorlist.currentText() == 'None.png': lbl.clear() image = QtGui.QImage('None.png') pixmap = QtGui.QPixmap(image) lbl.setPixmap(pixmap.scaled(lbl.size(), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)) window.show() app.exec_() elif transistorlist.currentIndexChanged != -1: lbl.clear() transistorlist.currentIndexChanged.connect(showimage()) window.show() app.exec_() else: break # window.show() # app.exec_()
class Main(QMainWindow): def __init__(self): super(Main, self).__init__() # Basic Settings self.setGeometry(300, 200, 591, 280) self.setMinimumSize(QSize(591, 280)) self.setMaximumSize(QSize(591, 280)) self.setWindowIcon(QIcon("arti.PNG")) self.setWindowTitle("Artigence Updates") self.setFont(QFont('Roboto', 12)) # Color Scheme self.palette = QPalette() self.palette.setColor(self.palette.Window, QColor('#000000')) self.palette.setColor(self.palette.WindowText, QColor('#FFFFFF')) self.setPalette(self.palette) self.light_palette = QPalette() self.light_palette.setColor(self.light_palette.Window, QColor('#FFFFFF')) self.light_palette.setColor(self.light_palette.WindowText, QColor('#000000')) # Setting MenuBar self.menubar = QMenuBar(self) self.menubar.setGeometry(0, 0, 682, 21) self.menubar.setFont(QFont('Roboto', 10)) self.date_menu = QMenu(self.menubar) self.date_menu.setTitle(str(datetime.now().strftime('%d-%m-%Y'))) self.theme_menu = QMenu(self.menubar) self.theme_menu.setTitle('Theme') self.dark_theme = QAction('Dark Theme') self.dark_theme.setShortcut(QKeySequence('Ctrl+Shift+D')) self.theme_menu.addAction(self.dark_theme) self.dark_theme.triggered.connect(lambda: self.dark()) self.light_theme = QAction('Light Theme') self.light_theme.setShortcut(QKeySequence('Ctrl+Shift+L')) self.theme_menu.addAction(self.light_theme) self.light_theme.triggered.connect(lambda: self.light()) self.setMenuBar(self.menubar) self.menubar.addAction(self.date_menu.menuAction()) self.menubar.addAction(self.theme_menu.menuAction()) # Widgets self.update_label = QLabel(self) self.update_label.setGeometry(20, 30, 551, 41) self.update_label.setText('Update Checker') self.update_label.setAlignment(Qt.AlignCenter) self.available = QLabel(self) self.available.setGeometry(20, 110, 541, 61) self.available.setText('') self.available.setAlignment(Qt.AlignCenter) self.yes = QPushButton(self) self.yes.setGeometry(350, 230, 75, 33) self.yes.setText('Yes') self.no = QPushButton(self) self.no.setGeometry(440, 230, 145, 33) self.no.setText('Just Open My App') self.no.clicked.connect(lambda: self.main_func()) def main_func(self): self.setWindowFlags(QtCore.Qt.Tool) time.sleep(2) from start import Artigence self.main = Artigence() self.main.show() def dark(self): self.setPalette(self.palette) def light(self): self.setPalette(self.light_palette) # The A.I. will speak through this function def speak(self, audio): self.engine = pyttsx3.init('sapi5') voices = self.engine.getProperty('voices') self.engine.setProperty('voice', voices[1].id) self.engine.setProperty('rate', 165) self.available.setText(audio) self.engine.say(audio) self.engine.runAndWait() self.available.clear()