class ModelFinder(QDialog, Ui_ModelFinder, object): exception_signal = pyqtSignal(str) # 定义所有类都可以使用的信号 progressSig = pyqtSignal(int) # 控制进度条 startButtonStatusSig = pyqtSignal(list) logGuiSig = pyqtSignal(str) search_algorithm = pyqtSignal(str) # 定义所有类都可以使用的信号 workflow_progress = pyqtSignal(int) workflow_finished = pyqtSignal(str) iq_tree_exception = pyqtSignal(str) # 定义所有类都可以使用的信号 # 用于flowchart自动popup combobox等操作 showSig = pyqtSignal(QDialog) closeSig = pyqtSignal(str, str) # 用于输入文件后判断用 ui_closeSig = pyqtSignal(str) ##弹出识别输入文件的信号 auto_popSig = pyqtSignal(QDialog) def __init__( self, autoMFPath=None, partFile=None, workPath=None, focusSig=None, IQ_exe=None, workflow=False, parent=None): super(ModelFinder, self).__init__(parent) self.parent = parent self.function_name = "ModelFinder" self.factory = Factory() self.thisPath = self.factory.thisPath self.workPath = workPath self.focusSig = focusSig if focusSig else pyqtSignal( str) # 为了方便workflow self.workflow = workflow self.modelfinder_exe = '"' + IQ_exe + '"' self.autoMFPath = autoMFPath[0] if type(autoMFPath) == list else autoMFPath self.partFile = partFile self.interrupt = False self.setupUi(self) # 保存设置 if not workflow: self.modelfinder_settings = QSettings( self.thisPath + '/settings/modelfinder_settings.ini', QSettings.IniFormat) else: self.modelfinder_settings = QSettings( self.thisPath + '/settings/workflow_settings.ini', QSettings.IniFormat) self.modelfinder_settings.beginGroup("Workflow") self.modelfinder_settings.beginGroup("temporary") self.modelfinder_settings.beginGroup('ModelFinder') # File only, no fallback to registry or or. self.modelfinder_settings.setFallbacksEnabled(False) # 开始装载样式表 with open(self.thisPath + os.sep + 'style.qss', encoding="utf-8", errors='ignore') as f: self.qss_file = f.read() self.setStyleSheet(self.qss_file) # 恢复用户的设置 self.guiRestore() self.exception_signal.connect(self.popupException) self.iq_tree_exception.connect(self.popup_IQTREE_exception) self.startButtonStatusSig.connect(self.factory.ctrl_startButton_status) self.progressSig.connect(self.runProgress) self.logGuiSig.connect(self.addText2Log) self.comboBox_4.installEventFilter(self) self.log_gui = self.gui4Log() self.lineEdit.installEventFilter(self) self.lineEdit.autoDetectSig.connect(self.popupAutoDec) self.lineEdit_2.installEventFilter(self) self.lineEdit_3.installEventFilter(self) self.comboBox_3.activated[str].connect(self.controlCodonTable) self.comboBox_5.activated[str].connect(self.ctrl_ratehet) self.ctrl_ratehet(self.comboBox_5.currentText()) self.lineEdit_2.setLineEditNoChange(True) self.lineEdit_3.setLineEditNoChange(True) self.lineEdit.deleteFile.clicked.connect( self.clear_lineEdit) # 删除了内容,也要把tooltip删掉 self.lineEdit_2.deleteFile.clicked.connect( self.clear_lineEdit) # 删除了内容,也要把tooltip删掉 self.lineEdit_3.deleteFile.clicked.connect( self.clear_lineEdit) # 删除了内容,也要把tooltip删掉 # 初始化codon table的选择 self.controlCodonTable(self.comboBox_3.currentText()) self.checkBox.stateChanged.connect(self.switchPart) self.switchPart(self.checkBox.isChecked()) # 给开始按钮添加菜单 menu = QMenu(self) menu.setToolTipsVisible(True) action = QAction(QIcon(":/picture/resourses/terminal-512.png"), "View | Edit command", menu, triggered=self.showCMD) self.work_action = QAction(QIcon(":/picture/resourses/work.png"), "", menu) self.work_action.triggered.connect(lambda: self.factory.swithWorkPath(self.work_action, parent=self)) self.dir_action = QAction(QIcon(":/picture/resourses/folder.png"), "Output Dir: ", menu) self.dir_action.triggered.connect(lambda: self.factory.set_direct_dir(self.dir_action, self)) menu.addAction(action) menu.addAction(self.work_action) menu.addAction(self.dir_action) self.pushButton.toolButton.setMenu(menu) self.pushButton.toolButton.menu().installEventFilter(self) self.factory.swithWorkPath(self.work_action, init=True, parent=self) # 初始化一下 ## brief demo self.label_2.clicked.connect(lambda: QDesktopServices.openUrl(QUrl( "https://dongzhang0725.github.io/dongzhang0725.github.io/documentation/#5-9-1-Brief-example"))) ##自动弹出识别文件窗口 self.auto_popSig.connect(self.popupAutoDecSub) @pyqtSlot() def on_pushButton_clicked(self): """ execute program """ try: self.command = self.getCMD() if self.command: self.MF_popen = self.factory.init_popen(self.command) self.factory.emitCommands(self.logGuiSig, self.command) self.worker = WorkThread(self.run_command, parent=self) self.worker.start() except: self.exceptionInfo = ''.join( traceback.format_exception( *sys.exc_info())) # 捕获报错内容,只能在这里捕获,没有报错的地方无法捕获 self.exception_signal.emit(self.exceptionInfo) # 激发这个信号 @pyqtSlot() def on_pushButton_3_clicked(self): """ alignment file """ fileName = QFileDialog.getOpenFileName( self, "Input alignment file", filter="Phylip Format(*.phy *.phylip);;Fasta Format(*.fas *.fasta);;Nexus Format(*.nex *.nexus *.nxs);;Clustal Format(*.aln)") if fileName[0]: base = os.path.basename(fileName[0]) self.lineEdit.setText(base) self.lineEdit.setToolTip(fileName[0]) @pyqtSlot() def on_pushButton_4_clicked(self): """ tree file """ fileName = QFileDialog.getOpenFileName( self, "Input tree file", filter="Newick Format(*.nwk *.newick);;") if fileName[0]: base = os.path.basename(fileName[0]) self.lineEdit_2.setText(base) self.lineEdit_2.setToolTip(fileName[0]) @pyqtSlot() def on_pushButton_22_clicked(self): """ partition file """ fileName = QFileDialog.getOpenFileName( self, "Input partition file", filter="All(*);;") if fileName[0]: base = os.path.basename(fileName[0]) self.lineEdit_3.setText(base) self.lineEdit_3.setToolTip(fileName[0]) @pyqtSlot() def on_pushButton_9_clicked(self): """ show log """ self.log_gui.show() @pyqtSlot() def on_pushButton_2_clicked(self): """ Stop """ if self.isRunning(): if not self.workflow: reply = QMessageBox.question( self, "Confirmation", "<p style='line-height:25px; height:25px'>ModelFinder is still running, terminate it?</p>", QMessageBox.Yes, QMessageBox.Cancel) else: reply = QMessageBox.Yes if reply == QMessageBox.Yes: try: self.worker.stopWork() if platform.system().lower() in ["windows", "darwin"]: self.MF_popen.kill() else: os.killpg(os.getpgid(self.MF_popen.pid), signal.SIGTERM) self.MF_popen = None self.interrupt = True except: self.MF_popen = None self.interrupt = True if not self.workflow: QMessageBox.information( self, "ModelFinder", "<p style='line-height:25px; height:25px'>Program has been terminated!</p>") self.startButtonStatusSig.emit( [ self.pushButton, self.progressBar, "except", self.exportPath, self.qss_file, self]) def run_command(self): try: time_start = datetime.datetime.now() self.startButtonStatusSig.emit( [ self.pushButton, self.progressBar, "start", self.exportPath, self.qss_file, self]) self.run_MF() time_end = datetime.datetime.now() self.time_used = str(time_end - time_start) self.time_used_des = "Start at: %s\nFinish at: %s\nTotal time used: %s\n\n" % (str(time_start), str(time_end), self.time_used) softWare = self.comboBox_5.currentText() if softWare in ["BEAST1 (NUC)", "BEAST2 (NUC)", "BEAST (AA)"]: str1 = self.description + " " + self.parseResults() +\ "\n\nIf you use PhyloSuite, please cite:\nZhang, D., F. Gao, I. Jakovlić, H. Zou, J. Zhang, W.X. Li, and G.T. Wang, PhyloSuite: An integrated and scalable desktop platform for streamlined molecular sequence data management and evolutionary phylogenetics studies. Molecular Ecology Resources, 2020. 20(1): p. 348–355. DOI: 10.1111/1755-0998.13096.\n" \ "If you use ModelFinder, please cite:\n" + self.reference + \ "\n\nhttps://justinbagley.rbind.io/2016/10/11/setting-dna-substitution-models-beast/\nDetails for setting substitution models in %s\n" % softWare array = [[i] for i in str1.split( "\n")] + self.model2beast_des() + [[j] for j in ("\n\n" + self.time_used_des).split("\n")] self.factory.write_csv_file( self.exportPath + os.sep + "summary.csv", array, self, silence=True) else: with open(self.exportPath + os.sep + "summary.txt", "w", encoding="utf-8") as f: f.write(self.description + " " + self.parseResults() + "\n\nIf you use PhyloSuite, please cite:\nZhang, D., F. Gao, I. Jakovlić, H. Zou, J. Zhang, W.X. Li, and G.T. Wang, PhyloSuite: An integrated and scalable desktop platform for streamlined molecular sequence data management and evolutionary phylogenetics studies. Molecular Ecology Resources, 2020. 20(1): p. 348–355. DOI: 10.1111/1755-0998.13096.\n" "If you use ModelFinder, please cite:\n" + self.reference + "\n\n" + self.time_used_des) if not self.interrupt: if self.workflow: # work flow跑的 self.startButtonStatusSig.emit( [ self.pushButton, self.progressBar, "workflow stop", self.exportPath, self.qss_file, self]) self.workflow_finished.emit("finished") return self.startButtonStatusSig.emit( [ self.pushButton, self.progressBar, "stop", self.exportPath, self.qss_file, self]) else: self.startButtonStatusSig.emit( [ self.pushButton, self.progressBar, "except", self.exportPath, self.qss_file, self]) if not self.workflow: self.focusSig.emit(self.exportPath) except BaseException: self.exceptionInfo = ''.join( traceback.format_exception( *sys.exc_info())) # 捕获报错内容,只能在这里捕获,没有报错的地方无法捕获 self.exception_signal.emit(self.exceptionInfo) # 激发这个信号 self.startButtonStatusSig.emit( [ self.pushButton, self.progressBar, "except", self.exportPath, self.qss_file, self]) def guiSave(self): # Save geometry self.modelfinder_settings.setValue('size', self.size()) # self.modelfinder_settings.setValue('pos', self.pos()) for name, obj in inspect.getmembers(self): # if type(obj) is QComboBox: # this works similar to isinstance, but # missed some field... not sure why? if isinstance(obj, QComboBox): # save combobox selection to registry index = obj.currentIndex() self.modelfinder_settings.setValue(name, index) # if isinstance(obj, QLineEdit): # text = obj.text() # tooltip = obj.toolTip() # self.modelfinder_settings.setValue(name, [text, tooltip]) if isinstance(obj, QCheckBox): state = obj.isChecked() self.modelfinder_settings.setValue(name, state) if isinstance(obj, QRadioButton): state = obj.isChecked() self.modelfinder_settings.setValue(name, state) def guiRestore(self): # Restore geometry self.resize(self.modelfinder_settings.value('size', QSize(500, 500))) self.factory.centerWindow(self) # self.move(self.modelfinder_settings.value('pos', QPoint(875, 254))) for name, obj in inspect.getmembers(self): if isinstance(obj, QComboBox): if name == "comboBox_6": cpu_num = multiprocessing.cpu_count() list_cpu = ["AUTO"] + [str(i + 1) for i in range(cpu_num)] index = self.modelfinder_settings.value(name, "0") model = obj.model() obj.clear() for num, i in enumerate(list_cpu): item = QStandardItem(i) # 背景颜色 if num % 2 == 0: item.setBackground(QColor(255, 255, 255)) else: item.setBackground(QColor(237, 243, 254)) model.appendRow(item) obj.setCurrentIndex(int(index)) else: allItems = [obj.itemText(i) for i in range(obj.count())] index = self.modelfinder_settings.value(name, "0") model = obj.model() obj.clear() for num, i in enumerate(allItems): item = QStandardItem(i) # 背景颜色 if num % 2 == 0: item.setBackground(QColor(255, 255, 255)) else: item.setBackground(QColor(237, 243, 254)) model.appendRow(item) obj.setCurrentIndex(int(index)) if isinstance(obj, QLineEdit): # text, tooltip = self.modelfinder_settings.value(name, ["", ""]) # if os.path.exists(tooltip): # if os.path.exists(tooltip): # obj.setText(text) # obj.setToolTip(tooltip) if self.autoMFPath and name == "lineEdit": if os.path.exists(self.autoMFPath): obj.setText(os.path.basename(self.autoMFPath)) obj.setToolTip(self.autoMFPath) if self.partFile and name == "lineEdit_3": self.inputPartition(self.partFile) if isinstance(obj, QCheckBox): value = self.modelfinder_settings.value( name, "true") # get stored value from registry obj.setChecked( self.factory.str2bool(value)) # restore checkbox if isinstance(obj, QRadioButton): value = self.modelfinder_settings.value( name, "true") # get stored value from registry obj.setChecked( self.factory.str2bool(value)) # restore checkbox def runProgress(self, num): oldValue = self.progressBar.value() done_int = int(num) if done_int > oldValue: self.progressBar.setProperty("value", done_int) QCoreApplication.processEvents() def popupException(self, exception): print(exception) rgx = re.compile(r'Permission.+?[\'\"](.+\.csv)[\'\"]') if rgx.search(exception): csvfile = rgx.search(exception).group(1) reply = QMessageBox.critical( self, "Extract sequence", "<p style='line-height:25px; height:25px'>Please close '%s' file first!</p>" % os.path.basename( csvfile), QMessageBox.Yes, QMessageBox.Cancel) if reply == QMessageBox.Yes and platform.system().lower() == "windows": os.startfile(csvfile) else: msg = QMessageBox(self) msg.setIcon(QMessageBox.Critical) msg.setText( 'The program encountered an unforeseen problem, please report the bug at <a href="https://github.com/dongzhang0725/PhyloSuite/issues">https://github.com/dongzhang0725/PhyloSuite/issues</a> or send an email with the detailed traceback to [email protected]') msg.setWindowTitle("Error") msg.setDetailedText(exception) msg.setStandardButtons(QMessageBox.Ok) msg.exec_() def popup_IQTREE_exception(self, text): if text.endswith("redo"): reply = QMessageBox.question( self, "ModelFinder", "<p style='line-height:25px; height:25px'>%s. Do you want to rerun the analysis and overwrite all output files?</p>" % text.replace( "redo", ""), QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.Yes: self.interrupt = False self.run_with_CMD(self.command + " -redo") else: QMessageBox.information( self, "ModelFinder", "<p style='line-height:25px; height:25px'>%s</p>" % text) if "Show log" in text: self.on_pushButton_9_clicked() def closeEvent(self, event): self.guiSave() self.log_gui.close() # 关闭子窗口 self.closeSig.emit("ModelFinder", self.fetchWorkflowSetting()) # 断开showSig和closeSig的槽函数连接 try: self.showSig.disconnect() except: pass try: self.closeSig.disconnect() except: pass if self.workflow: self.ui_closeSig.emit("ModelFinder") # 自动跑的时候不杀掉程序 return if self.isRunning(): # print(self.isRunning()) reply = QMessageBox.question( self, "ModelFinder", "<p style='line-height:25px; height:25px'>ModelFinder is still running, terminate it?</p>", QMessageBox.Yes, QMessageBox.Cancel) if reply == QMessageBox.Yes: try: self.worker.stopWork() if platform.system().lower() in ["windows", "darwin"]: self.MF_popen.kill() else: os.killpg(os.getpgid(self.MF_popen.pid), signal.SIGTERM) self.MF_popen = None self.interrupt = True except: self.MF_popen = None self.interrupt = True else: event.ignore() def eventFilter(self, obj, event): # modifiers = QApplication.keyboardModifiers() name = obj.objectName() if isinstance( obj, QLineEdit): if event.type() == QEvent.DragEnter: if event.mimeData().hasUrls(): # must accept the dragEnterEvent or else the dropEvent # can't occur !!! event.accept() return True if event.type() == QEvent.Drop: files = [u.toLocalFile() for u in event.mimeData().urls()] if name == "lineEdit": if os.path.splitext(files[0])[1].upper() in [".PHY", ".PHYLIP", ".FAS", ".FASTA", ".NEX", ".NEXUS", ".NXS", ".ALN"]: base = os.path.basename(files[0]) self.lineEdit.setText(base) self.lineEdit.setToolTip(files[0]) else: QMessageBox.information( self, "ModelFinder", "<p style='line-height:25px; height:25px'>Unsupported file!</p>", QMessageBox.Ok) elif name == "lineEdit_2" and os.path.splitext(files[0])[1].upper() in [".NWK", ".NEWICK"]: base = os.path.basename(files[0]) self.lineEdit_2.setText(base) self.lineEdit_2.setToolTip(files[0]) elif name == "lineEdit_3": base = os.path.basename(files[0]) self.lineEdit_3.setText(base) self.lineEdit_3.setToolTip(files[0]) if (event.type() == QEvent.Show) and (obj == self.pushButton.toolButton.menu()): if re.search(r"\d+_\d+_\d+\-\d+_\d+_\d+", self.dir_action.text()) or self.dir_action.text() == "Output Dir: ": self.factory.sync_dir(self.dir_action) ##同步文件夹名字 menu_x_pos = self.pushButton.toolButton.menu().pos().x() menu_width = self.pushButton.toolButton.menu().size().width() button_width = self.pushButton.toolButton.size().width() pos = QPoint(menu_x_pos - menu_width + button_width, self.pushButton.toolButton.menu().pos().y()) self.pushButton.toolButton.menu().move(pos) return True # return QMainWindow.eventFilter(self, obj, event) # # 其他情况会返回系统默认的事件处理方法。 return super(ModelFinder, self).eventFilter(obj, event) # 0 def gui4Log(self): dialog = QDialog(self) dialog.resize(800, 500) dialog.setWindowTitle("Log") gridLayout = QGridLayout(dialog) horizontalLayout_2 = QHBoxLayout() label = QLabel(dialog) label.setText("Log of ModelFinder:") horizontalLayout_2.addWidget(label) spacerItem = QSpacerItem( 40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) horizontalLayout_2.addItem(spacerItem) toolButton = QToolButton(dialog) icon2 = QIcon() icon2.addPixmap( QPixmap(":/picture/resourses/interface-controls-text-wrap-512.png")) toolButton.setIcon(icon2) toolButton.setCheckable(True) toolButton.setToolTip("Use Wraps") toolButton.clicked.connect(self.setWordWrap) toolButton.setChecked(True) horizontalLayout_2.addWidget(toolButton) pushButton = QPushButton("Save to file", dialog) icon = QIcon() icon.addPixmap(QPixmap(":/picture/resourses/Save-icon.png")) pushButton.setIcon(icon) pushButton_2 = QPushButton("Close", dialog) icon = QIcon() icon.addPixmap(QPixmap(":/picture/resourses/if_Delete_1493279.png")) pushButton_2.setIcon(icon) self.textEdit_log = QTextEdit(dialog) self.textEdit_log.setReadOnly(True) gridLayout.addLayout(horizontalLayout_2, 0, 0, 1, 2) gridLayout.addWidget(self.textEdit_log, 1, 0, 1, 2) gridLayout.addWidget(pushButton, 2, 0, 1, 1) gridLayout.addWidget(pushButton_2, 2, 1, 1, 1) pushButton.clicked.connect(self.save_log_to_file) pushButton_2.clicked.connect(dialog.close) dialog.setWindowFlags( dialog.windowFlags() | Qt.WindowMinMaxButtonsHint) return dialog def addText2Log(self, text): if re.search(r"\w+", text): self.textEdit_log.append(text) with open(self.exportPath + os.sep + "PhyloSuite_ModelFinder.log", "a") as f: f.write(text + "\n") def save_log_to_file(self): content = self.textEdit_log.toPlainText() fileName = QFileDialog.getSaveFileName( self, "ModelFinder", "log", "text Format(*.txt)") if fileName[0]: with open(fileName[0], "w", encoding="utf-8") as f: f.write(content) def setWordWrap(self): button = self.sender() if button.isChecked(): button.setChecked(True) self.textEdit_log.setLineWrapMode(QTextEdit.WidgetWidth) else: button.setChecked(False) self.textEdit_log.setLineWrapMode(QTextEdit.NoWrap) def isRunning(self): '''判断程序是否运行,依赖进程是否存在来判断''' return hasattr(self, "MF_popen") and self.MF_popen and not self.interrupt def controlCodonTable(self, text): if text in ["Codon", "DNA-->AA"]: self.comboBox_9.setEnabled(True) else: self.comboBox_9.setEnabled(False) if text == "Codon": QMessageBox.information( self, "Information", "<p style='line-height:25px; height:25px'>If you choose \"Codon\" sequence type, " "only \"IQ-TREE\" is allowed as the \"Model for\" option!</p>") self.comboBox_5.setCurrentText("IQ-TREE") def run_MF(self): rgx_test_model = re.compile(r"^ModelFinder will test (\d+) \w+ models") rgx_part_model = re.compile(r"^Loading (\d+) partitions\.\.\.") rgx_finished = re.compile(r"^Date and Time:") self.totleModels = None self.totlePartitions_2 = None list_partition_names = [] # 存放partition的名字 num = 0 # partition出现的次数,当num等于2倍partition的个数的时候,就完成 is_error = False ##判断是否出了error while True: QApplication.processEvents() if self.isRunning(): try: out_line = self.MF_popen.stdout.readline().decode("utf-8", errors="ignore") except UnicodeDecodeError: out_line = self.MF_popen.stdout.readline().decode("gbk", errors="ignore") if out_line == "" and self.MF_popen.poll() is not None: break list_outline = out_line.strip().split() if rgx_test_model.search(out_line): self.totleModels = int( rgx_test_model.search(out_line).group(1)) self.progressSig.emit(5) self.workflow_progress.emit(5) elif rgx_part_model.search(out_line): self.totlePartitions_2 = 2 * \ int(rgx_part_model.search(out_line).group(1)) self.progressSig.emit(5) self.workflow_progress.emit(5) elif self.totleModels and (len(list_outline) == 7) and list_outline[0].isdigit() and list_outline[3].isdigit(): # 普通模式 model_num = int(list_outline[0]) self.progressSig.emit( 5 + model_num * 90 / self.totleModels) self.workflow_progress.emit( 5 + model_num * 90 / self.totleModels) # 下面2个是partition mode elif self.totlePartitions_2 and re.search(r"^\d", out_line) and len(out_line.strip().split("\t")) == 8: # partition模式, Subset Type Seqs Sites Infor # Invar Model Name list_partition_names.append( out_line.strip().split("\t")[-1]) elif list_partition_names and self.totlePartitions_2 and (num <= self.totlePartitions_2) and re.search(r"^ +\d+|^Optimizing", out_line): for i in list_partition_names: if i in out_line: num += 1 self.progressSig.emit( 5 + num * 90 / self.totlePartitions_2) self.workflow_progress.emit( 5 + num * 90 / self.totlePartitions_2) # print(num, self.totlePartitions_2) elif rgx_finished.search(out_line): self.progressSig.emit(100) self.workflow_progress.emit(100) text = out_line.strip() if re.search(r"\w", out_line) else "\n" self.logGuiSig.emit(text) if re.search(r"^ERROR", out_line): is_error = True # self.on_pushButton_9_clicked() # redo if re.search(r"(?m)(^Checkpoint.+?a previous run successfully finished)", out_line): self.interrupt = True text = re.search( r"(?m)(^Checkpoint.+?a previous run successfully finished)", out_line).group(1) self.iq_tree_exception.emit(text + "redo") else: break if is_error: self.interrupt = True self.iq_tree_exception.emit( "Error happened! Click <span style='font-weight:600; color:#ff0000;'>Show log</span> to see detail!") self.MF_popen = None def clear_lineEdit(self): sender = self.sender() lineEdit = sender.parent() lineEdit.setText("") lineEdit.setToolTip("") def ctrl_ratehet(self, text): if text == "IQ-TREE": self.checkBox_2.setEnabled(True) else: self.checkBox_2.setEnabled(False) if self.comboBox_3.currentText() == "Codon" and text != "IQ-TREE": QMessageBox.information( self, "Information", "<p style='line-height:25px; height:25px'>If you choose \"Codon\" sequence type, " "only \"IQ-TREE\" is allowed as the \"Model for\" option!</p>") self.comboBox_5.setCurrentText("IQ-TREE") def workflow_input(self, MSA=None, partition=None): self.lineEdit.setText("") self.lineEdit_2.setText("") self.lineEdit_3.setText("") if MSA: MSA = MSA[0] if type(MSA) == list else MSA self.lineEdit.setText(os.path.basename(MSA)) self.lineEdit.setToolTip(MSA) if partition: self.inputPartition(partition) def inputPartition(self, partition): if os.path.exists(partition): with open(partition, encoding="utf-8", errors='ignore') as f: partition_txt = f.read() iq_partition = re.search( r"(?s)\*\*\*IQ-TREE style\*\*\*(.+?)[\*|$]", partition_txt).group(1).strip() path = os.path.dirname(partition) + os.sep + "MF_partition.txt" with open(path, "w", encoding="utf-8") as f1: f1.write(iq_partition) self.lineEdit_3.setText(os.path.basename(path)) self.lineEdit_3.setToolTip(path) def popupAutoDec(self, init=False): self.init = init self.factory.popUpAutoDetect( "ModelFinder", self.workPath, self.auto_popSig, self) def popupAutoDecSub(self, popupUI): if not popupUI: if not self.init: QMessageBox.warning( self, "Warning", "<p style='line-height:25px; height:25px'>No available file detected!</p>") return if not self.init: popupUI.checkBox.setVisible(False) if popupUI.exec_() == QDialog.Accepted: widget = popupUI.listWidget_framless.itemWidget( popupUI.listWidget_framless.selectedItems()[0]) input_file, partition_file = widget.autoInputs self.workflow_input(input_file, partition_file) def fetchWorkflowSetting(self): '''* if partition mode, partition style: * seq type: * code table(if codon) * criterion * model for: ''' settings = '''<p class="title">***ModelFinder***</p>''' ifPartition = "Yes" if self.checkBox.isChecked() else "No" settings += '<p>Partition mode: <a href="self.ModelFinder_exe' \ ' factory.highlightWidgets(x.checkBox)">%s</a></p>' % ifPartition if ifPartition == "Yes": partStyle = "Edge-linked" if self.radioButton.isChecked() else "Edge-unlinked" settings += '<p>Partition style: <a href="self.ModelFinder_exe' \ ' factory.highlightWidgets(x.radioButton,x.radioButton_2)">%s</a></p>' % partStyle seq_type = self.comboBox_3.currentText() settings += '<p>Sequence type: <a href="self.ModelFinder_exe comboBox_3.showPopup()' \ ' factory.highlightWidgets(x.comboBox_3)">%s</a></p>' % seq_type threads = self.comboBox_6.currentText() settings += '<p>Threads: <a href="self.ModelFinder_exe comboBox_6.showPopup()' \ ' factory.highlightWidgets(x.comboBox_6)">%s</a></p>' % threads if seq_type == "Codon": code_table = self.comboBox_9.currentText() settings += '<p>Code table: <a href="self.ModelFinder_exe comboBox_9.showPopup()' \ ' factory.highlightWidgets(x.comboBox_9)">%s</a></p>' % code_table criterion = self.comboBox_4.currentText() settings += '<p>Criterion: <a href="self.ModelFinder_exe comboBox_4.showPopup()' \ ' factory.highlightWidgets(x.comboBox_4)">%s</a></p>' % criterion modelFor = self.comboBox_5.currentText() settings += '<p>Model used for: <a href="self.ModelFinder_exe comboBox_5.showPopup()' \ ' factory.highlightWidgets(x.comboBox_5)">%s</a></p>' % modelFor return settings def showEvent(self, event): QTimer.singleShot(100, lambda: self.showSig.emit(self)) # self.showSig.emit(self) def isFileIn(self): alignment = self.lineEdit.toolTip() if os.path.exists(alignment): return alignment else: return False def parseResults(self): # 非partition模式 list_input_model_file = [self.exportPath + os.sep + i for i in os.listdir(self.exportPath) if os.path.splitext(i)[1].upper() == ".IQTREE"] input_model_file = list_input_model_file[ 0] if list_input_model_file else "" if input_model_file: f = self.factory.read_file(input_model_file) # with open(input_model_file, encoding="utf-8", errors='ignore') as f: #"rb", model_content = f.read() # f.close() rgx_model = re.compile(r"(Best-fit model according to.+?\: )(.+)") # softWare = self.comboBox_5.currentText() # if softWare in ["BEAST1", "BEAST2"]: # prefix = rgx_model.search(model_content).group(1) # model = rgx_model.search(model_content).group(2) # version = softWare[-1] # model_split = model.split("+")[0] # model4beast, addSpecify = self.convertModel2beast(softWare, model_split) # best_model = model.replace(model_split, model4beast) # best_model = prefix + best_model + " [Additional specifications in BEAUti%s: %s]"%(version, addSpecify) \ # if addSpecify else prefix + best_model # else: best_model = rgx_model.search(model_content).group() else: best_model = "" return best_model + "." # def convertModel2beast(self, softWare, model): # '''作废''' # if softWare == "BEAST1": # model_replace = {"JC69": ["JC69", ""], # "TrN": ["TN93", ""], # "TrNef": ["TN93", 'base Frequencies set to "All Equal"'], # "K80": ["HKY", 'base Frequencies set to "All Equal"'], # "K2P": ["HKY", 'base Frequencies set to "All Equal"'], # "F81": ["GTR", 'turn off operators for all rate parameters'], # "HKY": ["HKY", ''], # "SYM": ["GTR", 'base Frequencies set to "All Equal"'], # "TIM": ["GTR", 'edit XML file by hand so that all other rates are equal to the AG rate'], # "TVM": ["GTR", 'unchecking AG rate operator'], # "TVMef": ["GTR", 'unchecking AG rate operator and setting base Frequencies to "All Equal"'], # "GTR": ["GTR", '']} # if model in model_replace: # return model_replace[model] # else: # return [model, ''] # else: # #BEAST2 # model_replace = {"JC69": ["JC69", ""], # "TrN": ["TN93", ""], # "TrNef": ["TN93", 'base Frequencies set to "All Equal"'], # "K80": ["HKY", 'base Frequencies set to "All Equal"'], # "K2P": ["HKY", 'base Frequencies set to "All Equal"'], # "F81": ["GTR", 'fix all rate parameters to 1.0 (uncheck the "estimate" box)'], # "HKY": ["HKY", ''], # "SYM": ["GTR", 'base Frequencies set to "All Equal"'], # "TIM": ["GTR", 'fix CT and AG rate parameters to 1.0 (uncheck the "estimate" box)'], # "TVM": ["GTR", 'fix the AG rate parameter to 1.0 (uncheck the "estimate" box)'], # "TVMef": ["GTR", 'fix the AG rate parameter to 1.0 (uncheck the "estimate" box), ' # 'and also set base Frequencies to "All Equal"'], # "GTR": ["GTR", '']} # if model in model_replace: # return model_replace[model] # else: # return [model, ''] def model2beast_des(self): softWare = self.comboBox_5.currentText() if softWare == "BEAST1 (NUC)": return [['Best-fit substitution model', '(Base) Model to select in BEAUti', 'Additional specifications in BEAUti'], ['JC69', 'JC69', 'None '], ['TrN', 'TN93', 'None '], ['TrNef', 'TN93', 'base Frequencies set to "All Equal"'], ['K80 (K2P)', 'HKY', 'base Frequencies set to "All Equal"'], ['F81', 'GTR', 'turn off operators for all rate parameters'], ['HKY', 'HKY', 'None'], ['SYM', 'GTR', 'base Frequencies set to "All Equal"'], ['TIM', 'GTR', 'edit XML file by hand so that all other rates are equal to the AG rate'], ['TVM', 'GTR', 'unchecking AG rate operator'], ['TVMef', 'GTR', 'unchecking AG rate operator and setting base Frequencies to "All Equal"'], ['GTR', 'GTR', 'None']] elif softWare == "BEAST2 (NUC)": return [['Best-fit substitution model', '(Base) Model to select in BEAUti 2', 'Additional specifications in BEAUti 2'], ['JC69', 'JC69', 'None '], ['TrN', 'TN93', 'None'], ['TrNef', 'TN93', 'base Frequencies set to "All Equal"'], ['K80 (K2P)', 'HKY', 'base Frequencies set to "All Equal"'], ['F81', 'GTR', 'fix all rate parameters to 1.0 (uncheck the "estimate" box)'], ['HKY', 'HKY', ' None'], ['SYM', 'GTR', 'base Frequencies set to "All Equal"'], ['TIM', 'GTR', 'fix CT and AG rate parameters to 1.0 (uncheck the "estimate" box)'], ['TVM', 'GTR', 'fix the AG rate parameter to 1.0 (uncheck the "estimate" box)'], ['TVMef', 'GTR', 'fix the AG rate parameter to 1.0 (uncheck the "estimate" box), and also set base Frequencies to "All Equal"'], ['GTR', 'GTR', ' None']] else: return [] def switchPart(self, state): if state: if not self.workflow: self.lineEdit_3.setEnabled(True) self.pushButton_22.setEnabled(True) else: if not self.workflow: self.lineEdit_3.setEnabled(False) self.pushButton_22.setEnabled(False) def getCMD(self): alignment = self.isFileIn() if alignment: # 有数据才执行 self.output_dir_name = self.factory.fetch_output_dir_name(self.dir_action) self.interrupt = False self.exportPath = self.factory.creat_dirs(self.workPath + os.sep + "ModelFinder_results" + os.sep + self.output_dir_name) ok = self.factory.remove_dir(self.exportPath, parent=self) if not ok: # 提醒是否删除旧结果,如果用户取消,就不执行 return os.chdir(self.exportPath) # 因为用了-pre,所以要先切换目录到该文件夹 inputFile = os.path.basename( shutil.copy(alignment, self.exportPath)) pre = " -pre \"%s\""%self.factory.refineName(inputFile + "." + self.comboBox_5.currentText().lower().replace("-", "_")) # use_model_for = " -m MF" if self.comboBox_5.currentText() == "IQ-TREE" else " -m TESTONLY -mset %s"%self.comboBox_5.currentText().lower() if self.comboBox_5.currentText() == "IQ-TREE": if self.checkBox_2.isChecked(): print(self.lineEdit_3.toolTip()) if self.checkBox.isChecked() and self.lineEdit_3.toolTip() and self.lineEdit_3.isEnabled(): # partition #确保勾选了partition以及输入了文件 use_model_for = " -m TESTNEWMERGEONLY" else: use_model_for = " -m TESTNEWONLY" else: if self.checkBox.isChecked() and self.lineEdit_3.toolTip() and self.lineEdit_3.isEnabled(): # partition use_model_for = " -m TESTMERGEONLY" else: use_model_for = " -m TESTONLY" elif self.comboBox_5.currentText() in ["BEAST1 (NUC)", "BEAST2 (NUC)"]: use_model_for = " -mset JC69,TrN,TrNef,K80,K2P,F81,HKY,SYM,TIM,TVM,TVMef,GTR -mrate E,G" elif self.comboBox_5.currentText() in ["BEAST (AA)"]: use_model_for = " -mset Blosum62,cpREV,JTT,mtREV,WAG,LG,Dayhoff -mrate E,G" else: use_model_for = " -m TESTONLY -mset %s" % self.comboBox_5.currentText().lower() \ if ((not self.checkBox.isChecked()) or (not self.lineEdit_3.toolTip())) else " -m TESTMERGEONLY -mset %s" % self.comboBox_5.currentText().lower() criterion_org = re.search( r"\((\w+)\)", self.comboBox_4.currentText()).group(1) criterion = " -%s" % criterion_org if criterion_org != "BIC" else "" dict_seq = {"DNA": "DNA", "Protein": "AA", "Codon": "CODON", "Binary": "BIN", "Morphology": "MORPH", "DNA-->AA": "NT2AA"} seqType = " -st %s" % dict_seq[ self.comboBox_3.currentText()] if self.comboBox_3.currentText() != "Auto detect" else "" codon_table_index = self.comboBox_9.currentText().split(" ")[0] seqType = seqType + \ codon_table_index if seqType in [ " -st CODON", " -st NT2AA"] else seqType treeFile = " -te %s" % shutil.copy(self.lineEdit_2.toolTip(), self.exportPath) if self.lineEdit_2.toolTip() else "" partitionCMD = "-spp" if self.radioButton.isChecked() else "-sp" partFile = " %s \"%s\"" % ( partitionCMD, os.path.basename(shutil.copy(self.lineEdit_3.toolTip(), self.exportPath))) if ( self.checkBox.isChecked() and self.lineEdit_3.toolTip()) else "" threads = " -nt %s" % self.comboBox_6.currentText() command = self.modelfinder_exe + " -s \"%s\"" % inputFile + \ use_model_for + criterion + seqType + \ treeFile + partFile + threads + pre # print(self.command) self.textEdit_log.clear() # 清空 # 描述 type = "Edge-linked" if self.radioButton.isChecked() else "Edge-unlinked" model = "best-fit model" if not partFile else "best-fit partition model (%s)" % type self.description = '''ModelFinder (Kalyaanamoorthy et al., 2017) was used to select the %s using %s criterion.''' % ( model, criterion_org) self.reference = "Kalyaanamoorthy, S., Minh, B.Q., Wong, T.K.F., von Haeseler, A., Jermiin, L.S., 2017. ModelFinder: fast model selection for accurate phylogenetic estimates. Nat. Methods 14, 587-589." return command else: QMessageBox.critical( self, "ModelFinder", "<p style='line-height:25px; height:25px'>Please input alignment file first!</p>") return None def showCMD(self): """ show command """ self.command = self.getCMD() if self.command: dialog = QDialog(self) dialog.resize(600, 200) dialog.setWindowTitle("Command") gridLayout = QGridLayout(dialog) label = QLabel(dialog) label.setText("Current Command:") pushButton = QPushButton("Save and run", dialog) icon = QIcon() icon.addPixmap(QPixmap(":/picture/resourses/Save-icon.png")) pushButton.setIcon(icon) pushButton_2 = QPushButton("Close", dialog) icon = QIcon() icon.addPixmap( QPixmap(":/picture/resourses/if_Delete_1493279.png")) pushButton_2.setIcon(icon) self.textEdit_cmd = QTextEdit(dialog) self.textEdit_cmd.setText(self.command) gridLayout.addWidget(label, 0, 0, 1, 2) gridLayout.addWidget(self.textEdit_cmd, 1, 0, 1, 2) gridLayout.addWidget(pushButton, 2, 0, 1, 1) gridLayout.addWidget(pushButton_2, 2, 1, 1, 1) pushButton.clicked.connect( lambda: [self.run_with_CMD(self.textEdit_cmd.toPlainText()), dialog.close()]) pushButton_2.clicked.connect(dialog.close) dialog.setWindowFlags( dialog.windowFlags() | Qt.WindowMinMaxButtonsHint) dialog.exec_() def run_with_CMD(self, cmd): self.command = cmd if self.command: self.MF_popen = self.factory.init_popen(self.command) self.factory.emitCommands(self.logGuiSig, self.command) self.worker = WorkThread(self.run_command, parent=self) self.worker.start()
class MACSE(QDialog, Ui_MACSE, object): exception_signal = pyqtSignal(str) # 定义所有类都可以使用的信号 progressSig = pyqtSignal(int) # 控制进度条 startButtonStatusSig = pyqtSignal(list) logGuiSig = pyqtSignal(str) workflow_progress = pyqtSignal(int) workflow_finished = pyqtSignal(str) # 用于输入文件后判断用 ui_closeSig = pyqtSignal(str) # 用于flowchart自动popup combobox等操作 showSig = pyqtSignal(QDialog) closeSig = pyqtSignal(str, str) # 比对完有空文件报错 emptySig = pyqtSignal(str) def __init__(self, workPath=None, focusSig=None, workflow=False, java=None, macseEXE=None, autoMFPath=None, parent=None): super(MACSE, self).__init__(parent) self.parent = parent self.workflow = workflow self.factory = Factory() self.thisPath = self.factory.thisPath self.workPath = workPath self.focusSig = focusSig self.java = java self.macseEXE = macseEXE self.autoMFPath = autoMFPath self.setupUi(self) # 保存设置 if not workflow: self.MACSE_settings = QSettings( self.thisPath + '/settings/MACSE_settings.ini', QSettings.IniFormat) else: self.MACSE_settings = QSettings( self.thisPath + '/settings/workflow_settings.ini', QSettings.IniFormat) self.MACSE_settings.beginGroup("Workflow") self.MACSE_settings.beginGroup("temporary") self.MACSE_settings.beginGroup('MACSE') # File only, no fallback to registry or or. self.MACSE_settings.setFallbacksEnabled(False) # 开始装载样式表 with open(self.thisPath + os.sep + 'style.qss', encoding="utf-8", errors='ignore') as f: self.qss_file = f.read() self.setStyleSheet(self.qss_file) # 恢复用户的设置 self.guiRestore() self.exception_signal.connect(self.popupException) self.startButtonStatusSig.connect(self.factory.ctrl_startButton_status) self.progressSig.connect(self.runProgress) self.logGuiSig.connect(self.addText2Log) self.comboBox_4.installEventFilter(self) self.comboBox_4.lineEdit().autoDetectSig.connect( self.popupAutoDec) # 自动识别可用的输入 self.comboBox_5.installEventFilter(self) self.comboBox_7.installEventFilter(self) self.log_gui = self.gui4Log() # self.pushButton_2.clicked.connect(print) self.doubleSpinBox_5.valueChanged.connect(self.judgeStopCost) self.doubleSpinBox.valueChanged.connect(self.judgeStopCost) self.doubleSpinBox_2.valueChanged.connect(self.judgeStoplrCost) self.doubleSpinBox_6.valueChanged.connect(self.judgeStoplrCost) self.comboBox_5.itemRemovedSig.connect(self.judgeCombobox5) self.checkBox_2.toggled.connect(self.controlRefineText) self.emptySig.connect(self.popup_MACSE_exception) self.interrupt = False # 给开始按钮添加菜单 menu = QMenu(self) menu.setToolTipsVisible(True) action = QAction(QIcon(":/picture/resourses/terminal-512.png"), "View | Edit command", menu, triggered=self.showCMD) self.work_action = QAction(QIcon(":/picture/resourses/work.png"), "", menu) self.work_action.triggered.connect( lambda: self.factory.swithWorkPath(self.work_action, parent=self)) self.dir_action = QAction(QIcon(":/picture/resourses/folder.png"), "Output Dir: ", menu) self.dir_action.triggered.connect( lambda: self.factory.set_direct_dir(self.dir_action, self)) menu.addAction(action) menu.addAction(self.work_action) menu.addAction(self.dir_action) self.pushButton.toolButton.setMenu(menu) self.pushButton.toolButton.menu().installEventFilter(self) self.factory.swithWorkPath(self.work_action, init=True, parent=self) # 初始化一下 ## brief demo self.label_7.clicked.connect(lambda: QDesktopServices.openUrl( QUrl( "https://dongzhang0725.github.io/dongzhang0725.github.io/documentation/#5-3-1-Brief-example" ))) @pyqtSlot() def on_pushButton_clicked(self): """ execute program """ self.command = self.fetchCommands() if self.command: self.interrupt = False self.list_pids = [] self.queue = multiprocessing.Queue() thread = int(self.comboBox_6.currentText()) if self.dict_args["refine"]: thread = thread if len( self.dict_args["alignment"]) > thread else len( self.dict_args["alignment"]) else: thread = thread if len( self.dict_args["seq_files"]) > thread else len( self.dict_args["seq_files"]) self.pool = multiprocessing.Pool(processes=thread, initializer=pool_init, initargs=(self.queue, )) # Check for progress periodically self.timer = QTimer() self.timer.timeout.connect(self.updateProcess) self.timer.start(1) self.worker = WorkThread(self.run_command, parent=self) self.worker.start() @pyqtSlot() def on_pushButton_3_clicked(self): """ sequence file """ fileNames = QFileDialog.getOpenFileNames( self, "Input sequence file", filter="Fasta Format(*.fas *.fasta *.fa *.fna);;") if fileNames[0]: self.input(self.comboBox_4, fileNames[0]) @pyqtSlot() def on_pushButton_4_clicked(self): """ less reliable sequence file """ fileNames = QFileDialog.getOpenFileNames( self, "Input sequence file", filter="Fasta Format(*.fas *.fasta *.fa *.fna);;") if fileNames[0]: self.input(self.comboBox_5, fileNames[0]) @pyqtSlot() def on_pushButton_22_clicked(self): """ alignment """ fileNames = QFileDialog.getOpenFileNames( self, "Input alignment", filter="Fasta Format(*.fas *.fasta *.fa *.fna);;") if fileNames[0]: self.input(self.comboBox_7, fileNames[0]) @pyqtSlot() def on_pushButton_9_clicked(self): """ show log """ self.log_gui.show() @pyqtSlot() def on_pushButton_2_clicked(self): """ Stop """ if self.isRunning(): if not self.workflow: reply = QMessageBox.question( self, "Confirmation", "<p style='line-height:25px; height:25px'>MACSE is still running, terminate it?</p>", QMessageBox.Yes, QMessageBox.Cancel) else: reply = QMessageBox.Yes if reply == QMessageBox.Yes: try: self.worker.stopWork() self.pool.terminate( ) # Terminate all processes in the Pool ## 删除subprocess if platform.system().lower() == "windows": for pid in self.list_pids: os.popen('taskkill /F /T /PID %s' % pid) else: for pid in self.list_pids: os.killpg(os.getpgid(pid), signal.SIGTERM) self.pool = None self.interrupt = True except: self.pool = None self.interrupt = True if not self.workflow: QMessageBox.information( self, "MACSE", "<p style='line-height:25px; height:25px'>Program has been terminated!</p>" ) self.startButtonStatusSig.emit([ self.pushButton, [self.progressBar], "except", self.dict_args["exportPath"], self.qss_file, self ]) def run_command(self): try: # 清空文件夹,放在这里方便统一报错 time_start = datetime.datetime.now() self.startButtonStatusSig.emit([ self.pushButton, self.progressBar, "start", self.dict_args["exportPath"], self.qss_file, self ]) ##进度条用 if self.dict_args["refine"]: self.dict_file_progress = { os.path.basename(file): 0 for file in self.dict_args["alignment"] } async_results = [ self.pool.apply_async(run, args=(self.dict_args, self.command, file, num)) for num, file in enumerate(self.dict_args["alignment"]) ] else: self.dict_file_progress = { os.path.basename(file): 0 for file in self.dict_args["seq_files"] } async_results = [ self.pool.apply_async(run, args=(self.dict_args, self.command, file, num)) for num, file in enumerate(self.dict_args["seq_files"]) ] self.pool.close() # 关闭进程池,防止进一步操作。如果所有操作持续挂起,它们将在工作进程终止前完成 map(ApplyResult.wait, async_results) lst_results = [r.get() for r in async_results] # 判断比对是否成功 macse_NT_results = glob.glob(self.dict_args["exportPath"] + os.sep + "*_NT.*") empty_files = [ os.path.basename(file) for file in macse_NT_results if os.stat(file).st_size == 0 ] has_error = False refine_nopt = True if (self.checkBox_2.isChecked() and self.comboBox_2.currentText() == "NONE" ) else False #fefine模式,并且不optimization if not macse_NT_results or empty_files: log = self.textEdit_log.toPlainText() list_commands = re.findall(r"Command: (.+)\n", log) last_cmd = list_commands[-1] if list_commands else "" if ("StringIndexOutOfBoundsException" in log) and ("-prog refineAlignment" in log): has_error = True self.emptySig.emit( "MACSE executes failed, click <span style=\"color:red\">Show log</span> to see details! " "<br>As you selected \"Refine\", please ensure that your input files are aligned. " "<br>You can also copy this command to terminal to debug: %s" % last_cmd) elif not refine_nopt: has_error = True # if self.checkBox_2.isChecked(): # self.emptySig.emit("MACSE executes failed, click <span style=\"color:red\">Show log</span> to see details! " # "<br>As you selected \"Refine\", please ensure that your input files are aligned. " # "<br>You can also copy this command to terminal to debug: %s" % last_cmd) # else: self.emptySig.emit( "MACSE execute failed, click <span style=\"color:red\">Show log</span> to see details! " "<br>You can also copy this command to terminal to debug: %s" % last_cmd) if not has_error: # 替换感叹号和星号,以便下游程序调用 macse_AA_results = glob.glob(self.dict_args["exportPath"] + os.sep + "*_AA.*") self.replaceSymbols(macse_NT_results + macse_AA_results) ##预防optimization为0的时候 self.progressSig.emit(100) self.workflow_progress.emit(100) time_end = datetime.datetime.now() self.time_used = str(time_end - time_start) self.time_used_des = "Start at: %s\nFinish at: %s\nTotal time used: %s\n\n" % ( str(time_start), str(time_end), self.time_used) with open(self.exportPath + os.sep + "summary.txt", "w", encoding="utf-8") as f: f.write( self.description + "\n\nIf you use PhyloSuite, please cite:\nZhang, D., Gao, F., Li, W.X., Jakovlić, I., Zou, H., Zhang, J., and Wang, G.T. (2018). PhyloSuite: an integrated and scalable desktop platform for streamlined molecular sequence data management and evolutionary phylogenetics studies. bioRxiv, doi: 10.1101/489088.\n" "If you use MACSE, please cite:\n" + self.reference + "\n\n" + self.time_used_des) if (not self.interrupt) and (not has_error): self.pool = None self.interrupt = False if self.workflow: # work flow跑的 self.startButtonStatusSig.emit([ self.pushButton, self.progressBar, "workflow stop", self.exportPath, self.qss_file, self ]) self.workflow_finished.emit("finished") return self.startButtonStatusSig.emit([ self.pushButton, self.progressBar, "stop", self.exportPath, self.qss_file, self ]) self.focusSig.emit(self.exportPath) else: self.startButtonStatusSig.emit([ self.pushButton, self.progressBar, "except", self.exportPath, self.qss_file, self ]) self.pool = None self.interrupt = False except BaseException: self.exceptionInfo = ''.join( traceback.format_exception( *sys.exc_info())) # 捕获报错内容,只能在这里捕获,没有报错的地方无法捕获 self.exception_signal.emit(self.exceptionInfo) # 激发这个信号 self.startButtonStatusSig.emit([ self.pushButton, self.progressBar, "except", self.dict_args["exportPath"], self.qss_file, self ]) self.pool = None self.interrupt = False def guiSave(self): # Save geometry self.MACSE_settings.setValue('size', self.size()) # self.MACSE_settings.setValue('pos', self.pos()) for name, obj in inspect.getmembers(self): # if type(obj) is QComboBox: # this works similar to isinstance, but # missed some field... not sure why? if isinstance(obj, QComboBox): # save combobox selection to registry index = obj.currentIndex() self.MACSE_settings.setValue(name, index) if isinstance(obj, QCheckBox): state = obj.isChecked() self.MACSE_settings.setValue(name, state) elif isinstance(obj, QSpinBox): value = obj.value() self.MACSE_settings.setValue(name, value) elif isinstance(obj, QDoubleSpinBox): float_ = obj.value() self.MACSE_settings.setValue(name, float_) elif isinstance(obj, QLineEdit): value = obj.text() self.MACSE_settings.setValue(name, value) def guiRestore(self): # Restore geometry self.resize(self.MACSE_settings.value('size', QSize(679, 693))) # self.move(self.MACSE_settings.value('pos', QPoint(875, 254))) for name, obj in inspect.getmembers(self): if isinstance(obj, QComboBox): if name == "comboBox_6": cpu_num = multiprocessing.cpu_count() list_cpu = [str(i + 1) for i in range(cpu_num)] index = self.MACSE_settings.value(name, "0") model = obj.model() obj.clear() for num, i in enumerate(list_cpu): item = QStandardItem(i) # 背景颜色 if num % 2 == 0: item.setBackground(QColor(255, 255, 255)) else: item.setBackground(QColor(237, 243, 254)) model.appendRow(item) obj.setCurrentIndex(int(index)) elif name == "comboBox_4": if self.autoMFPath and (not "_mafft." in self.autoMFPath[0]): self.input(self.comboBox_4, self.autoMFPath) self.input(self.comboBox_7, []) else: self.input(self.comboBox_4, []) elif name == "comboBox_5": self.input(self.comboBox_5, None) elif name == "comboBox_7": if self.autoMFPath and ("_mafft." in self.autoMFPath[0]): ## MAFFT的结果导入 self.checkBox_2.setChecked(True) self.input(self.comboBox_7, self.autoMFPath) self.input(self.comboBox_4, []) else: self.input(self.comboBox_7, []) else: allItems = [obj.itemText(i) for i in range(obj.count())] index = self.MACSE_settings.value(name, "0") model = obj.model() obj.clear() for num, i in enumerate(allItems): item = QStandardItem(i) # 背景颜色 if num % 2 == 0: item.setBackground(QColor(255, 255, 255)) else: item.setBackground(QColor(237, 243, 254)) model.appendRow(item) obj.setCurrentIndex(int(index)) if isinstance(obj, QCheckBox): value = self.MACSE_settings.value( name, "no setting") # get stored value from registry if value != "no setting": obj.setChecked( self.factory.str2bool(value)) # restore checkbox elif isinstance(obj, QSpinBox): ini_value = obj.value() value = self.MACSE_settings.value(name, ini_value) obj.setValue(int(value)) elif isinstance(obj, QDoubleSpinBox): ini_float_ = obj.value() float_ = self.MACSE_settings.value(name, ini_float_) obj.setValue(float(float_)) elif isinstance(obj, QLineEdit): value = self.MACSE_settings.value( name, "") # get stored value from registry if value: obj.setText(value) # restore checkbox def runProgress(self, num): oldValue = self.progressBar.value() done_int = int(num) if done_int > oldValue: self.progressBar.setProperty("value", done_int) QCoreApplication.processEvents() def popupException(self, exception): msg = QMessageBox(self) msg.setIcon(QMessageBox.Critical) msg.setText( 'The program encountered an unforeseen problem, please report the bug at <a href="https://github.com/dongzhang0725/PhyloSuite/issues">https://github.com/dongzhang0725/PhyloSuite/issues</a> or send an email with the detailed traceback to [email protected]' ) msg.setWindowTitle("Error") msg.setDetailedText(exception) msg.setStandardButtons(QMessageBox.Ok) msg.exec_() def closeEvent(self, event): self.guiSave() self.log_gui.close() # 关闭子窗口 self.closeSig.emit("MACSE", self.fetchWorkflowSetting()) # 断开showSig和closeSig的槽函数连接 try: self.showSig.disconnect() except: pass try: self.closeSig.disconnect() except: pass if self.workflow: self.ui_closeSig.emit("MACSE") # 自动跑的时候不杀掉程序 return if self.isRunning(): reply = QMessageBox.question( self, "MACSE", "<p style='line-height:25px; height:25px'>MACSE is still running, terminate it?</p>", QMessageBox.Yes, QMessageBox.Cancel) if reply == QMessageBox.Yes: try: self.worker.stopWork() self.pool.terminate( ) # Terminate all processes in the Pool ## 删除subprocess if platform.system().lower() == "windows": for pid in self.list_pids: os.popen('taskkill /F /T /PID %s' % pid) else: for pid in self.list_pids: os.killpg(os.getpgid(pid), signal.SIGTERM) self.pool = None self.interrupt = True except: self.pool = None self.interrupt = True else: event.ignore() def showEvent(self, event): QTimer.singleShot(100, lambda: self.showSig.emit(self)) def eventFilter(self, obj, event): # modifiers = QApplication.keyboardModifiers() if isinstance(obj, QComboBox): if event.type() == QEvent.DragEnter: if event.mimeData().hasUrls(): # must accept the dragEnterEvent or else the dropEvent # can't occur !!! event.accept() return True if event.type() == QEvent.Drop: files = [u.toLocalFile() for u in event.mimeData().urls()] self.input(obj, files) if (event.type() == QEvent.Show) and (obj == self.pushButton.toolButton.menu()): if re.search(r"\d+_\d+_\d+\-\d+_\d+_\d+", self.dir_action.text() ) or self.dir_action.text() == "Output Dir: ": self.factory.sync_dir(self.dir_action) ##同步文件夹名字 menu_x_pos = self.pushButton.toolButton.menu().pos().x() menu_width = self.pushButton.toolButton.menu().size().width() button_width = self.pushButton.toolButton.size().width() pos = QPoint(menu_x_pos - menu_width + button_width, self.pushButton.toolButton.menu().pos().y()) self.pushButton.toolButton.menu().move(pos) return True # return QMainWindow.eventFilter(self, obj, event) # # 其他情况会返回系统默认的事件处理方法。 return super(MACSE, self).eventFilter(obj, event) # 0 def gui4Log(self): dialog = QDialog(self) dialog.resize(800, 500) dialog.setWindowTitle("Log") gridLayout = QGridLayout(dialog) horizontalLayout_2 = QHBoxLayout() label = QLabel(dialog) label.setText("Log of MACSE:") horizontalLayout_2.addWidget(label) spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) horizontalLayout_2.addItem(spacerItem) toolButton = QToolButton(dialog) icon2 = QIcon() icon2.addPixmap( QPixmap( ":/picture/resourses/interface-controls-text-wrap-512.png")) toolButton.setIcon(icon2) toolButton.setCheckable(True) toolButton.setToolTip("Use Wraps") toolButton.clicked.connect(self.setWordWrap) toolButton.setChecked(True) horizontalLayout_2.addWidget(toolButton) pushButton = QPushButton("Save to file", dialog) icon = QIcon() icon.addPixmap(QPixmap(":/picture/resourses/Save-icon.png")) pushButton.setIcon(icon) pushButton_2 = QPushButton("Close", dialog) icon = QIcon() icon.addPixmap(QPixmap(":/picture/resourses/if_Delete_1493279.png")) pushButton_2.setIcon(icon) self.textEdit_log = QTextEdit(dialog) self.textEdit_log.setReadOnly(True) gridLayout.addLayout(horizontalLayout_2, 0, 0, 1, 2) gridLayout.addWidget(self.textEdit_log, 1, 0, 1, 2) gridLayout.addWidget(pushButton, 2, 0, 1, 1) gridLayout.addWidget(pushButton_2, 2, 1, 1, 1) pushButton.clicked.connect(self.save_log_to_file) pushButton_2.clicked.connect(dialog.close) dialog.setWindowFlags(dialog.windowFlags() | Qt.WindowMinMaxButtonsHint) return dialog def addText2Log(self, text): if re.search(r"\w+", text): self.textEdit_log.append(text) def save_log_to_file(self): content = self.textEdit_log.toPlainText() fileName = QFileDialog.getSaveFileName(self, "MACSE", "log", "text Format(*.txt)") if fileName[0]: with open(fileName[0], "w", encoding="utf-8") as f: f.write(content) def setWordWrap(self): button = self.sender() if button.isChecked(): button.setChecked(True) self.textEdit_log.setLineWrapMode(QTextEdit.WidgetWidth) else: button.setChecked(False) self.textEdit_log.setLineWrapMode(QTextEdit.NoWrap) def input(self, combobox, list_items=None): if list_items: combobox.refreshInputs(list_items) else: combobox.refreshInputs([]) if combobox == self.comboBox_5: self.comboBox_5.lineEdit().setText("Optional!") elif combobox == self.comboBox_7: self.comboBox_7.lineEdit().setText( "No input MSA (Improves previously computed alignments)") def judgeStopCost(self, value): stop_value = self.doubleSpinBox_5.value() fs_value = self.doubleSpinBox.value() if stop_value >= (2 * fs_value): QMessageBox.information( self, "MACSE", "<p style='line-height:25px; height:25px'>The value of \"stop\" should be less than twice the value of \"fs\".</p>" ) self.doubleSpinBox.setValue(30.0) self.doubleSpinBox_5.setValue(50.0) def judgeStoplrCost(self, value): stop_value = self.doubleSpinBox_6.value() fs_value = self.doubleSpinBox_2.value() if stop_value >= (2 * fs_value): QMessageBox.information( self, "MACSE", "<p style='line-height:25px; height:25px'>The value of \"stop_lr\" should be less than twice the value of \"fs_lr\".</p>" ) self.doubleSpinBox_2.setValue(10.0) self.doubleSpinBox_6.setValue(17.0) def fetchCommands(self): if self.isFileIn(): self.interrupt = False self.dict_args = {} self.dict_args[ "allowNT"] = " -allow_NT \"%s\"" % self.lineEdit.text( ) if self.lineEdit.text() else "" self.dict_args[ "alphabetAA"] = " -alphabet_AA %s" % self.comboBox.currentText( ) self.dict_args[ "ambiOFF"] = " -ambi_OFF true" if self.checkBox.isChecked( ) else "" self.dict_args["fs"] = " -fs %.1f" % self.doubleSpinBox.value( ) if self.doubleSpinBox.value() != 30.0 else "" self.dict_args[ "fs_lr"] = " -fs_lr %.1f" % self.doubleSpinBox_2.value( ) if self.doubleSpinBox_2.value() != 10.0 else "" self.dict_args[ "fs_lr_term"] = " -fs_lr_term %.1f" % self.doubleSpinBox_3.value( ) if self.doubleSpinBox_3.value() != 7.0 else "" self.dict_args[ "fs_term"] = " -fs_term %.1f" % self.doubleSpinBox_4.value( ) if self.doubleSpinBox_4.value() != 10.0 else "" self.dict_args[ "gap_ext"] = " -gap_ext %.1f" % self.doubleSpinBox_7.value( ) if self.doubleSpinBox_7.value() != 1.0 else "" self.dict_args[ "gap_ext_term"] = " -gap_ext_term %.1f" % self.doubleSpinBox_9.value( ) if self.doubleSpinBox_9.value() != 0.9 else "" self.dict_args[ "gap_op"] = " -gap_op %.1f" % self.doubleSpinBox_8.value( ) if self.doubleSpinBox_8.value() != 7.0 else "" self.dict_args[ "gap_op_term"] = " -gap_op_term %.1f" % self.doubleSpinBox_10.value( ) if self.doubleSpinBox_10.value() != 6.3 else "" self.dict_args[ "local_realign_dec"] = " -local_realign_dec %.1f" % self.doubleSpinBox_11.value( ) if self.doubleSpinBox_11.value() != 0.5 else "" self.dict_args[ "local_realign_init"] = " -local_realign_init %.1f" % self.doubleSpinBox_12.value( ) if self.doubleSpinBox_12.value() != 0.5 else "" self.dict_args[ "max_refine_iter"] = " -max_refine_iter %d" % self.spinBox.value( ) if self.spinBox.value() != -1 else "" self.dict_args["gc_def"] = " -gc_def %s" % str( self.comboBox_9.currentText()).split(" ")[0] if self.comboBox_2.currentText() == "NONE": self.dict_args["optim"] = " -optim 0" elif self.comboBox_2.currentText() == "BASIC": self.dict_args["optim"] = " -optim 1" else: self.dict_args["optim"] = "" self.dict_args["score_matrix"] = " -score_matrix %s" % str( self.comboBox_3.currentText()) self.dict_args[ "stop"] = " -stop %.1f" % self.doubleSpinBox_5.value( ) if self.doubleSpinBox_5.value() != 50.0 else "" self.dict_args[ "stop_lr"] = " -stop_lr %.1f" % self.doubleSpinBox_6.value( ) if self.doubleSpinBox_6.value() != 17.0 else "" self.dict_args["java"] = self.java self.dict_args["macseEXE"] = self.macseEXE self.dict_args["refine"] = self.checkBox_2.isChecked() self.dict_args["prog"] = "alignSequences" if not self.dict_args[ "refine"] else "refineAlignment" self.dict_args["algn_cmd"] = " -align alignment" if self.dict_args[ "refine"] else "" self.reference = "Ranwez V, Douzery EJP, Cambon C, Chantret N, Delsuc F. 2018. MACSE v2: Toolkit for the " \ "Alignment of Coding Sequences Accounting for Frameshifts and Stop Codons. Mol Biol Evol. " \ "35: 2582-2584. doi: 10.1093/molbev/msy159." prefix = "The sequences were aligned" if not self.dict_args[ "refine"] else "The alignments were refined" self.description = "%s using the codon-aware program MACSE v. 2.03 (Ranwez et al., 2018), " \ "which preserves reading frame and allows incorporation of sequencing errors or " \ "sequences with frameshifts."%prefix # self.dict_args["exception_signal"] = self.exception_signal # self.dict_args["progressSig"] = self.progressSig self.dict_args["workPath"] = self.workPath self.output_dir_name = self.factory.fetch_output_dir_name( self.dir_action) self.exportPath = self.factory.creat_dirs(self.workPath + \ os.sep + "MACSE_results" + os.sep + self.output_dir_name) self.dict_args["exportPath"] = self.exportPath ok = self.factory.remove_dir(self.exportPath, parent=self) if not ok: # 提醒是否删除旧结果,如果用户取消,就不执行 return ##拷贝输入文件进去 self.dict_args["seq_files"] = [] self.dict_args["seq_lr_files"] = [] self.dict_args["alignment"] = [] try: for seq_file in self.comboBox_4.fetchListsText(): self.dict_args["seq_files"].append( shutil.copy(seq_file, self.exportPath)) for seq_lr_file in self.comboBox_5.fetchListsText(): self.dict_args["seq_lr_files"].append( shutil.copy(seq_lr_file, self.exportPath)) for alin in self.comboBox_7.fetchListsText(): self.dict_args["alignment"].append( shutil.copy(alin, self.exportPath)) except: QMessageBox.information( self, "MACSE", "<p style='line-height:25px; height:25px'>File copying failed, please check your input files!</p>" ) return command = "\"{java}\" -jar \"{macseEXE}\" -prog {prog}{allowNT}{alphabetAA}{ambiOFF}{fs}{fs_lr}" \ "{fs_lr_term}{fs_term}{gap_ext}{gap_ext_term}{gap_op}{gap_op_term}{local_realign_dec}" \ "{local_realign_init}{max_refine_iter}{gc_def}{optim}{score_matrix}{stop}{stop_lr}" \ "{algn_cmd} -seq seqFile -seq_lr seqLrFile".format(**self.dict_args) self.textEdit_log.clear() # 清空 return command else: if self.checkBox_2.isChecked(): QMessageBox.critical( self, "MACSE", "<p style='line-height:25px; height:25px'>Please input files to \"Refine\" box first!</p>" ) else: QMessageBox.critical( self, "MACSE", "<p style='line-height:25px; height:25px'>Please input files to \"Seq.\" box first!</p>" ) @pyqtSlot(str, result=bool) def fetchPopen(self, command): self.MS_popen = self.factory.init_popen(command) return True def showCMD(self): """ show command """ self.command = self.fetchCommands() if self.command: dialog = QDialog(self) dialog.resize(600, 200) dialog.setWindowTitle("Command") gridLayout = QGridLayout(dialog) label = QLabel(dialog) label.setText("Current Command:") pushButton = QPushButton("Save and run", dialog) icon = QIcon() icon.addPixmap(QPixmap(":/picture/resourses/Save-icon.png")) pushButton.setIcon(icon) pushButton_2 = QPushButton("Close", dialog) icon = QIcon() icon.addPixmap( QPixmap(":/picture/resourses/if_Delete_1493279.png")) pushButton_2.setIcon(icon) self.textEdit_cmd = QTextEdit(dialog) self.textEdit_cmd.setText(self.command) self.textEdit_cmd.textChanged.connect(self.judgeCmdText) gridLayout.addWidget(label, 0, 0, 1, 2) gridLayout.addWidget(self.textEdit_cmd, 1, 0, 1, 2) gridLayout.addWidget(pushButton, 2, 0, 1, 1) gridLayout.addWidget(pushButton_2, 2, 1, 1, 1) pushButton.clicked.connect(lambda: [ self.run_with_CMD(self.textEdit_cmd.toPlainText()), dialog.close() ]) pushButton_2.clicked.connect(dialog.close) dialog.setWindowFlags(dialog.windowFlags() | Qt.WindowMinMaxButtonsHint) dialog.exec_() def judgeCombobox5(self): if not self.comboBox_5.fetchListsText(): self.comboBox_5.lineEdit().setText("Optional!") def run_with_CMD(self, cmd): self.command = cmd if self.command: self.interrupt = False self.list_pids = [] self.queue = multiprocessing.Queue() thread = int(self.comboBox_6.currentText()) thread = thread if len( self.dict_args["seq_files"]) > thread else len( self.dict_args["seq_files"]) self.pool = multiprocessing.Pool(processes=thread, initializer=pool_init, initargs=(self.queue, )) # Check for progress periodically self.timer = QTimer() self.timer.timeout.connect(self.updateProcess) self.timer.start(1) self.worker = WorkThread(self.run_command, parent=self) self.worker.start() def judgeCmdText(self): text = self.textEdit_cmd.toPlainText() judge_text = "%s -seq seqFile -seq_lr seqLrFile" % self.dict_args[ "algn_cmd"] if judge_text not in text: QMessageBox.information( self, "MACSE", "<p style='line-height:25px; height:25px'>\"%s\" cannot be changed!</p>" % judge_text) self.textEdit_cmd.undo() def updateProcess(self): if self.queue.empty(): return info = self.queue.get() if info[0] == "log": message = info[1] self.logGuiSig.emit(message) elif info[0] == "prog": flag, file, progress = info self.dict_file_progress[file] = progress total_progress = 0.95 * (sum(self.dict_file_progress.values()) / len(self.dict_file_progress)) self.workflow_progress.emit(total_progress) self.progressSig.emit(total_progress) elif info[0] == "popen": self.list_pids.append(info[1]) elif info[0] == "popen finished": if info[1] in self.list_pids: self.list_pids.remove(info[1]) def isRunning(self): '''判断程序是否运行,依赖进程是否存在来判断''' return hasattr(self, "pool") and self.pool and not self.interrupt def popupAutoDec(self): popupUI = self.factory.popUpAutoDetect("MACSE", self.workPath, self) if not popupUI: QMessageBox.warning( self, "Warning", "<p style='line-height:25px; height:25px'>No available file detected!</p>" ) return popupUI.checkBox.setVisible(False) if popupUI.exec_() == QDialog.Accepted: widget = popupUI.listWidget_framless.itemWidget( popupUI.listWidget_framless.selectedItems()[0]) autoInputs = widget.autoInputs self.input(self.comboBox_4, autoInputs) def fetchWorkflowSetting(self): '''* Alignment Mode * Code table(if codon mode) * strategy * export format''' settings = '''<p class="title">***MACSE (Aligns nucleotide coding sequences)***</p>''' if not \ self.checkBox_2.isChecked() else '''<p class="title">***MACSE (Improves a previously computed alignment)***</p>''' code_table = self.comboBox_9.currentText() settings += '<p>Code table: <a href="self.MACSE_exe comboBox_9.showPopup()' \ ' factory.highlightWidgets(x.comboBox_9)">%s</a></p>' % code_table Alphabet_AA = self.comboBox.currentText() settings += '<p>Alphabet_AA: <a href="self.MACSE_exe comboBox.showPopup()' \ ' factory.highlightWidgets(x.comboBox)">%s</a></p>' % Alphabet_AA fs = self.doubleSpinBox.value() settings += '<p>fs: <a href="self.MACSE_exe doubleSpinBox.setFocus() doubleSpinBox.selectAll()' \ ' factory.highlightWidgets(x.doubleSpinBox)">%s</a></p>' % fs stop = self.doubleSpinBox_5.value() settings += '<p>stop: <a href="self.MACSE_exe doubleSpinBox_5.setFocus() doubleSpinBox_5.selectAll()' \ ' factory.highlightWidgets(x.doubleSpinBox_5)">%s</a></p>' % stop local_realign_dec = self.doubleSpinBox_11.value() settings += '<p>local_realign_dec: <a href="self.MACSE_exe doubleSpinBox_11.setFocus() doubleSpinBox_11.selectAll()' \ ' factory.highlightWidgets(x.doubleSpinBox_11)">%s</a></p>' % local_realign_dec local_realign_init = self.doubleSpinBox_12.value() settings += '<p>local_realign_init: <a href="self.MACSE_exe doubleSpinBox_12.setFocus() doubleSpinBox_12.selectAll()' \ ' factory.highlightWidgets(x.doubleSpinBox_12)">%s</a></p>' % local_realign_init max_refine_iter = self.spinBox.value() settings += '<p>max_refine_iter: <a href="self.MACSE_exe spinBox.setFocus() spinBox.selectAll()' \ ' factory.highlightWidgets(x.spinBox)">%s</a></p>' % max_refine_iter optimization = self.comboBox_2.currentText() settings += '<p>Optimization: <a href="self.MACSE_exe comboBox_2.showPopup()' \ ' factory.highlightWidgets(x.comboBox_2)">%s</a></p>' % optimization score_matrix = self.comboBox_3.currentText() settings += '<p>Score matrix: <a href="self.MACSE_exe comboBox_3.showPopup()' \ ' factory.highlightWidgets(x.comboBox_3)">%s</a></p>' % score_matrix thread = self.comboBox_6.currentText() settings += '<p>Thread: <a href="self.MACSE_exe comboBox_6.showPopup()' \ ' factory.highlightWidgets(x.comboBox_6)">%s</a></p>' % thread return settings def isFileIn(self): if self.checkBox_2.isChecked(): return self.comboBox_7.count() else: return self.comboBox_4.count() def controlRefineText(self, bool_): if bool_: if not self.comboBox_7.count(): self.comboBox_7.lineEdit().switchColor( "No input MSA (Improves previously computed alignments)") else: self.comboBox_7.lineEdit().switchColor() else: self.comboBox_7.lineEdit().switchColor() def popup_MACSE_exception(self, text): QMessageBox.critical( self, "MACSE", "<p style='line-height:25px; height:25px'>%s</p>" % text) if "Show log" in text: self.on_pushButton_9_clicked() def replaceSymbols(self, list_results): ''' 替换结果里面的!和* :return: ''' for num, file in enumerate(list_results): with open(file, encoding="utf-8", errors="ignore") as f: content = f.read() name, ext = os.path.splitext(file) with open("%s_removed_chars%s" % (name, ext), "w", encoding="utf-8") as f1: f1.write(content.replace("!", "?").replace("*", "?")) self.progressSig.emit(95 + (5 * (num + 1) / len(list_results))) self.workflow_progress.emit(95 + (5 * (num + 1) / len(list_results)))