class Lg_addFiles(QDialog, Ui_addFile, object): closeGUI_signal = pyqtSignal() inputSig = pyqtSignal(list, list, bool, str) inputContentSig = pyqtSignal(str, str) exception_signal = pyqtSignal(str) progressDiologSig = pyqtSignal(int) fastaDownloadFinishedSig = pyqtSignal(str) inputFasSig = pyqtSignal(str, str) def __init__(self, exportPath=None, parent=None): super(Lg_addFiles, self).__init__(parent) self.factory = Factory() self.parent = parent self.thisPath = self.factory.thisPath self.exportPath = exportPath # 保存设置 self.addFiles_settings = QSettings( self.thisPath + '/settings/addFiles_settings.ini', QSettings.IniFormat) self.closeGUI_signal.connect(self.close) self.progressDiologSig.connect(self.runProgressDialog) # self.close() 不能在信号槽里面 self.fastaDownloadFinishedSig.connect(self.parent.setTreeViewFocus) # 开始装载样式表 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.setupUi(self) self.guiRestore() self.exception_signal.connect(self.popupException) self.interrupt = False self.label_3.clicked.connect(lambda : QDesktopServices.openUrl(QUrl( "https://dongzhang0725.github.io/dongzhang0725.github.io/documentation/#4-3-1-1-Brief-example"))) def fetSeqFromNCBI(self, id_array): batch_size = 20 count = len(id_array) download_contents = "" for start in range(0, count, batch_size): if self.interrupt: return end = min(count, start + batch_size) print("Going to download record %i to %i" % (start + 1, end)) if (start + batch_size) > count: batch_size = count - start Entrez.email = self.email if self.email else "*****@*****.**" fetch_handle = Entrez.efetch(db="nucleotide", rettype=self.rettype, retmode="text", retstart=start, retmax=batch_size, id=id_array) download_contents += fetch_handle.read() self.progressDiologSig.emit(end * 100 / count) if self.rettype == "gb": self.inputContentSig.emit( download_contents, self.outputPath) else: with open(self.outputPath + os.sep + self.fasta_file_name, "w", encoding="utf-8") as f: f.write(download_contents) self.fastaDownloadFinishedSig.emit(self.outputPath) # result_handle = Entrez.efetch( # db="nucleotide", rettype="gb", id=id_array, retmode="text") # # with open(self.exportPath + os.sep + "new.gb", "w", encoding="utf-8") as f2: # # f2.write(result_handle.read()) # self.inputContentSig.emit( # result_handle.read(), []) @pyqtSlot() def on_pushButton_clicked(self): self.outputPath = self.fetchOutputPath() if self.parent.isWorkFolder(self.outputPath, mode="gb"): files = QFileDialog.getOpenFileNames( self, "Input GenBank Files", filter="GenBank and Fasta Format (*.gb *.gbk *.gbf *.gp *.gbff *.fas *.fasta);;") if files[0]: list_gbs = [] list_fas = [] for i in files[0]: if os.path.splitext(i)[1].upper() in [".GB", ".GBK", ".GP", ".GBF", ".GBFF"]: list_gbs.append(i) elif os.path.splitext(i)[1].upper() in [".FAS", ".FASTA"]: list_fas.append(i) if list_gbs: self.inputSig.emit(list_gbs, [], False, self.outputPath) if list_fas: fasContent = "" for i in list_fas: with open(i, encoding="utf-8", errors='ignore') as f: fasContent += f.read() + "\n" self.inputFasSig.emit(fasContent, self.outputPath) self.close() self.deleteLater() del self else: files = QFileDialog.getOpenFileNames( self, "Input Files", filter="Supported Format (*.docx *.doc *.odt *.docm *.dotx *.dotm *.dot " "*.fas *.fasta *.phy *.phylip *.nex *.nxs *.nexus);;") if files[0]: self.inputSig.emit([], files[0], False, self.outputPath) self.close() self.deleteLater() del self @pyqtSlot() def on_pushButton_2_clicked(self): # download self.interrupt = False text_content = self.plainTextEdit.toPlainText() if text_content: self.outputPath = self.fetchOutputPath() self.rettype = "gb" if self.parent.isWorkFolder(self.outputPath, mode="gb") else "fasta" if self.rettype == "fasta": name, ok = QInputDialog.getText( self, 'Set output file name', 'Output file name:', text="sequence.fas") if ok: self.fasta_file_name = name + ".fas" if "." not in name else name if os.path.exists(self.outputPath + os.sep + self.fasta_file_name): reply = QMessageBox.question( self, "Concatenate sequence", "<p style='line-height:25px; height:25px'>The file exists, replace it?</p>", QMessageBox.Yes, QMessageBox.Cancel) if reply == QMessageBox.Cancel: return else: QMessageBox.information( self, "Information", "<p style='line-height:25px; height:25px'>Download canceled!</p>") return self.downloadState("start") self.progressDialog = self.factory.myProgressDialog( "Please Wait", "Downloading...", parent=self) self.progressDialog.show() self.id_array = re.split(r"\s|,", text_content) while "" in self.id_array: self.id_array.remove("") self.email = self.lineEdit.text() self.worker_download = WorkThread(self.run_command, parent=self) self.progressDialog.canceled.connect(lambda: [setattr(self, "interrupt", True), self.worker_download.stopWork(), self.progressDialog.close(), self.downloadState("stop")]) self.worker_download.start() else: QMessageBox.information( self, "Information", "<p style='line-height:25px; height:25px'>Please input ID(s) first</p>") @pyqtSlot() def on_pushButton_8_clicked(self): #search in NCBI self.close() self.parent.on_SerhNCBI_triggered() @pyqtSlot() def on_toolButton_clicked(self): info = '''To make use of NCBI's E-utilities, NCBI requires you to specify your email address with each request.<br> In case of excessive usage of the E-utilities, NCBI will attempt to contact a user at the email address provided before blocking access to the E-utilities.''' QMessageBox.information( self, "Information", "<p style='line-height:25px; height:25px'>%s</p>"%info) def run_command(self): try: self.fetSeqFromNCBI(self.id_array) self.closeGUI_signal.emit() except BaseException: self.exceptionInfo = ''.join( traceback.format_exception( *sys.exc_info())) # 捕获报错内容,只能在这里捕获,没有报错的地方无法捕获 self.exception_signal.emit(self.exceptionInfo) # self.popupException(self.exceptionInfo) # 激发这个信号 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_() self.pushButton_2.setEnabled(True) self.pushButton_2.setStyleSheet(self.qss_file) self.pushButton_2.setText("Start") def guiSave(self): # Save geometry self.addFiles_settings.setValue('size', self.size()) # self.addFiles_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, QTextBrowser): # # save combobox selection to registry # htmlText = obj.toHtml() # self.addFiles_settings.setValue(name, htmlText) def guiRestore(self): # Restore geometry self.resize(self.addFiles_settings.value('size', QSize(500, 500))) # self.move(self.addFiles_settings.value('pos', QPoint(875, 254))) for name, obj in inspect.getmembers(self): if isinstance(obj, QComboBox): if name == "comboBox": allItems = self.factory.fetchAllWorkFolders(self.exportPath) model = obj.model() obj.clear() for num, i in enumerate(allItems): if self.parent.isWorkFolder(i, mode="gb"): text = "\"%s\" in GenBank_File (GenBank format)"%os.path.basename(i) else: text = "\"%s\" in Other_File" % os.path.basename(i) item = QStandardItem(text) item.setToolTip(i) # 背景颜色 if num % 2 == 0: item.setBackground(QColor(255, 255, 255)) else: item.setBackground(QColor(237, 243, 254)) model.appendRow(item) if self.parent.isWorkFolder(self.exportPath, mode="gb"): obj.setCurrentText("\"%s\" in GenBank_File (GenBank format)"%os.path.basename(self.exportPath)) else: obj.setCurrentText("\"%s\" in Other_File" % os.path.basename(self.exportPath)) def closeEvent(self, event): self.interrupt = True if hasattr(self, "worker_download"): self.worker_download.stopWork() if hasattr(self, "progressDialog"): self.progressDialog.close() self.guiSave() def runProgressDialog(self, num): oldValue = self.progressDialog.value() done_int = int(num) if done_int > oldValue: self.progressDialog.setProperty("value", done_int) QCoreApplication.processEvents() if done_int == 100: self.progressDialog.close() def fetchOutputPath(self): index = self.comboBox.currentIndex() return self.comboBox.itemData(index, role=Qt.ToolTipRole) def downloadState(self, state): if state == "start": self.pushButton_2.setEnabled(False) # 使之失效 self.pushButton_2.setStyleSheet( 'QPushButton {color: red; background-color: rgb(219, 217, 217)}') self.pushButton_2.setText("Downloading...") elif state == "stop": self.pushButton_2.setText("Download") self.pushButton_2.setStyleSheet(self.qss_file) self.pushButton_2.setEnabled(True)
class SerhNCBI(QMainWindow, Ui_SerhNCBI, object): exception_signal = pyqtSignal(str) # 定义所有类都可以使用的信号 updateSig = pyqtSignal(list) searchSig = pyqtSignal(str) progressBarSig = pyqtSignal(int) # 控制进度条 progressDiologSig = pyqtSignal(int) ctrlItemsSig = pyqtSignal(int) inputContentSig = pyqtSignal(str, str) downloadFinished = pyqtSignal() fastaDownloadFinishedSig = pyqtSignal(str) def __init__(self, workPath=None, parent=None): super(SerhNCBI, self).__init__(parent) # self.thisPath = os.path.dirname(os.path.realpath(__file__)) self.parent = parent self.factory = Factory() self.thisPath = self.factory.thisPath self.workPath = workPath self.setupUi(self) # 保存设置 self.serhNCBI_settings = QSettings( self.thisPath + '/settings/serhNCBI_settings.ini', QSettings.IniFormat) # File only, no fallback to registry or or. self.serhNCBI_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.progressSig.connect(self.runProgress) # self.logGuiSig.connect(self.addText2Log) self.lineEdit.buttonSearch.clicked.connect(lambda: [ self.startSearch(), self.sync_completer_texts(self.lineEdit.text()) ]) self.lineEdit.buttonStop.clicked.connect(self.stopSearch) # self.lineEdit.completer().popup().show() # self.lineEdit.clicked.connect(lambda : self.lineEdit.completer().popup()) self.dict_ncbi_headers = { "GBSeq_accession-version": "ID", "GBSeq_definition": "Description", "GBSeq_organism": "Organism", "GBSeq_length": "Length", "GBSeq_update-date": "Update date", "GBSeq_taxonomy": "Taxonomy", "GBSeq_create-date": "Create date", "GBSeq_moltype": "Molecular type", "GBSeq_topology": "Topology", "GBSeq_references": "References", "GBSeq_source": "Source", "GBSeq_keywords": "Keywords", "GBSeq_project": "Project", "GBSeq_other-seqids": "Other IDs", "GBSeq_strandedness": "Strandedness", "GBSeq_comment": "Comments" } self.init_list() self.updateSig.connect(self.updateTable) self.searchSig.connect(self.ctrlSearchState) self.progressBarSig.connect(self.searchProgress) self.progressDiologSig.connect(self.runProgressDialog) self.spinBox.valueChanged.connect(self.sync_display_items) self.ctrlItemsSig.connect(self.ctrlItems) # self.downloadFinished.connect(self.downloadDone) self.fastaDownloadFinishedSig.connect(self.parent.setTreeViewFocus) self.NCBI_model = MyNCBITableModel([self.list_table_header], parent=self) self.tableView.setModel(self.NCBI_model) header = CheckBoxHeader(parent=self.tableView) header.clicked.connect(self.check_displayed) self.tableView.setHorizontalHeader(header) self.NCBI_model.checkedChanged.connect(self.ctrl_text) self.interrupt = False ##下载的状态 widget = QWidget(self) horizBox = QHBoxLayout(widget) horizBox.setContentsMargins(0, 0, 0, 0) spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.disp_check_label = QLabel( "<span style='font-weight:600; color:red;'>Searching...</span>", self) self.disp_check_gifLabel = QLabel(self) movie = QMovie(":/picture/resourses/Spinner-1s-34px.gif") self.disp_check_gifLabel.setMovie(movie) self.disp_check_progress = QProgressBar(self) self.disp_check_progress.setFixedWidth(100) movie.start() horizBox.addItem(spacerItem) horizBox.addWidget(self.disp_check_gifLabel) horizBox.addWidget(self.disp_check_label) horizBox.addWidget(self.disp_check_progress) self.statusbar.addPermanentWidget(widget) self.disp_check_label.setVisible(False) self.disp_check_gifLabel.setVisible(False) self.disp_check_progress.setVisible(False) self.lineEdit.installEventFilter(self) self.spinBox.installEventFilter(self) table_popMenu = QMenu(self) table_popMenu.setToolTipsVisible(True) OpenID = QAction(QIcon(":/seq_Viewer/resourses/field-Display.png"), "Open in NCBI webpage", self, triggered=self.openID) OpenID.setToolTip("Open sequence in NCBI webpage") table_popMenu.addAction(OpenID) def table_popmenu(qpoint): if self.tableView.indexAt(qpoint).isValid(): table_popMenu.exec_(QCursor.pos()) self.tableView.setContextMenuPolicy(Qt.CustomContextMenu) self.tableView.customContextMenuRequested.connect(table_popmenu) # self.tableView.horizontalHeader().resizeSection(1, 300) # self.tableView.horizontalHeader().resizeSection(2, 80) # ##增加check按钮 # self.check_all = QCheckBox("Check/Uncheck", self) # self.check_all.toggled.connect(self.check_displayed) # self.statusbar.addWidget(self.check_all) ## brief demo self.label_6.clicked.connect(lambda: QDesktopServices.openUrl( QUrl( "https://dongzhang0725.github.io/dongzhang0725.github.io/documentation/#4-3-3-1-Brief-example" ))) @pyqtSlot() def on_toolButton_clicked(self): info = '''To make use of NCBI's E-utilities, NCBI requires you to specify your email address with each request.<br> In case of excessive usage of the E-utilities, NCBI will attempt to contact a user at the email address provided before blocking access to the E-utilities.''' QMessageBox.information( self, "Information", "<p style='line-height:25px; height:25px'>%s</p>" % info) @pyqtSlot() def on_toolButton_2_clicked(self): #refresh self.interrupt = False self.exist_base = self.NCBI_model.rowCount(self.tableView) if not self.exist_base: return if hasattr(self, "worker_addition") and self.worker_addition.isRunning(): return if self.display_items > self.exist_base: self.worker_addition = WorkThread(self.addition_search, parent=self) self.worker_addition.start() # elif self.display_items < self.exist_base: # list_checked = self.NCBI_model.list_checked # array = self.NCBI_model.array[:self.display_items+1] # self.NCBI_model = MyNCBITableModel(array, list_checked, self.tableView) # self.tableView.setModel(self.NCBI_model) # self.tableView.update() @pyqtSlot() def on_toolButton_3_clicked(self): # download index = self.comboBox.currentIndex() self.outputPath = self.comboBox.itemData(index, role=Qt.ToolTipRole) self.rettype = "gb" if self.parent.isWorkFolder(self.outputPath, mode="gb") else "fasta" if self.rettype == "fasta": name, ok = QInputDialog.getText(self, 'Set output file name', 'Output file name:', text="sequence.fas") if ok: self.fasta_file_name = name + ".fas" if "." not in name else name if os.path.exists(self.outputPath + os.sep + self.fasta_file_name): reply = QMessageBox.question( self, "Concatenate sequence", "<p style='line-height:25px; height:25px'>The file exists, replace it?</p>", QMessageBox.Yes, QMessageBox.Cancel) if reply == QMessageBox.Cancel: return else: QMessageBox.information( self, "Information", "<p style='line-height:25px; height:25px'>Download canceled!</p>" ) return if self.NCBI_model.arraydata: self.interrupt = False self.progressDialog = self.factory.myProgressDialog( "Please Wait", "Downloading...", parent=self) self.progressDialog.show() self.worker_download = WorkThread(self.downloadSeq, parent=self) self.progressDialog.canceled.connect(lambda: [ setattr(self, "interrupt", True), self.worker_download.stopWork(), self.progressDialog.close() ]) self.worker_download.finished.connect(self.downloadDone) self.worker_download.start() else: QMessageBox.information( self, "Information", "<p style='line-height:25px; height:25px'>Please search first!</p>" ) def downloadSeq(self): try: checked_ids = self.NCBI_model.list_checked # if not checked_ids: # checked_ids = self.NCBI_model.fetchAllIDs() batch_size = 20 count = len(checked_ids) if checked_ids else self.count self.download_contents = "" for start in range(0, count, batch_size): if self.interrupt: return end = min(count, start + batch_size) print("Going to download record %i to %i" % (start + 1, end)) if (start + batch_size) > count: batch_size = count - start if not checked_ids: #下载所有序列的模式 fetch_handle = Entrez.efetch(db=self.database, rettype=self.rettype, retmode="text", retstart=start, retmax=batch_size, webenv=self.webenv, query_key=self.query_key) else: fetch_handle = Entrez.efetch(db=self.database, rettype=self.rettype, retmode="text", retstart=start, retmax=batch_size, id=checked_ids) self.download_contents += fetch_handle.read() self.progressDiologSig.emit(end * 100 / count) # index = self.comboBox.currentIndex() # filepath = self.comboBox.itemData(index, role=Qt.ToolTipRole) # self.downloadFinished.emit() except: self.exception_signal.emit(''.join( traceback.format_exception(*sys.exc_info()))) def downloadDone(self): self.progressDialog.close() QMessageBox.information( self, "Download finished", "<p style='line-height:25px; height:25px'>Done! Back to home page to view them.</p>" ) if self.rettype == "gb": self.inputContentSig.emit(self.download_contents, self.outputPath) else: with open(self.outputPath + os.sep + self.fasta_file_name, "w", encoding="utf-8") as f: f.write(self.download_contents) self.fastaDownloadFinishedSig.emit(self.outputPath) def startSearch(self): if hasattr(self, "worker") and self.worker.isRunning(): return if self.spinBox.value() == 0: self.spinBox.setValue(20) if self.lineEdit.text(): self.interrupt = False self.NCBI_model.init_table() #刷新一下table(归零) self.worker = WorkThread(self.search, parent=self) self.worker.start() else: QMessageBox.information( self, "Search in NCBI", "<p style='line-height:25px; height:25px'>Please input keywords first!</p>" ) def guiSave(self): # Save geometry self.serhNCBI_settings.setValue('size', self.size()) # self.serhNCBI_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 # text = obj.currentText() # if text: # allItems = [ # obj.itemText(i) for i in range(obj.count())] # allItems.remove(text) # sortItems = [text] + allItems # self.serhNCBI_settings.setValue(name, sortItems) if isinstance(obj, QLineEdit): if name == "lineEdit": self.serhNCBI_settings.setValue(name, self.completer_texts) if isinstance(obj, QSpinBox): if name == "spinBox": self.serhNCBI_settings.setValue(name, self.display_items) def guiRestore(self): # Restore geometry self.resize(self.serhNCBI_settings.value('size', QSize(1000, 600))) self.factory.centerWindow(self) # self.move(self.serhNCBI_settings.value('pos', QPoint(875, 254))) for name, obj in inspect.getmembers(self): if isinstance(obj, QComboBox): if name == "comboBox": allItems = self.factory.fetchAllWorkFolders(self.workPath) model = obj.model() obj.clear() for num, i in enumerate(allItems): if self.parent.isWorkFolder(i, mode="gb"): text = "\"%s\" in GenBank_File (GenBank format)" % os.path.basename( i) else: text = "\"%s\" in Other_File (Fasta format)" % os.path.basename( i) item = QStandardItem(text) item.setToolTip(i) # 背景颜色 if num % 2 == 0: item.setBackground(QColor(255, 255, 255)) else: item.setBackground(QColor(237, 243, 254)) model.appendRow(item) # obj.setCurrentText(os.path.basename(self.workPath)) if self.parent.isWorkFolder(self.workPath, mode="gb"): obj.setCurrentText( "\"%s\" in GenBank_File (GenBank format)" % os.path.basename(self.workPath)) else: obj.setCurrentText( "\"%s\" in Other_File (Fasta format)" % os.path.basename(self.workPath)) if isinstance(obj, QLineEdit): if name == "lineEdit": self.completer_texts = self.serhNCBI_settings.value( name, []) # get stored value from registry self.setLCompleter() if isinstance(obj, QSpinBox): if name == "spinBox": self.display_items = int( self.serhNCBI_settings.value( name, 100)) # get stored value from registry obj.setValue(self.display_items) def runProgressDialog(self, num): oldValue = self.progressDialog.value() done_int = int(num) if done_int > oldValue: self.progressDialog.setProperty("value", done_int) QCoreApplication.processEvents() if done_int == 100: self.progressDialog.close() def popupException(self, exception): if ("getaddrinfo failed" in exception) or ("RuntimeError" in exception): text = "<p style='line-height:25px; height:25px'>Search failed, try to check your network connection!</p>" else: text = "<p style='line-height:25px; height:25px'>The search failed for some of the sequences. As this may have been caused by a network problem, you can wait for a while and try again.<br> If the problem persists you may report it 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]</p>" msg = QMessageBox(self) msg.setIcon(QMessageBox.Critical) msg.setText(text) msg.setWindowTitle("Error") msg.setDetailedText(exception) msg.setStandardButtons(QMessageBox.Ok) msg.exec_() def closeEvent(self, event): self.interrupt = True if hasattr(self, "worker") and self.worker.isRunning(): self.worker.stopWork() if hasattr(self, "worker_addition") and self.worker_addition.isRunning(): self.worker_addition.stopWork() if hasattr(self, "worker_download") and self.worker_download.isRunning(): self.worker_download.stopWork() if hasattr(self, "progressDialog"): self.progressDialog.close() self.guiSave() # self.log_gui.close() # 关闭子窗口 def eventFilter(self, obj, event): name = obj.objectName() if event.type() == QEvent.KeyPress: # 首先得判断type if event.key() == Qt.Key_Return: if name == "lineEdit": self.sync_completer_texts(self.lineEdit.text()) self.startSearch() return True if name == "spinBox": self.on_toolButton_2_clicked() return True # return QMainWindow.eventFilter(self, obj, event) # # 其他情况会返回系统默认的事件处理方法。 return super(SerhNCBI, self).eventFilter(obj, event) # 0 def sync_completer_texts(self, text): if text and (text not in self.completer_texts): self.completer_texts.insert(0, text) if len(self.completer_texts) > 15: self.completer_texts = self.completer_texts[:15] self.guiSave() self.setLCompleter() def setLCompleter(self): comp = QCompleter(self.completer_texts) comp.setFilterMode(Qt.MatchContains) comp.setCompletionMode(QCompleter.UnfilteredPopupCompletion) # comp.popup().setStyleSheet("background-color: yellow") self.lineEdit.setCompleter(comp) def init_list(self): self.list_table_header = [ "ID", "Description", "Organism", "Length", "Update date", "Taxonomy", "Create date", "Molecular type", "Topology", "References", "Source", "Keywords", "Project", "Other IDs", "Strandedness", "Comments" ] def updateTable(self, list_): # print(array) # 测试array是新添加的list self.NCBI_model.appendTable(list_) def ctrlSearchState(self, state): if state == "searching": self.disp_check_label.setText("Searching...") self.disp_check_label.setVisible(True) self.disp_check_gifLabel.setVisible(True) self.lineEdit.buttonSearch.setDisabled(True) self.lineEdit.buttonStop.setDisabled(False) self.toolButton_2.setDisabled(True) self.toolButton_3.setDisabled(True) elif state == "fetching": self.disp_check_label.setText("Fetching...") self.disp_check_label.setVisible(True) self.disp_check_gifLabel.setVisible(True) self.disp_check_progress.setValue(0) self.disp_check_progress.setVisible(True) self.lineEdit.buttonStop.setDisabled(False) elif state == "except": self.disp_check_label.setVisible(False) self.disp_check_gifLabel.setVisible(False) self.disp_check_progress.setVisible(False) self.lineEdit.buttonSearch.setDisabled(False) self.lineEdit.buttonStop.setDisabled(True) self.toolButton_2.setDisabled(False) self.toolButton_3.setDisabled(False) elif state == "finished": self.disp_check_label.setVisible(False) self.disp_check_gifLabel.setVisible(False) self.disp_check_progress.setVisible(False) self.lineEdit.buttonSearch.setDisabled(False) self.lineEdit.buttonStop.setDisabled(True) self.toolButton_2.setDisabled(False) self.toolButton_3.setDisabled(False) ##如果啥也没搜到 self.exist_base = self.NCBI_model.rowCount(self.tableView) if not self.exist_base: QMessageBox.information( self, "Download finished", "<p style='line-height:25px; height:25px'>No items found!</p>" ) def ctrlItems(self, count): count = count if count != 0 else 20 self.spinBox.setMaximum(count) if count == 0: self.spinBox.setValue(20) self.label_4.setText("items of %s (total)" % count) self.label_2.setText("Download all (%s) sequences to:" % count) # if count < self.display_items: # self.display_items = count # self.spinBox.setValue(count) def searchProgress(self, value): oldValue = self.disp_check_progress.value() done_int = int(value) if done_int > oldValue: self.disp_check_progress.setProperty("value", done_int) QCoreApplication.processEvents() def sync_display_items(self, value): self.display_items = value def search(self): try: self.searchSig.emit("searching") self.init_list() self.ctrl_text() #文本还原 self.NCBI_model.list_checked = [] self.database = self.comboBox_2.currentText() keywords = self.lineEdit.text() email = self.lineEdit_2.text() email = email if email else "*****@*****.**" Entrez.email = email search_handle = Entrez.esearch(db=self.database, term=keywords, usehistory="y") search_results = Entrez.read(search_handle) self.webenv = search_results["WebEnv"] self.query_key = search_results["QueryKey"] self.count = int(search_results["Count"]) self.ctrlItemsSig.emit( self.count) #如果只有2个序列,self.display_items也会变成2 search_handle.close() batch_size = 20 self.searchSig.emit("fetching") # time_start = time.time() total_displayed = self.display_items if self.count < total_displayed: total_displayed = self.count for start in range(0, total_displayed, batch_size): # try: if self.interrupt: return end = min(total_displayed, start + batch_size) print("Going to download record %i to %i" % (start + 1, end)) if (start + batch_size) > total_displayed: batch_size = total_displayed - start fetch_handle = Entrez.efetch(db=self.database, retmode="xml", retstart=start, retmax=batch_size, webenv=self.webenv, query_key=self.query_key) fetch_records = Entrez.read(fetch_handle) for num, record in enumerate(fetch_records): list_ = [] for i in [ "GBSeq_accession-version", "GBSeq_definition", "GBSeq_organism", "GBSeq_length", "GBSeq_update-date", "GBSeq_taxonomy", "GBSeq_create-date", "GBSeq_moltype", "GBSeq_topology", "GBSeq_references", "GBSeq_source", "GBSeq_keywords", "GBSeq_project", "GBSeq_other-seqids", "GBSeq_strandedness", "GBSeq_comment" ]: if i in record: list_.append(str(record[i])) else: list_.append("N/A") self.updateSig.emit(list_) self.progressBarSig.emit( (start + num + 1) * 100 / total_displayed) fetch_handle.close() # except: # pass self.searchSig.emit("finished") except: self.searchSig.emit("except") self.exception_signal.emit(''.join( traceback.format_exception(*sys.exc_info()))) # time_end = time.time() # print("time:", time_end - time_start) def addition_search(self): try: total_displayed = self.display_items if self.count < total_displayed: total_displayed = self.count batch_size = 20 self.searchSig.emit("fetching") for start in range(self.exist_base, total_displayed, batch_size): if self.interrupt: break end = min(total_displayed, start + batch_size) print("Going to download record %i to %i" % (start + 1, end)) if (start + batch_size) > total_displayed: batch_size = total_displayed - start fetch_handle = Entrez.efetch(db=self.database, retmode="xml", retstart=start, retmax=batch_size, webenv=self.webenv, query_key=self.query_key) fetch_records = Entrez.read(fetch_handle) for num, record in enumerate(fetch_records): list_ = [] for i in [ "GBSeq_accession-version", "GBSeq_definition", "GBSeq_organism", "GBSeq_length", "GBSeq_update-date", "GBSeq_taxonomy", "GBSeq_create-date", "GBSeq_moltype", "GBSeq_topology", "GBSeq_references", "GBSeq_source", "GBSeq_keywords", "GBSeq_project", "GBSeq_other-seqids", "GBSeq_strandedness", "GBSeq_comment" ]: if i in record: list_.append(str(record[i])) else: list_.append("N/A") self.updateSig.emit(list_) self.progressBarSig.emit( (start - self.exist_base + num + 1) * 100 / (total_displayed - self.exist_base)) # self.progressBarSig.emit((start - self.exist_base)*100/(total_displayed - self.exist_base)) fetch_handle.close() self.searchSig.emit("finished") except: self.searchSig.emit("except") self.exception_signal.emit(''.join( traceback.format_exception(*sys.exc_info()))) def ctrl_text(self): selected_num = len(self.NCBI_model.list_checked) if selected_num: self.label_2.setText("Download selected (%d) sequences to:" % (selected_num)) else: text = "Download all (%d) sequences to:" % self.count if hasattr( self, "count") else "Download all sequences to:" self.label_2.setText(text) def openID(self): indices = self.tableView.selectedIndexes() arraydata = self.NCBI_model.arraydata ID = arraydata[indices[0].row()][0].text() url = "https://www.ncbi.nlm.nih.gov/nuccore/%s"%ID if self.comboBox_2.currentText() == "nucleotide" \ else "https://www.ncbi.nlm.nih.gov/protein/%s"%ID QDesktopServices.openUrl(QUrl(url)) def stopSearch(self): self.interrupt = True if hasattr(self, "worker") and self.worker.isRunning(): self.worker.stopWork() if hasattr(self, "worker_addition") and self.worker_addition.isRunning(): self.worker_addition.stopWork() self.searchSig.emit("except") def check_displayed(self, isCheck): if hasattr(self, "NCBI_model") and self.NCBI_model.rowCount( self.tableView): # self.NCBI_model.beginResetModel() self.NCBI_model.layoutAboutToBeChanged.emit() if isCheck: self.NCBI_model.list_checked = [ i[0].text() for i in self.NCBI_model.arraydata ] else: self.NCBI_model.list_checked = [] self.NCBI_model.layoutChanged.emit() self.ctrl_text()
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 Gblocks(QDialog, Ui_Gblocks, object): exception_signal = pyqtSignal(str) # 定义所有类都可以使用的信号 progressSig = pyqtSignal(int) # 控制进度条 startButtonStatusSig = pyqtSignal(list) logGuiSig = pyqtSignal(str) workflow_progress = pyqtSignal(int) workflow_finished = pyqtSignal(str) auto_parSig = pyqtSignal() gblocks_exception = pyqtSignal(str) # 定义所有类都可以使用的信号 # 用于flowchart自动popup combobox等操作 showSig = pyqtSignal(QDialog) closeSig = pyqtSignal(str, str) # 用于输入文件后判断用 ui_closeSig = pyqtSignal(str) ##弹出识别输入文件的信号 auto_popSig = pyqtSignal(QDialog) def __init__(self, autoInputs=None, workPath=None, focusSig=None, gb_exe=None, workflow=False, parent=None): super(Gblocks, self).__init__(parent) self.parent = parent self.function_name = "Gblocks" self.factory = Factory() self.thisPath = self.factory.thisPath self.workPath = workPath self.focusSig = focusSig if focusSig else pyqtSignal( str) # 为了方便workflow self.workflow = workflow self.gb_exe = '"' + gb_exe + '"' self.autoInputs = autoInputs self.interrupt = False self.setupUi(self) # 保存设置 if not workflow: self.gblocks_settings = QSettings( self.thisPath + '/settings/gblocks_settings.ini', QSettings.IniFormat) else: self.gblocks_settings = QSettings( self.thisPath + '/settings/workflow_settings.ini', QSettings.IniFormat) self.gblocks_settings.beginGroup("Workflow") self.gblocks_settings.beginGroup("temporary") self.gblocks_settings.beginGroup('Gblocks') # File only, no fallback to registry or or. self.gblocks_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.log_gui = self.gui4Log() self.exception_signal.connect(self.popupException) self.startButtonStatusSig.connect(self.factory.ctrl_startButton_status) self.gblocks_exception.connect(self.popup_Gblocks_exception) 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_4.itemRemovedSig.connect(self.auto_parFromfile) self.comboBox_2.currentIndexChanged[str].connect(self.comboLink) reg_ex = QRegExp(".{,5}") validator = QRegExpValidator(reg_ex, self.lineEdit_3) self.lineEdit_3.setValidator(validator) self.auto_parSig.connect(self.auto_parFromfile) # 给开始按钮添加菜单 menu = QMenu(self) menu.setToolTipsVisible(True) 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(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-6-1-Brief-example" ))) ##自动弹出识别文件窗口 self.auto_popSig.connect(self.popupAutoDecSub) @pyqtSlot() def on_pushButton_clicked(self): """ execute program """ if self.isFileIn(): # 创建输出文件夹 self.output_dir_name = self.factory.fetch_output_dir_name( self.dir_action) self.exportPath = self.factory.creat_dirs(self.workPath + os.sep + "Gblocks_results" + os.sep + self.output_dir_name) self.interrupt = False ok = self.factory.remove_dir(self.exportPath, parent=self) # 第二次运行的时候要清空 if not ok: # 提醒是否删除旧结果,如果用户取消,就不执行 return self.collect_args() self.textEdit_log.clear() # 清空log self.worker = WorkThread(self.run_command, parent=self) self.worker.start() else: QMessageBox.critical( self, "Gblocks", "<p style='line-height:25px; height:25px'>Please input files first!</p>" ) @pyqtSlot() def on_pushButton_3_clicked(self): """ open files """ fileNames = QFileDialog.getOpenFileNames( self, "Input Files", filter="NBRF/PIR Format(*.pir);;Fasta Format(*.fas *.fasta *.fsa);;" ) if fileNames[0]: self.input(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'>Gblocks 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.gb_popen.kill() else: os.killpg(os.getpgid(self.gb_popen.pid), signal.SIGTERM) self.gb_popen = None self.interrupt = True except: self.gb_popen = None self.interrupt = True if not self.workflow: QMessageBox.information( self, "Gblocks", "<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 ]) # if not self.workflow: self.input_files = [] for j in self.files: shutil.copy(j, self.exportPath) self.input_files.append(os.path.basename(j)) os.chdir(self.exportPath) # self.input_files = os.listdir(self.exportPath) for num, self.input_file in enumerate(self.input_files): if self.seq_num_differ and (self.workflow or self.runAnyway): with open(self.input_file, encoding="utf-8", errors='ignore') as f: content = f.read() self.seq_num = content.count(">") self.auto_parFromfile() # self.auto_parSig.emit() self.collect_args() # 参数刷新了 commands = "{self.gb_exe} \"{self.input_file}\" -t={self.t} -b1={self.b1} -b2={self.b2} -b3={self.b3} -b4={self.b4} -b5={self.b5}{self.b6} -s={self.s} -p={self.p} -v={self.v} -n={self.n} -u={self.u} -k={self.k} -d={self.d} -e={self.e}".format( self=self) # print(os.path.basename(self.input_file), commands) self.run_code(commands) self.progressSig.emit((num + 1) * 100 / len(self.input_files)) self.workflow_progress.emit( (num + 1) * 100 / len(self.input_files)) if self.interrupt: return # 将gb后缀放到前面 for j in os.listdir(self.exportPath): name, ext = os.path.splitext(j) if ext.endswith(self.e): # 将ext的后缀放到前面 if not os.path.exists(self.exportPath + os.sep + name + self.e + ".fasta"): os.rename( self.exportPath + os.sep + j, self.exportPath + os.sep + name + self.e + ".fasta") else: try: os.remove(self.exportPath + os.sep + j) except: pass if self.interrupt: break 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., 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 Gblocks, 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 ]) self.focusSig.emit(self.exportPath) else: self.startButtonStatusSig.emit([ self.pushButton, self.progressBar, "except", self.exportPath, self.qss_file, self ]) 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.gblocks_settings.setValue('size', self.size()) # self.gblocks_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 if name != "comboBox_4": text = obj.currentText() if text: allItems = [ obj.itemText(i) for i in range(obj.count()) ] allItems.remove(text) sortItems = [text] + allItems self.gblocks_settings.setValue(name, sortItems) if isinstance(obj, QRadioButton): state = obj.isChecked() self.gblocks_settings.setValue(name, state) if isinstance(obj, QLineEdit): text = obj.text() self.gblocks_settings.setValue(name, text) if isinstance(obj, QSpinBox): value = obj.value() self.gblocks_settings.setValue(name, value) def guiRestore(self): # Restore geometry self.resize(self.gblocks_settings.value('size', QSize(658, 500))) self.factory.centerWindow(self) # self.move(self.gblocks_settings.value('pos', QPoint(875, 254))) for name, obj in inspect.getmembers(self): if isinstance(obj, QComboBox): if name not in ["comboBox_10", "comboBox_2"]: allItems = [obj.itemText(i) for i in range(obj.count())] values = self.gblocks_settings.value(name, allItems) if name == "comboBox_4": if not self.workflow: if self.autoInputs: self.input(self.autoInputs) else: self.input(values) else: self.input([]) else: model = obj.model() obj.clear() for num, i in enumerate(values): item = QStandardItem(i) # 背景颜色 if num % 2 == 0: item.setBackground(QColor(255, 255, 255)) else: item.setBackground(QColor(237, 243, 254)) model.appendRow(item) if isinstance(obj, QRadioButton): value = self.gblocks_settings.value( name, "true") # get stored value from registry obj.setChecked( self.factory.str2bool(value)) # restore checkbox if isinstance(obj, QLineEdit): value = self.gblocks_settings.value( name, "_gb") # get stored value from registry obj.setText(value) if isinstance(obj, QSpinBox): value = self.gblocks_settings.value( name) # get stored value from registry if value: obj.setValue(int(value)) if self.radioButton_2.isChecked(): self.comboBox_13.setEnabled(True) elif self.radioButton.isChecked() or self.radioButton_3.isChecked(): self.comboBox_13.setEnabled(False) 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("Gblocks", self.fetchWorkflowSetting()) # 取消选中文字 self.radioButton.setFocus() self.spinBox_2.lineEdit().deselect() self.spinBox_3.lineEdit().deselect() # 断开showSig和closeSig的槽函数连接 try: self.showSig.disconnect() except: pass try: self.closeSig.disconnect() except: pass if self.workflow: self.ui_closeSig.emit("Gblocks") # 自动跑的时候不杀掉程序 return if self.isRunning(): # print(self.isRunning()) reply = QMessageBox.question( self, "Gblocks", "<p style='line-height:25px; height:25px'>Gblocks 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.gb_popen.kill() else: os.killpg(os.getpgid(self.gb_popen.pid), signal.SIGTERM) self.gb_popen = None self.interrupt = True except: self.gb_popen = None self.interrupt = True else: event.ignore() 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()] pick_files = [ i for i in files if os.path.splitext(i)[1].upper() in [".FASTA", ".FAS", ".PIR", ".NBRF"] ] if pick_files: self.input(files) else: QMessageBox.warning( self, "Gblocks", "<p style='line-height:25px; height:25px'>Only files in fasta, nbrf and pir formats are allowed!</p>" ) 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 super(Gblocks, self).eventFilter(obj, event) # 0 def input(self, infiles): self.comboBox_4.refreshInputs([]) # 先刷新一个空的 dict_taxon_num = {} self.seq_num_differ = False # 序列数目相同就是false self.runAnyway = False self.comboBox_10.setDisabled(False) self.comboBox_2.setDisabled(False) new_infiles = [] # 预防有时候有些文件无效 for i in infiles: if not os.path.exists(i): continue with open(i, encoding="utf-8", errors='ignore') as f: content = f.read() self.seq_num = content.count(">") if self.seq_num == 0: self.popup_Gblocks_exception( "The file %s is not in fasta format!" % (os.path.basename(i))) self.comboBox_4.refreshInputs([]) return try: if re.search(r"(?m)^\n(?!\>)", content): # 序列内部有空行 content = re.sub(r"(?m)^\n(?!\>)", "", content) with open(i, "w", encoding="utf-8") as f1: # 重新保存一下该文件 f1.write(content) else: # 多了一个\r的情况 with open(i, "w", encoding="utf-8") as f1: # 重新保存一下该文件 f1.write(content) except: pass dict_taxon_num.setdefault(self.seq_num, []).append(os.path.basename(i)) new_infiles.append(i) if not new_infiles: # 如果没有有效的文件 return if len(dict_taxon_num) > 1: self.seq_num_differ = True # 物种数目一样,如果是workflow方式,就直接都用最低配置 if len(dict_taxon_num) == 1 or self.workflow: self.auto_parFromfile() else: msg = QMessageBox(self) msg.setIcon(QMessageBox.Information) msg.setText( 'Input files contain different numbers of sequences (which implies that some genes are missing), ' 'execute Gblocks with most relaxed parameters to retain as much data as possible?' ) msg.setWindowTitle("Gblocks") list_detail = ["Seqs | Files"] + [ str(i).ljust(6) + "| " + str(dict_taxon_num[i]) for i in dict_taxon_num ] msg.setDetailedText("\n".join(list_detail)) msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No) if msg.exec_() == QMessageBox.Yes: self.comboBox_10.setDisabled(True) self.comboBox_2.setDisabled(True) self.runAnyway = True self.auto_parFromfile() else: return # 输入序列 self.comboBox_4.refreshInputs(infiles) def comboLink(self, text): if text: # 有时候清空combobox会报错 list_first_opt = [] for i in range(int(text) + 1): if i >= self.seq_num // 2 + 1: list_first_opt.append(str(i)) self.combobox_refresh(self.comboBox_10, list_first_opt) # self.comboBox_10.setCurrentIndex(0) def run_code(self, commands): startupINFO = None if platform.system().lower() == "windows": startupINFO = subprocess.STARTUPINFO() startupINFO.dwFlags |= subprocess.STARTF_USESHOWWINDOW startupINFO.wShowWindow = subprocess.SW_HIDE self.gb_popen = subprocess.Popen(commands, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, startupinfo=startupINFO) else: self.gb_popen = subprocess.Popen(commands, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, startupinfo=startupINFO, shell=True, preexec_fn=os.setsid) self.factory.emitCommands(self.logGuiSig, commands) is_error = False ##判断是否出了error while True: QApplication.processEvents() if self.isRunning(): try: out_line = self.gb_popen.stdout.readline().decode( "utf-8", errors='ignore') except UnicodeDecodeError: out_line = self.gb_popen.stdout.readline().decode( "gbk", errors='ignore') if out_line == "" and self.gb_popen.poll() is not None: break self.logGuiSig.emit(out_line.strip()) if "Execution terminated" in out_line: is_error = True else: break if is_error: self.interrupt = True self.gblocks_exception.emit( "Error happened! Click <span style='font-weight:600; color:#ff0000;'>Show log</span> to see detail!" ) self.gb_popen = None def addText2Log(self, text): if re.search(r"\w+", text): self.textEdit_log.append(text) with open(self.exportPath + os.sep + "PhyloSuite_Gblocks.log", "a") as f: f.write(text + "\n") 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 Gblocks:") 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 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 save_log_to_file(self): content = self.textEdit_log.toPlainText() fileName = QFileDialog.getSaveFileName(self, "Gblocks", "log", "text Format(*.txt)") if fileName[0]: with open(fileName[0], "w", encoding="utf-8") as f: f.write(content) def collect_args(self): self.files = self.comboBox_4.fetchListsText() dict_abbre = { "None": "n", "With Half": "h", "All": "a", "Yes": "y", "No": "n", "Save": "y", "Don't Save": "n", "Save Text": "t", "Save Short": "s" } if self.radioButton.isChecked(): self.t = "d" elif self.radioButton_2.isChecked(): self.t = "p" elif self.radioButton_3.isChecked(): self.t = "c" self.b1 = self.comboBox_10.currentText() self.b2 = self.comboBox_2.currentText() self.b3 = str(self.spinBox_2.value()) self.b4 = str(self.spinBox_3.value()) self.b5 = dict_abbre[self.comboBox_5.currentText()] self.b6 = " -b6=%s" % dict_abbre[ self.comboBox_13.currentText()] if self.t == "p" else "" self.s = dict_abbre[self.comboBox_6.currentText()] self.p = dict_abbre[self.comboBox_12.currentText()] self.v = str(self.spinBox.value()) self.n = dict_abbre[self.comboBox_7.currentText()] self.u = dict_abbre[self.comboBox_8.currentText()] self.k = dict_abbre[self.comboBox_11.currentText()] self.d = dict_abbre[self.comboBox_9.currentText()] self.e = self.lineEdit_3.text() prefix = "Ambiguously aligned fragments of %d alignments were removed in batches" % self.comboBox_4.count( ) if self.comboBox_4.count( ) > 1 else "Ambiguously aligned fragments of 1 alignment was removed" self.description = '''%s using Gblocks (Talavera and Castresana, 2007) with the following parameter settings: minimum number of sequences for a conserved/flank position (%s/%s), maximum number of contiguous non-conserved positions (%s), minimum length of a block (%s), allowed gap positions (%s).''' % ( prefix, self.b1, self.b2, self.b3, self.b4, self.comboBox_5.currentText().lower()) self.reference = "Talavera, G., Castresana, J., 2007. Improvement of phylogenies after removing divergent and ambiguously aligned blocks from protein sequence alignments. Syst. Biol. 56, 564-577." def auto_parFromfile(self): list_second_opt = [] # 设置第二个选项 self.seq_range_num = self.seq_num + 1 # 必须加1,参数才会正确 for j in range(self.seq_range_num): if j >= self.seq_num // 2 + 1: list_second_opt.append(str(j)) self.combobox_refresh(self.comboBox_2, list_second_opt) # self.comboBox_2.setCurrentIndex(0) list_first_opt = [] # 设置第一个选项 for k in range(int(self.comboBox_2.currentText()) + 1): if k >= self.seq_num // 2 + 1: list_first_opt.append(str(k)) self.combobox_refresh(self.comboBox_10, list_first_opt) def check_MSA(self, files): self.unaligns = [] self.dict_genes_alignments = OrderedDict() parsefmt = Parsefmt("") for num, eachFile in enumerate(files): geneName = os.path.splitext(os.path.basename(eachFile))[0] dict_taxon = parsefmt.readfile(eachFile) if self.factory.is_aligned(dict_taxon): self.dict_genes_alignments[geneName] = dict_taxon else: self.unaligned = True self.unaligns.append(geneName) # 补足数据 dict_warning_data = OrderedDict() dict_maxTaxa = [] for k in self.dict_genes_alignments: list_taxon = list(self.dict_genes_alignments[k].keys()) dict_maxTaxa.extend(list_taxon) list_set_dict_maxTaxa = list(set(dict_maxTaxa)) self.seq_num = len(list_set_dict_maxTaxa) self.auto_parFromfile() for i in list_set_dict_maxTaxa: lossingGene = [] for j in self.dict_genes_alignments: if i not in self.dict_genes_alignments[j]: keys = list(self.dict_genes_alignments[j].keys()) alignmentLenth = len( self.dict_genes_alignments[j][keys[0]]) self.dict_genes_alignments[j][i] = "?" * alignmentLenth lossingGene.append(j) if lossingGene: dict_warning_data[i] = lossingGene # 覆盖以前的文件 self.input_files = [] convertfmt = Convertfmt(**{ "export_path": self.exportPath, "export_fas": True }) for name in self.dict_genes_alignments: convertfmt.generate_each(self.dict_genes_alignments[name], name) self.input_files.append(convertfmt.f6) # 生成报错信息 if dict_warning_data: max_len_taxa = len(max(list(dict_warning_data), key=len)) # 要大于species的占位符 max_len_taxa = max_len_taxa if max_len_taxa > 7 else 7 list_detail = [ "Species".ljust(max_len_taxa) + " |Missing genes" ] + [ str(i).ljust(max_len_taxa) + " |" + str(dict_warning_data[i]) for i in dict_warning_data ] msg = QMessageBox(self) msg.setIcon(QMessageBox.Warning) msg.setText( "<p style='line-height:25px; height:25px'>Missing genes are replaced with '?' (see details or 'missing_genes.txt')</p>" ) msg.setWindowTitle("Warning") msg.setDetailedText("\n".join(list_detail)) msg.setStandardButtons(QMessageBox.Ok) with open(self.exportPath + os.sep + "missing_genes.txt", "w", encoding="utf-8") as f: f.write("\n".join(list_detail)) msg.exec_() # if msg.exec_() == 1024: # QDialog.Accepted: # 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) def combobox_refresh(self, widget, list_items): model = widget.model() widget.clear() for num, i in enumerate(list_items): item = QStandardItem(i) # 背景颜色 if num % 2 == 0: item.setBackground(QColor(255, 255, 255)) else: item.setBackground(QColor(237, 243, 254)) model.appendRow(item) widget.setCurrentIndex(0) def isRunning(self): '''判断程序是否运行,依赖进程是否存在来判断''' return hasattr(self, "gb_popen") and self.gb_popen and not self.interrupt def popup_Gblocks_exception(self, text): QMessageBox.information( self, "Gblocks", "<p style='line-height:25px; height:25px'>%s</p>" % text) if "Show log" in text: self.on_pushButton_9_clicked() def popupAutoDec(self, init=False): self.init = init self.factory.popUpAutoDetect("Gblocks", 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]) autoInputs = widget.autoInputs self.input(autoInputs) def fetchWorkflowSetting(self): '''* Data type * Minimum Number Of Sequences For A Conserved Position: * Minimum Number Of Sequences For A Flank Position: * Maximum Number Of Contiguous Nonconserved Positions: * Minimum Length Of A Block: * Allowed Gap Positions:''' settings = '''<p class="title">***Gblocks***</p>''' if self.radioButton.isChecked(): data_type = "Nucleotide" elif self.radioButton_2.isChecked(): data_type = "Protein" elif self.radioButton_3.isChecked(): data_type = "Codons" settings += '<p>Data type: <a href="self.Gblocks_exe factory.highlightWidgets(x.radioButton,' \ 'x.radioButton_2,x.radioButton_3)">%s</a></p>' % data_type conserve_pos = self.comboBox_10.currentText() if conserve_pos: settings += '<p>Minimum Number Of Sequences For A Conserved Position: <a href="self.Gblocks_exe ' \ 'comboBox_10.showPopup() factory.highlightWidgets(x.comboBox_10)">%s</a></p>' % conserve_pos else: settings += '<p>Minimum Number Of Sequences For A Conserved Position: <span style="font-weight:600; color:green;">auto detect when loading files</span></p>' flank_pos = self.comboBox_2.currentText() if flank_pos: settings += '<p>Minimum Number Of Sequences For A Conserved Position: <a href="self.Gblocks_exe ' \ 'comboBox_2.showPopup() factory.highlightWidgets(x.comboBox_2)">%s</a></p>' % flank_pos else: settings += '<p>Minimum Number Of Sequences For A Flank Position: <span style="font-weight:600; color:green;">auto detect when loading files</span></p>' max_pos = self.spinBox_2.value() settings += '<p>Maximum Number Of Contiguous Nonconserved Positions: ' \ '<a href="self.Gblocks_exe spinBox_2.setFocus() spinBox_2.selectAll()' \ ' factory.highlightWidgets(x.spinBox_2)">%s</a></p>' % max_pos min_block = self.spinBox_3.value() settings += '<p>Minimum Length Of A Block: <a href="self.Gblocks_exe spinBox_3.setFocus() spinBox_3.selectAll()' \ ' factory.highlightWidgets(x.spinBox_3)">%s</a></p>' % min_block allow_gap = self.comboBox_5.currentText() settings += '<p>Allowed Gap Positions: <a href="self.Gblocks_exe ' \ 'comboBox_5.showPopup() factory.highlightWidgets(x.comboBox_5)">%s</a></p>' % allow_gap return settings def showEvent(self, event): QTimer.singleShot(100, lambda: self.showSig.emit(self)) # self.showSig.emit(self) def isFileIn(self): return self.comboBox_4.count()
class HmmCleaner(QDialog, Ui_HmmCleaner, object): exception_signal = pyqtSignal(str) # 定义所有类都可以使用的信号 progressSig = pyqtSignal(int) # 控制进度条 startButtonStatusSig = pyqtSignal(list) logGuiSig = pyqtSignal(str) HmmCleaner_exception = pyqtSignal(str) workflow_progress = pyqtSignal(int) workflow_finished = pyqtSignal(str) # 用于输入文件后判断用 ui_closeSig = pyqtSignal(str) # 用于flowchart自动popup combobox等操作 showSig = pyqtSignal(QDialog) closeSig = pyqtSignal(str, str) def __init__(self, workPath=None, HmmCleanerPath=None, autoInputs=None, perl=None, focusSig=None, workflow=None, parent=None): super(HmmCleaner, self).__init__(parent) self.parent = parent self.workflow = workflow self.factory = Factory() self.thisPath = self.factory.thisPath self.workPath = workPath self.focusSig = focusSig self.autoInputs = autoInputs self.HmmCleanerPath = HmmCleanerPath self.perl = perl self.setupUi(self) # 保存设置 if not workflow: self.HmmCleaner_settings = QSettings( self.thisPath + '/settings/HmmCleaner_settings.ini', QSettings.IniFormat) else: self.HmmCleaner_settings = QSettings( self.thisPath + '/settings/workflow_settings.ini', QSettings.IniFormat) self.HmmCleaner_settings.beginGroup("Workflow") self.HmmCleaner_settings.beginGroup("temporary") self.HmmCleaner_settings.beginGroup('HmmCleaner') # File only, no fallback to registry or or. self.HmmCleaner_settings.setFallbacksEnabled(False) # print(self.HmmCleaner_settings.childGroups()) # self.factory.settingsGroup2Group(self.HmmCleaner_settings, "PCGs", "temporary") # 开始装载样式表 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.interrupt = False 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.log_gui = self.gui4Log() self.HmmCleaner_exception.connect(self.popup_HmmCleaner_exception) self.checkBox_4.toggled.connect(self.popupAliWarning) # 给开始按钮添加菜单 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-5-1-Brief-example" ))) @pyqtSlot() def on_pushButton_clicked(self): """ execute program """ self.command = self.fetchCommands() if self.command: self.interrupt = False self.error_has_shown = False # 保证只报一次错 self.list_pids = [] self.queue = multiprocessing.Queue() thread = int(self.comboBox_6.currentText()) thread = thread if len( self.dict_args["inputFiles"]) > thread else len( self.dict_args["inputFiles"]) thread = 1 if not self.dict_args[ "inputFiles"] else thread # compare的情况 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_9_clicked(self): """ show log """ self.log_gui.show() @pyqtSlot() def on_pushButton_3_clicked(self): """ alignment file """ fileNames = QFileDialog.getOpenFileNames(self, "Input alignment file") if fileNames[0]: self.input(fileNames[0]) @pyqtSlot() def on_pushButton_2_clicked(self, quiet=False): """ Stop """ if self.isRunning(): if (not self.workflow) and (not quiet): reply = QMessageBox.question( self, "Confirmation", "<p style='line-height:25px; height:25px'>HmmCleaner 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) and (not quiet): QMessageBox.information( self, "HmmCleaner", "<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 ]) ##进度条用 # 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)) for file in self.dict_args["inputFiles"] ] self.totalFileNum = len(self.dict_args["inputFiles"]) self.finishedFileNum = 0 #进度条用 self.pool.close() # 关闭进程池,防止进一步操作。如果所有操作持续挂起,它们将在工作进程终止前完成 map(ApplyResult.wait, async_results) lst_results = [r.get() for r in async_results] # 判断比对是否成功 HmmCleaner_results = glob.glob(self.exportPath + os.sep + "*_hmm.*") empty_files = [ os.path.basename(file) for file in HmmCleaner_results if os.stat(file).st_size == 0 ] has_error = False if (not self.checkBox_3.isChecked()) and (not HmmCleaner_results or empty_files): # log_only不管 has_error = True log = self.textEdit_log.toPlainText() if "Can't locate" in log: self.HmmCleaner_exception.emit( "HmmCleaner executes failed, it seems the " "<a href=\"https://cpandeps.grinnz.com/?dist=Bio-MUST-Apps-HmmCleaner&phase=build&perl_version=v5.30.0&style=table\">" "<span style=\" font-size:12pt; text-decoration: underline; color:#0000ff;\">dependencies" "</span></a>(e.g. Bio-FastParsers) of HmmCleaner are not installed, " "click <span style=\"color:red\">Show log</span> to see details! <br> You can install HmmCleaner following this " \ "<a href=\"https://dongzhang0725.github.io/dongzhang0725.github.io/PhyloSuite-demo/how-to-configure-plugins/#2-4-HmmCleaner-configuration\">" \ "<span style=\" font-size:12pt; text-decoration: underline; color:#0000ff;\">instruction</a>." \ "</span>") else: list_commands = re.findall(r"Command: (.+)\n", log) last_cmd = list_commands[-1] if list_commands else "" self.HmmCleaner_exception.emit( "HmmCleaner executes failed, click <span style=\"color:red\">Show log</span> to see details! " "You can also copy this command to terminal to debug: %s" % last_cmd) 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 HmmCleaner, 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.HmmCleaner_settings.setValue('size', self.size()) # self.HmmCleaner_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.HmmCleaner_settings.setValue(name, index) if isinstance(obj, QCheckBox): state = obj.isChecked() self.HmmCleaner_settings.setValue(name, state) elif isinstance(obj, QDoubleSpinBox): float_ = obj.value() self.HmmCleaner_settings.setValue(name, float_) def guiRestore(self): # Restore geometry self.resize(self.HmmCleaner_settings.value('size', QSize(490, 380))) # self.move(self.HmmCleaner_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.HmmCleaner_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": self.input(self.autoInputs) else: allItems = [obj.itemText(i) for i in range(obj.count())] index = self.HmmCleaner_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)) item.setToolTip(i) model.appendRow(item) obj.setCurrentIndex(int(index)) elif isinstance(obj, QCheckBox): value = self.HmmCleaner_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, QDoubleSpinBox): ini_float_ = obj.value() float_ = self.HmmCleaner_settings.value(name, ini_float_) obj.setValue(float(float_)) 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("HmmCleaner", self.fetchWorkflowSetting()) # 断开showSig和closeSig的槽函数连接 try: self.showSig.disconnect() except: pass try: self.closeSig.disconnect() except: pass if self.workflow: self.ui_closeSig.emit("HmmCleaner") # 自动跑的时候不杀掉程序 return if self.isRunning(): reply = QMessageBox.question( self, "HmmCleaner", "<p style='line-height:25px; height:25px'>HmmCleaner 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(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(HmmCleaner, 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 HmmCleaner:") 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, "HmmCleaner", "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, list_items=None): if list_items: self.comboBox_4.refreshInputs(list_items) else: self.comboBox_4.refreshInputs([]) 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 isRunning(self): '''判断程序是否运行,依赖进程是否存在来判断''' return hasattr(self, "pool") and self.pool and not self.interrupt def run_with_CMD(self, cmd): self.command = cmd if self.command: self.interrupt = False self.error_has_shown = False self.list_pids = [] self.queue = multiprocessing.Queue() thread = int(self.comboBox_6.currentText()) thread = thread if len( self.dict_args["inputFiles"]) > thread else len( self.dict_args["inputFiles"]) thread = 1 if not self.dict_args[ "inputFiles"] else thread # compare的情况 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() if " $alignment$" not in text: QMessageBox.information( self, "HmmCleaner", "<p style='line-height:25px; height:25px'>\"$alignment$\" cannot be changed!</p>" ) self.textEdit_cmd.undo() def fetchCommands(self): if self.isFileIn(): self.interrupt = False self.error_has_shown = False self.dict_args = {} 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 + "HmmCleaner_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 costs = "\"%.2f\" \"%.2f\" \"%.2f\" \"%.2f\"" % ( self.doubleSpinBox.value(), self.doubleSpinBox_2.value(), self.doubleSpinBox_3.value(), self.doubleSpinBox_4.value()) self.dict_args[ "costs"] = " -costs %s" % costs if costs != '"-0.15" "-0.08" "0.15" "0.45"' else "" self.dict_args["noX"] = " --noX" if self.checkBox_5.isChecked( ) else "" self.dict_args[ "specificity"] = " --specificity" if self.checkBox.isChecked( ) else "" self.dict_args["large"] = " --large" if self.checkBox_2.isChecked( ) else "" self.dict_args[ "log_only"] = " --log_only" if self.checkBox_3.isChecked( ) else "" self.dict_args["ali"] = " --ali" if self.checkBox_4.isChecked( ) else "" self.dict_args[ "changeID"] = " --changeID" if self.checkBox_6.isChecked( ) else "" self.dict_args[ "profile"] = " -profile=%s" % self.comboBox_3.currentText() self.dict_args[ "symfrac"] = " -symfrac %s" % self.doubleSpinBox_6.value( ) if self.doubleSpinBox_6.value() != 0.50 else "" self.dict_args[ "verbosity"] = " -v=%s" % self.comboBox_5.currentText( ) if self.comboBox_5.currentText() != "0" else "" self.dict_args[ "perl"] = "\"%s\" " % self.perl if self.HmmCleanerPath != "HmmCleaner.pl" else "" #如果是用的环境变量的脚本,就不用perl self.dict_args["HmmCleaner"] = self.HmmCleanerPath ##输入文件 self.dict_args["inputFiles"] = [] try: for aln_file in self.comboBox_4.fetchListsText(): with open(aln_file, encoding="utf-8", errors="ignore") as f: content = f.read() copy_path = self.exportPath + os.sep + os.path.basename( aln_file) with open(copy_path, "w", encoding="utf-8") as f1: f1.write(content.replace("\r\n", "\n")) self.dict_args["inputFiles"].append(copy_path) except: QMessageBox.information( self, "HmmCleaner", "<p style='line-height:25px; height:25px'>File copying failed, please check your input files!</p>" ) command = "{perl}\"{HmmCleaner}\" $alignment${costs}{noX}{specificity}{large}{log_only}{ali}" \ "{changeID}{profile}{symfrac}{verbosity}".format(**self.dict_args) self.reference = "Di Franco A, Poujol R, Baurain D, Philippe H. 2019. Evaluating the usefulness of " \ "alignment filtering methods to reduce the impact of errors on evolutionary " \ "inferences. BMC Evol Biol. 19: 21. doi: 10.1186/s12862-019-1350-2." cmd_used = "{costs}{specificity}{large}{log_only}{ali}{changeID}{profile}{symfrac}".format( **self.dict_args).strip() self.description = "Low similarity segments within the alignment were removed with HmmCleaner (Di Franco et al., 2019) using \"%s\" command." % cmd_used self.textEdit_log.clear() # 清空 return command else: QMessageBox.critical( self, "HmmCleaner", "<p style='line-height:25px; height:25px'>Please input files first!</p>" ) 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": self.finishedFileNum += 1 if not self.interrupt: self.progressSig.emit(self.finishedFileNum * 100 / self.totalFileNum) self.workflow_progress.emit(self.finishedFileNum * 100 / self.totalFileNum) elif info[0] == "popen": self.list_pids.append(info[1]) elif info[0] == "error": self.on_pushButton_2_clicked(quiet=True) #杀掉进程 self.HmmCleaner_exception.emit( "Error happened! Click <span style='font-weight:600; color:#ff0000;'>Show log</span> to see detail!" ) self.error_has_shown = True elif info[0] == "popen finished": if info[1] in self.list_pids: self.list_pids.remove(info[1]) def popup_HmmCleaner_exception(self, text): if not self.error_has_shown: QMessageBox.critical( self, "HmmCleaner", "<p style='line-height:25px; height:25px'>%s</p>" % text) if "Show log" in text: self.on_pushButton_9_clicked() def popupAliWarning(self, bool_): if bool_: QMessageBox.warning( self, "HmmCleaner", "<p style='line-height:25px; height:25px'>\"Ali\" format cannot be used by the downstream programs " "(e.g. IQ-TREE), please uncheck it if you are going to use this result for other functions.</p>" ) def popupAutoDec(self): popupUI = self.factory.popUpAutoDetect("HmmCleaner", 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(autoInputs) def fetchWorkflowSetting(self): '''* Alignment Mode * Code table(if codon mode) * strategy * export format''' settings = '''<p class="title">***HmmCleaner***</p>''' c1 = self.doubleSpinBox.value() c2 = self.doubleSpinBox_2.value() c3 = self.doubleSpinBox_3.value() c4 = self.doubleSpinBox_4.value() settings += '<p>costs: \"<a href="self.HmmCleaner_exe doubleSpinBox.setFocus() doubleSpinBox.selectAll() ' \ 'factory.highlightWidgets(x.doubleSpinBox)">%s</a>\" ' \ '\"<a href="self.HmmCleaner_exe doubleSpinBox_2.setFocus() doubleSpinBox_2.selectAll() ' \ 'factory.highlightWidgets(x.doubleSpinBox_2)">%s</a>\" ' \ '\"<a href="self.HmmCleaner_exe doubleSpinBox_3.setFocus() doubleSpinBox_3.selectAll() ' \ 'factory.highlightWidgets(x.doubleSpinBox_3)">%s</a>\" ' \ '\"<a href="self.HmmCleaner_exe doubleSpinBox_4.setFocus() doubleSpinBox_4.selectAll() ' \ 'factory.highlightWidgets(x.doubleSpinBox_4)">%s</a>\"</p>' % (c1, c2, c3, c4) verbosity = self.comboBox_5.currentText() settings += '<p>verbosity: <a href="self.HmmCleaner_exe comboBox_5.showPopup()' \ ' factory.highlightWidgets(x.comboBox_5)">%s</a></p>' % verbosity profile = self.comboBox_3.currentText() settings += '<p>profile: <a href="self.HmmCleaner_exe comboBox_3.showPopup()' \ ' factory.highlightWidgets(x.comboBox_3)">%s</a></p>' % profile thread = self.comboBox_6.currentText() settings += '<p>Thread: <a href="self.HmmCleaner_exe comboBox_6.showPopup()' \ ' factory.highlightWidgets(x.comboBox_6)">%s</a></p>' % thread specificity = "Yes" if self.checkBox.isChecked() else "No" settings += '<p>specificity: <a href="self.HmmCleaner_exe' \ ' factory.highlightWidgets(x.checkBox)">%s</a></p>' % specificity large = "Yes" if self.checkBox_2.isChecked() else "No" settings += '<p>large: <a href="self.HmmCleaner_exe' \ ' factory.highlightWidgets(x.checkBox_2)">%s</a></p>' % large changeID = "Yes" if self.checkBox_6.isChecked() else "No" settings += '<p>changeID: <a href="self.HmmCleaner_exe' \ ' factory.highlightWidgets(x.checkBox_6)">%s</a></p>' % changeID noX = "Yes" if self.checkBox_5.isChecked() else "No" settings += '<p>noX: <a href="self.HmmCleaner_exe' \ ' factory.highlightWidgets(x.checkBox_5)">%s</a></p>' % noX return settings def isFileIn(self): return self.comboBox_4.count()
class ExtractGB(QDialog, Ui_Extractor, object): exception_signal = pyqtSignal(str) # 定义所有类都可以使用的信号 progressSig = pyqtSignal(int) # 控制进度条 threadFinished = pyqtSignal() startButtonStatusSig = pyqtSignal(list) def __init__(self, gb_files=None, list_names=None, workPath=None, totleID=None, clearFolderSig=None, focusSig=None, parent=None): super(ExtractGB, self).__init__(parent) self.parent = parent self.function_name = "Extraction" self.setupUi(self) self.factory = Factory() self.thisPath = self.factory.thisPath self.gb_files = gb_files self.workPath = workPath self.totleID = totleID self.clearFolderSig = clearFolderSig self.list_names = list_names self.focusSig = focusSig self.installEventFilter(self) self.dict_icon = { "rectangle": ":/itol_domain/resourses/itol/re.png", "horizontal hexagon": ":/itol_domain/resourses/itol/hh.png", "vertical hexagon": ":/itol_domain/resourses/itol/hv.png", "ellipse": ":/itol_domain/resourses/itol/el.png", "rhombus (diamond)": ":/itol_domain/resourses/itol/di.png", "right pointing triangle": ":/itol_domain/resourses/itol/tr.png", "left pointing triangle": ":/itol_domain/resourses/itol/tl.png", "left pointing pentagram": ":/itol_domain/resourses/itol/pl.png", "right pointing pentagram": ":/itol_domain/resourses/itol/pr.png", "up pointing pentagram": ":/itol_domain/resourses/itol/pu.png", "down pointing pentagram": ":/itol_domain/resourses/itol/pd.png", "octagon": ":/itol_domain/resourses/itol/oc.png", "rectangle (gap)": ":/itol_domain/resourses/itol/gp.png" } self.dict_shape = { "rectangle": "RE", "horizontal hexagon": "HH", "vertical hexagon": "HV", "ellipse": "EL", "rhombus (diamond)": "DI", "right pointing triangle": "TR", "left pointing triangle": "TL", "left pointing pentagram": "PL", "right pointing pentagram": "PR", "up pointing pentagram": "PU", "down pointing pentagram": "PD", "octagon": "OC", "rectangle (gap)": "GP" } self.showOn() self.extractGB_settings = QSettings( self.thisPath + '/settings/extractGB_settings.ini', QSettings.IniFormat) # File only, no fallback to registry or or. self.extractGB_settings.setFallbacksEnabled(False) self.settings_ini = QSettings( self.thisPath + '/settings/setting_settings.ini', QSettings.IniFormat) self.settings_ini.setFallbacksEnabled(False) # 保存主界面设置 self.data_settings = QSettings( self.factory.workPlaceSettingPath + os.sep + 'data_settings.ini', QSettings.IniFormat) # File only, no fallback to registry or or. self.data_settings.setFallbacksEnabled(False) # 恢复用户的设置 self.guiRestore() # 开始装载样式表 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.progressSig.connect(self.runProgress) self.exception_signal.connect(self.popupException) self.startButtonStatusSig.connect(self.factory.ctrl_startButton_status) self.tableWidget_2.installEventFilter(self) self.table_popMenu = QMenu(self) self.Copy = QAction("Copy", self, statusTip="Copy color(s)", shortcut="Ctrl+C", triggered=self.copyColor) self.Cut = QAction("Cut", self, statusTip="Cut color(s)", shortcut="Ctrl+X", triggered=self.cutColor) self.Paste = QAction("Paste", self, statusTip="Paste color(s)", shortcut="Ctrl+V", triggered=self.pasteColor) self.remove = QAction("Delete", self, shortcut=QKeySequence.Delete, statusTip="Remove color(s)", triggered=self.on_pushButton_9_clicked) self.table_popMenu.addAction(self.Copy) self.table_popMenu.addAction(self.Cut) self.table_popMenu.addAction(self.Paste) self.table_popMenu.addAction(self.remove) self.tableWidget_2.setContextMenuPolicy(Qt.CustomContextMenu) self.tableWidget_2.customContextMenuRequested.connect( lambda x: self.table_popMenu.exec_(QCursor.pos())) self.comboBox_6.activated[str].connect(self.switchSeqType) # self.checkBox.toggled.connect(self.judgeCodonW) self.switchSeqType(self.comboBox_6.currentText()) # 给开始按钮添加菜单 menu = QMenu(self) menu.setToolTipsVisible(True) 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(self.work_action) menu.addAction(self.dir_action) self.pushButton_2.toolButton.setMenu(menu) self.pushButton_2.toolButton.menu().installEventFilter(self) self.factory.swithWorkPath(self.work_action, init=True, parent=self) # 初始化一下 ## brief demo self.label_3.clicked.connect(lambda: QDesktopServices.openUrl( QUrl( "https://dongzhang0725.github.io/dongzhang0725.github.io/documentation/#5-1-1-Brief-example" ))) @pyqtSlot() def on_pushButton_2_clicked(self): """ execute program """ self.dict_args = {} self.dict_args["progressSig"] = self.progressSig self.dict_args["gb_files"] = self.gb_files 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 + "extract_results" + os.sep + self.output_dir_name) self.dict_args["exportPath"] = self.exportPath self.dict_args["codon"] = str( self.comboBox_9.currentText()).split(" ")[0] self.dict_args["Name Type"] = [ self.comboBox_4.itemText(i) for i in range(self.comboBox_4.count()) if self.comboBox_4.model().item(i).checkState() == Qt.Checked ] # self.dict_args["Gene Number"] = str( # self.comboBox_8.currentText()) # bool self.dict_args[ "if ignore duplicated"] = True #self.checkBox_2.isChecked() self.dict_args["if statistics"] = True #self.checkBox_3.isChecked() self.dict_args["if itol"] = self.groupBox_4.isChecked() self.dict_args["totleID"] = self.totleID self.dict_args["seq type"] = str(self.comboBox_6.currentText()) for row, row_text in enumerate( ["atp", "nad", "cytb", "cox", "rRNA", "tRNA", "NCR"]): for column, column_text in enumerate( ["checked", "colour", "length", "shape"]): if column == 0: self.dict_args[ row_text + column_text] = True if self.tableWidget.item( row, column).checkState( ) == Qt.Checked else False # bool elif column == 1: self.dict_args[row_text + column_text] = self.tableWidget.item( row, column).text() elif column == 2: self.dict_args[row_text + column_text] = self.tableWidget.item( row, column).text() elif column == 3: shape_combo = self.tableWidget.cellWidget(row, column) shape = shape_combo.currentText() self.dict_args[row_text + column_text] = self.dict_shape[ shape] # 转为缩写形式 self.dict_args["gene interval"] = self.doubleSpinBox.value() self.dict_args["included_lineages"] = [ self.comboBox_7.itemText(i) for i in range(self.comboBox_7.count()) if self.comboBox_7.model().item(i).checkState() == Qt.Checked ] # {"Family":["red", "green", ""]} dict_color_array = self.getLineageColor() for i in dict_color_array: while "" in dict_color_array[i]: # 删除空行 dict_color_array[i].remove("") self.dict_args["lineage color"] = dict_color_array ##提取的设置 dict_extract_settings = copy.deepcopy( self.dict_gbExtract_set[self.comboBox_6.currentText()]) #提取所有的话,记得先判断有没有那个键 extract_all_features = dict_extract_settings.pop("extract all features") if "extract all features" \ in dict_extract_settings else False self.dict_args["extract_intergenic_regions"] = dict_extract_settings.pop("extract intergenic regions") if "extract intergenic regions" \ in dict_extract_settings else True self.dict_args["extract_overlapping_regions"] = dict_extract_settings.pop("extract overlapping regions") if "extract overlapping regions" \ in dict_extract_settings else True self.dict_args["intergenic_regions_threshold"] = dict_extract_settings.pop("intergenic regions threshold") if "intergenic regions threshold" \ in dict_extract_settings else 200 self.dict_args["overlapping_regions_threshold"] = dict_extract_settings.pop( "overlapping regions threshold") if "overlapping regions threshold" \ in dict_extract_settings else 1 self.dict_args["features"] = dict_extract_settings.pop( "Features to be extracted") if not extract_all_features else "All" # self.dict_args["features"] = "All" if self.extract_all_features else self.dict_args["features"] name_unify = dict_extract_settings.pop("Names unification") self.dict_args["replace"] = {i[0]: i[1] for i in name_unify} self.dict_args["extract_list_gene"] = dict_extract_settings.pop("extract listed gene") if "extract listed gene" \ in dict_extract_settings else False self.dict_args[ "qualifiers"] = dict_extract_settings ###只剩下qualifier的设置 self.dict_args["extract_entire_seq"] = self.radioButton.isChecked() self.dict_args["entire_seq_name"] = self.lineEdit.text( ) if self.lineEdit.text() else "sequence" self.dict_args["cal_codon_bias"] = False # self.checkBox.isChecked() ok = self.factory.remove_dir(self.dict_args["exportPath"], parent=self) if not ok: return # 提醒是否删除旧结果,如果用户取消,就不执行 self.worker = WorkThread(self.run_command, parent=self) self.worker.start() def run_command(self): try: # 清空文件夹再生成结果,放在这里执行好统一管理报错 # self.clearFolderSig.emit(self.dict_args["exportPath"]) time_start = datetime.datetime.now() self.startButtonStatusSig.emit([ self.pushButton_2, self.progressBar, "start", self.dict_args["exportPath"], self.qss_file, self ]) if (self.dict_args["seq type"] == "Mitogenome") and ( not self.dict_args["extract_entire_seq"]): # 如果是整个序列提取,也不能用这个 extract = GBextract_MT(**self.dict_args) extract._exec() else: extract = GBextract(**self.dict_args) extract._exec() # extract = GBextract(**self.dict_args) if extract.Error_ID: stopStatus = extract.Error_ID elif extract.source_feature_IDs: stopStatus = "extract_no_feature%s" % ", ".join( extract.source_feature_IDs) else: stopStatus = "stop" self.startButtonStatusSig.emit([ self.pushButton_2, self.progressBar, stopStatus, self.dict_args["exportPath"], self.qss_file, self ]) self.focusSig.emit(self.dict_args["exportPath"]) time_end = datetime.datetime.now() self.time_used_des = "Start at: %s\nFinish at: %s\nTotal time used: %s\n\n" % ( str(time_start), str(time_end), str(time_end - time_start)) with open(self.dict_args["exportPath"] + os.sep + "summary.txt", "w", encoding="utf-8") as f: f.write( "If 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\n" + self.time_used_des + "For the summary of this extraction, please see \"overview.csv\"" ) except BaseException: self.exceptionInfo = ''.join( traceback.format_exception( *sys.exc_info())) # 捕获报错内容,只能在这里捕获,没有报错的地方无法捕获 self.exception_signal.emit(self.exceptionInfo) # 激发这个信号 self.startButtonStatusSig.emit([ self.pushButton_2, self.progressBar, "except", self.dict_args["exportPath"], self.qss_file, self ]) @pyqtSlot() def on_pushButton_clicked(self): reply = QMessageBox.question( self, "Confirmation", "<p style='line-height:25px; height:25px'>Extracter is still running, terminate it?</p>", QMessageBox.Yes, QMessageBox.Cancel) if reply == QMessageBox.Yes: if hasattr(self, "worker"): try: self.worker.stopWork() except: pass self.close() @pyqtSlot() def on_pushButton_6_clicked(self): """ Add row """ currentRows = self.tableWidget_2.rowCount() self.tableWidget_2.setRowCount(currentRows + 1) for column in range(self.tableWidget_2.columnCount()): item = QTableWidgetItem("") item.setToolTip("Double click to set colors") self.tableWidget_2.setItem(currentRows, column, item) @pyqtSlot() def on_pushButton_8_clicked(self): """ delete row """ selecteditems = self.tableWidget_2.selectedItems() rows = sorted(set([i.row() for i in selecteditems]), reverse=True) for row in rows: self.tableWidget_2.removeRow(row) @pyqtSlot() def on_pushButton_9_clicked(self): """ delete cell """ selecteditems = self.tableWidget_2.selectedItems() if selecteditems: for i in selecteditems: i.setText("") i.setBackground(QColor('transparent')) @pyqtSlot() def on_toolButton_3_clicked(self): """ GenBank file extract settings """ self.extract_setting = ExtractSettings(self) self.extract_setting.closeSig.connect(self.displaySettings) # 添加最大化按钮 self.extract_setting.setWindowFlags(self.extract_setting.windowFlags() | Qt.WindowMinMaxButtonsHint) self.extract_setting.exec_() @pyqtSlot() def on_pushButton_10_clicked(self): """ GenBank file lineage recognization settings """ self.setting = Setting(self) self.setting.display_table(self.setting.listWidget.item(0)) self.setting.closeSig.connect(self.updateLineageCombo) self.setting.closeSig.connect(self.updateLineageTable) self.setting.setWindowFlags(self.setting.windowFlags() | Qt.WindowMinMaxButtonsHint) self.setting.exec_() def showOn(self): list_color = [ "#ffff33", "#99ffff", "#ff9999", "#6699ff", "#DAA520", "#ccff00", "#bfbfbf" ] self.tableWidget.itemClicked.connect(self.handleItemClicked) for row in range(self.tableWidget.rowCount()): # 2列颜色 (必须初始化一个item,不然恢复界面设置会出错) item2 = QTableWidgetItem(list_color[row]) item2.setBackground(QColor(list_color[row])) self.tableWidget.setItem(row, 1, item2) model = QStandardItemModel() for i in self.dict_icon: item = QStandardItem(i) item.setIcon(QIcon(self.dict_icon[i])) font = item.font() font.setPointSize(13) item.setFont(font) model.appendRow(item) comb_box = MyComboBox(self) comb_box.setModel(model) # 改变icon大小 view = comb_box.view() view.setIconSize(QSize(38, 38)) self.tableWidget.setCellWidget(row, 3, comb_box) self.tableWidget.resizeColumnsToContents() self.tableWidget.verticalHeader().setVisible(False) def handleItemClicked(self, item): if item.column() == 1: color = QColorDialog.getColor(QColor(item.text()), self) if color.isValid(): item.setText(color.name()) item.setBackground(color) self.tableWidget.clearSelection() def handleItemClicked_2(self, item): color = QColorDialog.getColor(QColor(item.text()), self) if color.isValid(): item.setText(color.name()) item.setBackground(color) self.tableWidget_2.clearSelection() def guiSave(self): # Save geometry self.extractGB_settings.setValue('size', self.size()) # self.extractGB_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 if name in ["comboBox_7", "comboBox_4"]: dict_state = {} for i in range(obj.count()): text = obj.itemText(i) state = 2 if obj.model().item( i).checkState() == Qt.Checked else 0 dict_state[text] = state self.extractGB_settings.setValue(name, dict_state) elif name != "comboBox_5": text = obj.currentText() if text: allItems = [ obj.itemText(i) for i in range(obj.count()) ] allItems.remove(text) sortItems = [text] + allItems self.extractGB_settings.setValue(name, sortItems) if isinstance(obj, QGroupBox): state = obj.isChecked() self.extractGB_settings.setValue(name, state) if isinstance(obj, QCheckBox): state = obj.isChecked() self.extractGB_settings.setValue(name, state) if isinstance(obj, QRadioButton): state = obj.isChecked() self.extractGB_settings.setValue(name, state) if isinstance(obj, QTableWidget): if name == "tableWidget": array = [] # 每一行存:checked, color, length, index_text for row in range(obj.rowCount()): checked = "true" if obj.item( row, 0).checkState() == Qt.Checked else "false" colour = obj.item(row, 1).text() length = obj.item(row, 2).text() shape_combo = obj.cellWidget(row, 3) shape = shape_combo.currentText() array.append([checked, colour, length, shape]) self.extractGB_settings.setValue(name, array) elif name == "tableWidget_2": dict_color_array = self.getLineageColor() self.extractGB_settings.setValue(name, dict_color_array) if isinstance(obj, QTabWidget): index = obj.currentIndex() self.extractGB_settings.setValue(name, index) if isinstance(obj, QDoubleSpinBox): value = obj.value() self.extractGB_settings.setValue(name, value) if isinstance(obj, QLineEdit): text = obj.text() self.extractGB_settings.setValue(name, text) def guiRestore(self): # Restore geometry self.resize(self.extractGB_settings.value('size', QSize(571, 680))) self.factory.centerWindow(self) if self.height() < 640: self.resize(QSize(571, 650)) # self.move(self.extractGB_settings.value('pos', QPoint(875, 254))) for name, obj in inspect.getmembers(self): if isinstance(obj, QComboBox): if name == "comboBox_5": # 展示输入 model = obj.model() obj.clear() for num, i in enumerate(self.list_names): item = QStandardItem(i) # 背景颜色 if num % 2 == 0: item.setBackground(QColor(255, 255, 255)) else: item.setBackground(QColor(237, 243, 254)) model.appendRow(item) self.changeLable() elif name == "comboBox_6": self.displaySettings() elif name == "comboBox_7": self.updateLineageCombo() elif name == "comboBox_4": key = re.sub(r"/|\\", "_", self.workPath) + "_availableInfo" value = self.data_settings.value(key, None) if value: source_keys = value[3][1] else: source_keys = None items = ["Organism", "ID", "Name", "Length", "Description", "Date"] + source_keys \ if source_keys else ["Organism", "ID", "Name", "Length"] ini_state = {}.fromkeys(items, 0) # 2代表选中 ini_state["Organism"] = 2 ini_state["Name"] = 2 dict_name_state = self.extractGB_settings.value( "comboBox_4", ini_state) if type(dict_name_state) != dict: dict_name_state = ini_state self.comboBox_4.sep = "_" model = self.comboBox_4.model() self.comboBox_4.clear() for num, text in enumerate(items): item = QStandardItem(str(text)) state = dict_name_state[ text] if text in dict_name_state else ini_state[ text] # 测试一下 item.setCheckState(int(state)) # 背景颜色 if num % 2 == 0: item.setBackground(QColor(255, 255, 255)) else: item.setBackground(QColor(237, 243, 254)) model.appendRow(item) self.comboBox_4.setTopText() self.comboBox_4.view().pressed.connect(self.judgeName) else: allItems = [obj.itemText(i) for i in range(obj.count())] values = self.extractGB_settings.value(name, allItems) model = obj.model() obj.clear() for num, i in enumerate(values): item = QStandardItem(i) # 背景颜色 if num % 2 == 0: item.setBackground(QColor(255, 255, 255)) else: item.setBackground(QColor(237, 243, 254)) model.appendRow(item) if isinstance(obj, QGroupBox): # 如果没有name,就设置为True state = self.extractGB_settings.value(name, "true") obj.setChecked(self.factory.str2bool(state)) if isinstance(obj, QCheckBox): value = self.extractGB_settings.value( name, "no setting") # get stored value from registry if value != "no setting": obj.setChecked( self.factory.str2bool(value)) # restore checkbox if isinstance(obj, QRadioButton): value = self.extractGB_settings.value( name, "first") # get stored value from registry if value != "first": obj.setChecked( self.factory.str2bool(value)) # restore checkbox if isinstance(obj, QTableWidget): if name == "tableWidget": # 每一行存:checked, color, length, index_text ini_array = [['true', '#ffff33', '25', 'rectangle'], ['true', '#99ffff', '25', 'rectangle'], ['true', '#ff9999', '30', 'rectangle'], ['true', '#6699ff', '25', 'rectangle'], ['true', '#DAA520', '18', 'rectangle'], ['true', '#ccff00', '15', 'rectangle'], ['false', '#bfbfbf', '15', 'ellipse']] array = self.extractGB_settings.value(name, ini_array) for row in range(obj.rowCount()): ifChecked = Qt.Checked if array[row][ 0] == "true" else Qt.Unchecked obj.item(row, 0).setCheckState(ifChecked) obj.item(row, 1).setText(array[row][1]) obj.item(row, 1).setBackground(QColor(array[row][1])) obj.item(row, 2).setText(array[row][2]) shape = array[row][3] shape_combo = obj.cellWidget(row, 3) shape_combo_index = shape_combo.findText(shape) if shape_combo_index == -1: # add to list if not found shape_combo.insertItems(0, [value]) index = shape_combo.findText(value) shape_combo.setCurrentIndex(index) else: # preselect a combobox value by index shape_combo.setCurrentIndex(shape_combo_index) elif name == "tableWidget_2": self.updateLineageTable() if isinstance(obj, QTabWidget): index = self.extractGB_settings.value(name, 0) obj.setCurrentIndex(int(index)) if isinstance(obj, QDoubleSpinBox): value = self.extractGB_settings.value(name, 1.0) obj.setValue(float(value)) if isinstance(obj, QLineEdit): text = self.extractGB_settings.value(name, "first") if text != "first": obj.setText(text) def popupException(self, 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) elif "Permission" in exception: reply = QMessageBox.critical( self, "Extract sequence", "<p style='line-height:25px; height:25px'>Error happened, please close the window and try again!</p>", QMessageBox.Yes, QMessageBox.Cancel) if reply == QMessageBox.Yes: self.close() 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 closeEvent(self, event): self.guiSave() 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 eventFilter(self, obj, event): name = obj.objectName() if isinstance(obj, QTableWidget): if event.type() == QEvent.KeyPress: # 首先得判断type modifiers = QApplication.keyboardModifiers() if name == "tableWidget_2": if event.key() == Qt.Key_Delete: self.on_pushButton_9_clicked() return True if (modifiers == Qt.ControlModifier) and (event.key() == Qt.Key_C): self.copyColor() return True if (modifiers == Qt.ControlModifier) and (event.key() == Qt.Key_X): self.cutColor() return True if (modifiers == Qt.ControlModifier) and (event.key() == Qt.Key_V): self.pasteColor() return True if (event.type() == QEvent.Show) and (obj == self.pushButton_2.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_2.toolButton.menu().pos().x() menu_width = self.pushButton_2.toolButton.menu().size().width() button_width = self.pushButton_2.toolButton.size().width() pos = QPoint(menu_x_pos - menu_width + button_width, self.pushButton_2.toolButton.menu().pos().y()) self.pushButton_2.toolButton.menu().move(pos) return True return super(ExtractGB, self).eventFilter(obj, event) def changeLable(self): count = str(self.comboBox_5.count()) self.label_4.setText("Inputs (" + count + "):") def getLineageColor(self): dict_color_array = OrderedDict() columnNum = self.tableWidget_2.columnCount() rowNum = self.tableWidget_2.rowCount() for column in range(columnNum): headerText = self.tableWidget_2.horizontalHeaderItem(column).text() list_columnText = [] for row in range(rowNum): if self.tableWidget_2.item(row, column): list_columnText.append( self.tableWidget_2.item(row, column).text()) dict_color_array[headerText] = list_columnText return dict_color_array def copyColor(self): selecteditems = self.tableWidget_2.selectedItems() if selecteditems: list_selItem_text = [i.text() for i in selecteditems] QApplication.clipboard().setText("\t".join(list_selItem_text)) def cutColor(self): selecteditems = self.tableWidget_2.selectedItems() list_selItem_text = [] if selecteditems: for i in selecteditems: list_selItem_text.append(i.text()) i.setText("") i.setBackground(QColor('transparent')) QApplication.clipboard().setText("\t".join(list_selItem_text)) def pasteColor(self): colors = QApplication.clipboard().text() list_colors = re.split(r"\s+|,", colors) while "" in list_colors: list_colors.remove("") selecteditems = self.tableWidget_2.selectedItems() for num, i in enumerate(selecteditems): if (num + 1) <= len(list_colors): color = list_colors[num] qcolor = QColor(color) if not qcolor.isValid(): continue i.setText(color) i.setBackground(qcolor) def displaySettings(self): ###提取的设置 self.GenBankExtract_settings = QSettings( self.thisPath + '/settings/GenBankExtract_settings.ini', QSettings.IniFormat) # File only, no fallback to registry or or. self.GenBankExtract_settings.setFallbacksEnabled(False) self.dict_gbExtract_set = self.GenBankExtract_settings.value( "set_version") # self.extract_list_gene = self.factory.str2bool(self.GenBankExtract_settings.value("extract listed gene", # "false")) # self.extract_all_features = self.factory.str2bool(self.GenBankExtract_settings.value("extract all features", # # "false")) if self.dict_gbExtract_set: self.allVersions = list(self.dict_gbExtract_set.keys()) model = self.comboBox_6.model() self.comboBox_6.clear() for num, i in enumerate(self.allVersions + [">>>More<<<"]): item = QStandardItem(i) # 背景颜色 if num % 2 == 0: item.setBackground(QColor(255, 255, 255)) else: item.setBackground(QColor(237, 243, 254)) model.appendRow(item) self.switchSeqType(self.comboBox_6.currentText()) def switchSeqType(self, text): ###保存version顺序 if text == ">>>More<<<": QDesktopServices.openUrl( QUrl( "https://dongzhang0725.github.io/dongzhang0725.github.io/PhyloSuite-demo/customize_extraction/" )) self.comboBox_6.setCurrentIndex(0) return if text != self.allVersions[0]: if text in self.allVersions: list_now_versions = copy.deepcopy(self.allVersions) list_now_versions.remove(text) reorder_list = [text] + list_now_versions self.dict_gbExtract_set = OrderedDict( (i, self.dict_gbExtract_set[i]) for i in reorder_list) self.GenBankExtract_settings.setValue('set_version', self.dict_gbExtract_set) ###控制itol if text != "Mitogenome": targetIndex = "null" for index in range(self.tabWidget.count()): if self.tabWidget.tabText(index) == "Gene order display": targetIndex = index if targetIndex != "null": self.hiddenIndex = targetIndex self.hiddenWidget = self.tabWidget.widget(self.hiddenIndex) self.hiddenTabText = self.tabWidget.tabText(self.hiddenIndex) self.tabWidget.removeTab(self.hiddenIndex) self.hiddenFlag = True else: if hasattr(self, "hiddenFlag") and self.hiddenFlag: self.tabWidget.insertTab(self.hiddenIndex, self.hiddenWidget, self.hiddenTabText) self.hiddenFlag = False self.tabWidget.setCurrentIndex(0) def updateLineageTable(self): # tableWidget_2 self.tableWidget_2.itemDoubleClicked.connect(self.handleItemClicked_2) header, array = self.factory.getCurrentTaxSetData() countColumn = len(header) self.tableWidget_2.setColumnCount(countColumn) ini_dict_array = OrderedDict() for i in header: ini_dict_array[i] = [""] * 6 familyColor = [ "#81C7D6", "#FF0033", "#6A00D1", "#49BF4E", "#AA538B", "#FF99CC" ] ini_dict_array[ "Family"] = familyColor if "Family" in ini_dict_array else ini_dict_array[ "Family"] self.tableWidget_2.setHorizontalHeaderLabels(header) dict_array = self.extractGB_settings.value("tableWidget_2", ini_dict_array) maxRow = len(max(list(dict_array.values()), key=len)) self.tableWidget_2.setRowCount(maxRow) if len(ini_dict_array) > len(dict_array): # 如果用户添加了lineage list_differ = list( set(ini_dict_array.keys()).difference(set(dict_array.keys()))) for i in list_differ: # 加上这个lineage dict_array[i] = [""] * maxRow for column, columnText in enumerate(header): for row, rowText in enumerate(dict_array[columnText]): item = QTableWidgetItem(rowText) item.setToolTip("Double click to set colors") color = QColor(rowText) if color.isValid(): item.setBackground(color) self.tableWidget_2.setItem(row, column, item) def updateLineageCombo(self): header, array = self.factory.getCurrentTaxSetData() ini_state = {}.fromkeys(header, 2) # 2代表选中 dict_lng_state = self.extractGB_settings.value("comboBox_7", ini_state) model = self.comboBox_7.model() self.comboBox_7.clear() for num, text in enumerate(header): item = QStandardItem(text) state = dict_lng_state[ text] if text in dict_lng_state else ini_state[text] # 测试一下 item.setCheckState(int(state)) # 背景颜色 if num % 2 == 0: item.setBackground(QColor(255, 255, 255)) else: item.setBackground(QColor(237, 243, 254)) model.appendRow(item) self.comboBox_7.setTopText() def judgeName(self): if not set(["Organism", "ID", "Name"]).intersection( set(self.comboBox_4.topText.split("_"))): QMessageBox.information( self, "Information", "<p style='line-height:25px; height:25px'>At least one of the following should be retained: \"Organism\", \"ID\", \"Name\"</p>" ) self.comboBox_4.item.setCheckState(Qt.Checked) self.comboBox_4.setTopText() def judgeCodonW(self, bool_): if bool_: pass
class TrimAl(QDialog, Ui_trimAl, object): exception_signal = pyqtSignal(str) # 定义所有类都可以使用的信号 progressSig = pyqtSignal(int) # 控制进度条 startButtonStatusSig = pyqtSignal(list) logGuiSig = pyqtSignal(str) trimAl_exception = pyqtSignal(str) workflow_progress = pyqtSignal(int) workflow_finished = pyqtSignal(str) # 用于输入文件后判断用 ui_closeSig = pyqtSignal(str) # 用于flowchart自动popup combobox等操作 showSig = pyqtSignal(QDialog) closeSig = pyqtSignal(str, str) ##弹出识别输入文件的信号 auto_popSig = pyqtSignal(QDialog) def __init__(self, workPath=None, TApath=None, autoInputs=None, focusSig=None, workflow=None, parent=None): super(TrimAl, self).__init__(parent) self.parent = parent self.function_name = "trimAl" self.workflow = workflow self.factory = Factory() self.thisPath = self.factory.thisPath self.workPath = workPath self.focusSig = focusSig self.TApath = TApath self.autoInputs = autoInputs self.setupUi(self) # 保存设置 if not workflow: self.trimAl_settings = QSettings( self.thisPath + '/settings/trimAl_settings.ini', QSettings.IniFormat) else: self.trimAl_settings = QSettings( self.thisPath + '/settings/workflow_settings.ini', QSettings.IniFormat) self.trimAl_settings.beginGroup("Workflow") self.trimAl_settings.beginGroup("temporary") self.trimAl_settings.beginGroup('trimAl') # File only, no fallback to registry or or. self.trimAl_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.trimAl_exception.connect(self.popup_trimAl_exception) self.comboBox_4.lineEdit().autoDetectSig.connect( self.popupAutoDec) # 自动识别可用的输入 self.comboBox_4.installEventFilter(self) self.lineEdit_2.installEventFilter(self) self.lineEdit_3.installEventFilter(self) self.log_gui = self.gui4Log() # stat output file name self.lineEdit_2.setLineEditNoChange(True) self.lineEdit_3.setLineEditNoChange(True) self.lineEdit_2.deleteFile.clicked.connect( self.clear_lineEdit) # 删除了内容,也要把tooltip删掉 self.lineEdit_3.deleteFile.clicked.connect( self.clear_lineEdit) # 删除了内容,也要把tooltip删掉 # 选框联动 self.checkBox_2.toggled.connect(self.switchCheckBox1) self.checkBox_5.toggled.connect(self.switchCheckBox1) # self.groupBox_5.toggled.connect(lambda bool_: self.groupBox_6.setChecked(not bool_)) # self.groupBox_6.toggled.connect(lambda bool_: self.groupBox_5.setChecked(not bool_)) self.checkBox_3.toggled.connect(self.switchCheckBox2) self.checkBox_6.toggled.connect(self.switchCheckBox2) self.checkBox_7.toggled.connect(self.switchCheckBox2) self.checkBox_8.toggled.connect(self.switchCheckBox2) self.checkBox_9.toggled.connect(self.judgeInput) self.radioButton.toggled.connect(self.Un_checkBox_9) self.comboBox_2.currentTextChanged.connect(self.judgeComboBox_2) self.comboBox.currentTextChanged.connect(self.judgeFormats) self.radioButton.toggled.connect(self.controlRefineText) 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-4-1-Brief-example" ))) ##自动弹出识别文件窗口 self.auto_popSig.connect(self.popupAutoDecSub) @pyqtSlot() def on_pushButton_clicked(self): """ execute program """ self.command = self.fetchCommands() if self.command: self.interrupt = False self.error_has_shown = False #保证只报一次错 self.list_pids = [] self.queue = multiprocessing.Queue() thread = int(self.comboBox_6.currentText()) thread = thread if len( self.dict_args["inputFiles"]) > thread else len( self.dict_args["inputFiles"]) thread = 1 if not self.dict_args[ "inputFiles"] else thread # compare的情况 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_9_clicked(self): """ show log """ self.log_gui.show() @pyqtSlot() def on_pushButton_3_clicked(self): """ alignment file """ fileNames = QFileDialog.getOpenFileNames(self, "Input alignment file") if fileNames[0]: self.input(fileNames[0]) @pyqtSlot() def on_pushButton_4_clicked(self): """ set file for comparison """ fileName = QFileDialog.getOpenFileName( self, "Input file containing alignment path to compare") 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): """ matrix file """ fileName = QFileDialog.getOpenFileName(self, "Input matrix file") if fileName[0]: base = os.path.basename(fileName[0]) self.lineEdit_3.setText(base) self.lineEdit_3.setToolTip(fileName[0]) @pyqtSlot() def on_pushButton_2_clicked(self, quiet=False): """ Stop """ if self.isRunning(): if (not self.workflow) and (not quiet): reply = QMessageBox.question( self, "Confirmation", "<p style='line-height:25px; height:25px'>trimAl 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) and (not quiet): QMessageBox.information( self, "trimAl", "<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 ]) ##进度条用 # self.dict_file_progress = {os.path.basename(file): 0 for file in self.dict_args["seq_files"]} if self.radioButton.isChecked(): async_results = [ self.pool.apply_async(run, args=(self.dict_args, self.command, file)) for file in self.dict_args["inputFiles"] ] self.totalFileNum = len(self.dict_args["inputFiles"]) else: async_results = [ self.pool.apply_async(run, args=(self.dict_args, self.command, self.dict_args["compareFile"])) ] self.totalFileNum = 1 self.finishedFileNum = 0 #进度条用 self.pool.close() # 关闭进程池,防止进一步操作。如果所有操作持续挂起,它们将在工作进程终止前完成 map(ApplyResult.wait, async_results) lst_results = [r.get() for r in async_results] # 判断比对是否成功 trimAl_results = glob.glob(self.exportPath + os.sep + "*_trimAl.fas") empty_files = [ os.path.basename(file) for file in trimAl_results if os.stat(file).st_size == 0 ] has_error = False if not trimAl_results or empty_files: has_error = True log = self.textEdit_log.toPlainText() list_commands = re.findall(r"Command: (.+)\n", log) last_cmd = list_commands[-1] if list_commands else "" self.trimAl_exception.emit( "trimAl 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: self.renameSequence() #修剪后序列的名字被,需要改回来 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., 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 trimAl, 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.trimAl_settings.setValue('size', self.size()) # self.trimAl_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.trimAl_settings.setValue(name, index) elif isinstance(obj, QCheckBox): state = obj.isChecked() self.trimAl_settings.setValue(name, state) elif isinstance(obj, QRadioButton): state = obj.isChecked() self.trimAl_settings.setValue(name, state) elif isinstance(obj, QDoubleSpinBox): float_ = obj.value() self.trimAl_settings.setValue(name, float_) elif isinstance(obj, QLineEdit): value = obj.text() self.trimAl_settings.setValue(name, value) elif isinstance(obj, QTabWidget): index = obj.currentIndex() self.trimAl_settings.setValue(name, index) def guiRestore(self): # Restore geometry self.resize(self.trimAl_settings.value('size', QSize(732, 595))) self.factory.centerWindow(self) # self.move(self.trimAl_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.trimAl_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": self.input(self.autoInputs) else: allItems = [obj.itemText(i) for i in range(obj.count())] index = self.trimAl_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)) item.setToolTip(i) model.appendRow(item) obj.setCurrentIndex(int(index)) elif isinstance(obj, QCheckBox): value = self.trimAl_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, QRadioButton): value = self.trimAl_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, QDoubleSpinBox): ini_float_ = obj.value() float_ = self.trimAl_settings.value(name, ini_float_) obj.setValue(float(float_)) elif isinstance(obj, QLineEdit): if name not in ["lineEdit_3", "lineEdit_2"]: value = self.trimAl_settings.value( name, "") # get stored value from registry if value: obj.setText(value) # restore checkbox elif isinstance(obj, QTabWidget): index = self.trimAl_settings.value(name, 0) obj.setCurrentIndex(int(index)) 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("trimAl", self.fetchWorkflowSetting()) # 断开showSig和closeSig的槽函数连接 try: self.showSig.disconnect() except: pass try: self.closeSig.disconnect() except: pass if self.workflow: self.ui_closeSig.emit("trimAl") # 自动跑的时候不杀掉程序 return if self.isRunning(): reply = QMessageBox.question( self, "trimAl", "<p style='line-height:25px; height:25px'>trimAl 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() name = obj.objectName() 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(files) 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_2": 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(TrimAl, 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 trimAl:") 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_TrimAl_.log", "a") as f: f.write(text + "\n") def save_log_to_file(self): content = self.textEdit_log.toPlainText() fileName = QFileDialog.getSaveFileName(self, "trimAl", "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, list_items=None): if list_items: self.comboBox_4.refreshInputs(list_items) else: self.comboBox_4.refreshInputs([]) 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 clear_lineEdit(self): sender = self.sender() lineEdit = sender.parent() lineEdit.setText("") lineEdit.setToolTip("") def fetchCommands(self): if (self.radioButton.isChecked() and self.comboBox_4.count()) or \ (self.radioButton_2.isChecked() and self.lineEdit_2.toolTip()): self.interrupt = False self.error_has_shown = False self.dict_args = {} 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 + "trimAl_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 if self.tabWidget.tabText( self.tabWidget.currentIndex()) == "Automated Trimming": # 自动模式 method = self.getAutoMethod() self.dict_args["autoMethod"] = " -%s" % method self.dict_args["cons"] = "" self.dict_args["gt"] = "" self.dict_args["st"] = "" self.dict_args["ct"] = "" self.dict_args["w"] = "" self.dict_args["gw"] = "" self.dict_args["sw"] = "" self.dict_args["cw"] = "" else: # 人工模式 self.dict_args["autoMethod"] = "" self.dict_args[ "cons"] = " -cons %.1f" % self.doubleSpinBox.value() self.dict_args[ "gt"] = " -gt %.1f" % self.doubleSpinBox_2.value() self.dict_args[ "st"] = " -st %.1f" % self.doubleSpinBox_3.value() self.dict_args[ "ct"] = " -ct %.1f" % self.doubleSpinBox_4.value( ) if self.checkBox_9.isChecked() else "" self.dict_args["w"] = " -w %.1f" % self.doubleSpinBox_7.value( ) if self.checkBox_3.isChecked() else "" self.dict_args[ "gw"] = " -gw %.1f" % self.doubleSpinBox_6.value( ) if self.checkBox_6.isChecked() else "" self.dict_args[ "sw"] = " -sw %.1f" % self.doubleSpinBox_5.value( ) if self.checkBox_7.isChecked() else "" self.dict_args[ "cw"] = " -cw %.1f" % self.doubleSpinBox_8.value( ) if self.checkBox_8.isChecked() else "" self.dict_args[ "complementary"] = " -complementary" if self.checkBox.isChecked( ) else "" self.dict_args[ "colnumbering"] = " -colnumbering" if self.checkBox_2.isChecked( ) else "" html_file_name = self.lineEdit_4.text() if self.lineEdit_4.text( ) else "summary.html" self.dict_args["htmlout"] = " -htmlout \"%s\"" % ( self.dict_args["exportPath"] + os.sep + "$fileBase$_" + html_file_name) if self.checkBox_4.isChecked() else "" self.dict_args["stat"] = " -%s" % self.comboBox_2.currentText( ).split(":")[0] if self.checkBox_5.isChecked() else "" self.dict_args["outFormat"] = " -%s" % self.comboBox.currentText() # 输出文件后缀 dict_suffix = { "fasta": ".fas", "phylip": ".phy", "phylip3.2": ".phy", "nexus": ".nex", "mega": ".meg", "clustal": ".clw", "nbrf": ".nbrf" } self.dict_args["suffix"] = dict_suffix[self.comboBox.currentText()] self.dict_args["trimAl"] = self.TApath ##输入文件 if self.radioButton.isChecked(): self.dict_args["compareFile"] = "" self.dict_args["inputFiles"] = self.comboBox_4.fetchListsText() command = "\"{trimAl}\" -in alignment{autoMethod}{cons}{gt}{st}{ct}" \ "{w}{gw}{sw}{cw}{complementary}{colnumbering}{htmlout}" \ "{outFormat} -out outputFile{stat}".format(**self.dict_args) else: # compare self.dict_args["inputFiles"] = [] self.dict_args["compareFile"] = self.lineEdit_2.toolTip() command = "\"{trimAl}\" -compareset setFile{autoMethod}{cons}{gt}{st}{ct}" \ "{w}{gw}{sw}{cw}{complementary}{colnumbering}{htmlout}{outFormat}" \ " -out outputFile{stat}".format(**self.dict_args) self.reference = "Capella-Gutierrez S, Silla-Martinez JM, Gabaldon T. 2009. trimAl: a tool for automated" \ " alignment trimming in large-scale phylogenetic analyses. Bioinformatics. 25: 1972-1973. " \ "doi: 10.1093/bioinformatics/btp348." cmd_used = "{autoMethod}{cons}{gt}{st}{ct}{w}{gw}{sw}{cw}".format( **self.dict_args).strip() self.description = "Gap sites were removed with trimAl (Capella‐Gutiérrez et al., 2009) using \"%s\" command." % cmd_used self.textEdit_log.clear() # 清空 return command else: if self.radioButton.isChecked(): QMessageBox.critical( self, "trimAl", "<p style='line-height:25px; height:25px'>Please input files to \"Input\" box first!</p>" ) else: QMessageBox.critical( self, "trimAl", "<p style='line-height:25px; height:25px'>Please input files to \"Compare set\" box first!</p>" ) def isRunning(self): '''判断程序是否运行,依赖进程是否存在来判断''' return hasattr(self, "pool") and self.pool and not self.interrupt def run_with_CMD(self, cmd): self.command = cmd if self.command: self.interrupt = False self.error_has_shown = False self.list_pids = [] self.queue = multiprocessing.Queue() thread = int(self.comboBox_6.currentText()) thread = thread if len( self.dict_args["inputFiles"]) > thread else len( self.dict_args["inputFiles"]) thread = 1 if not self.dict_args[ "inputFiles"] else thread # compare的情况 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() if self.radioButton.isChecked(): if " -in alignment" not in text: QMessageBox.information( self, "trimAl", "<p style='line-height:25px; height:25px'>\"-in alignment\" cannot be changed!</p>" ) self.textEdit_cmd.undo() else: if " -compareset setFile" not in text: QMessageBox.information( self, "trimAl", "<p style='line-height:25px; height:25px'>\"-compareset setFile\" cannot be changed!</p>" ) 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": self.finishedFileNum += 1 if not self.interrupt: self.workflow_progress.emit(self.finishedFileNum * 95 / self.totalFileNum) self.progressSig.emit(self.finishedFileNum * 95 / self.totalFileNum) elif info[0] == "popen": self.list_pids.append(info[1]) elif info[0] == "error": self.on_pushButton_2_clicked(quiet=True) #杀掉进程 self.trimAl_exception.emit( "Error happened! Click <span style='font-weight:600; color:#ff0000;'>Show log</span> to see detail!" ) self.error_has_shown = True elif info[0] == "popen finished": if info[1] in self.list_pids: self.list_pids.remove(info[1]) def popup_trimAl_exception(self, text): if not self.error_has_shown: QMessageBox.critical( self, "trimAl", "<p style='line-height:25px; height:25px'>%s</p>" % text) if "Show log" in text: self.on_pushButton_9_clicked() def judgeComboBox_2(self, text): if text in ["sfc: Print compare values for columns in the selected alignment from compare files method.", "sft: Print accumulated compare values count for the selected alignment from compare files method."] \ and self.radioButton.isChecked(): QMessageBox.information( self, "trimAl", "<p style='line-height:25px; height:25px'>This option should be used in combination with \"Compare set\".</p>" ) self.comboBox_2.setCurrentIndex(0) def switchCheckBox1(self, bool_): checkbox = self.sender() if (checkbox == self.checkBox_2) and bool_ and self.checkBox_5.isChecked(): self.checkBox_5.setChecked(False) if (checkbox == self.checkBox_5) and bool_ and self.checkBox_2.isChecked(): self.checkBox_2.setChecked(False) def switchCheckBox2(self, bool_): checkbox = self.sender() if checkbox == self.checkBox_3: if bool_: for i in [self.checkBox_6, self.checkBox_7, self.checkBox_8]: i.setChecked(not bool_) else: if bool_: self.checkBox_3.setChecked(not bool_) def judgeInput(self, bool_): if bool_ and (not self.radioButton_2.isChecked()): QMessageBox.information( self, "trimAl", "<p style='line-height:25px; height:25px'>This option should be used in combination with \"Compare set\".</p>" ) self.checkBox_9.setChecked(False) def Un_checkBox_9(self, bool_): if bool_: self.checkBox_9.setChecked(not bool_) def popupAutoDec(self, init=False): self.init = init self.factory.popUpAutoDetect("trimAl", 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]) autoInputs = widget.autoInputs self.input(autoInputs) def judgeFormats(self, text): if text != "fasta": QMessageBox.warning( self, "HmmCleaner", "<p style='line-height:25px; height:25px'>\"%s\" format cannot be used by the downstream programs " "(e.g. concatenation), please select fasta format if you are going to use " "this result for other functions.</p>" % text) def renameSequence(self): trimedFiles = glob.glob(self.exportPath + os.sep + "*_trimAl.fas") if trimedFiles: for num, file in enumerate(trimedFiles): with open(file, encoding="utf-8", errors="ignore") as f: content = f.read() with open(file, "w", encoding="utf-8") as f1: f1.write(re.sub(r"(>.+?) \d+ bp", "\\1", content)) self.progressSig.emit(95 + (5 * (num + 1) / len(trimedFiles))) self.workflow_progress.emit(95 + (5 * (num + 1) / len(trimedFiles))) def fetchWorkflowSetting(self): '''* Alignment Mode * Code table(if codon mode) * strategy * export format''' settings = '''<p class="title">***trimAl***</p>''' trim_strategy = self.tabWidget.tabText(self.tabWidget.currentIndex()) settings += '<p>Trimming strategy: <a href="self.trimAl_exe' \ ' factory.highlightWidgets(x.tabWidget.tabBar())">%s</a></p>' % trim_strategy if trim_strategy == "Automated Trimming": # settings += "|--Automated Trimming--|" strategy = self.getAutoMethod() settings += '<p>Automated method: <a href="self.trimAl_exe ' \ 'factory.highlightWidgets(x.radioButton_10,x.radioButton_11,x.radioButton_12,' \ 'x.radioButton_13,x.radioButton_14,x.radioButton_15)">%s</a></p>' % strategy else: # settings += "|--Manual Trimming--|" cons = self.doubleSpinBox.value() settings += '<p>Minimum percentage of positions to conserve [0-100]: <a href="self.trimAl_exe ' \ 'doubleSpinBox.setFocus() doubleSpinBox.selectAll()' \ ' factory.highlightWidgets(x.doubleSpinBox)">%s</a></p>' % cons gaps = self.doubleSpinBox_2.value() settings += '<p>Gap threshold, fraction of positions without gaps in a column [0-1]: <a href="self.trimAl_exe ' \ 'doubleSpinBox_2.setFocus() doubleSpinBox_2.selectAll()' \ ' factory.highlightWidgets(x.doubleSpinBox_2)">%s</a></p>' % gaps ss = self.doubleSpinBox_3.value() settings += '<p>Similarity threshold, minimum level of residue similarity within a column [0-1]: <a href="self.trimAl_exe ' \ 'doubleSpinBox_3.setFocus() doubleSpinBox_3.selectAll()' \ ' factory.highlightWidgets(x.doubleSpinBox_3)">%s</a></p>' % ss if self.checkBox_3.isChecked(): w = self.doubleSpinBox_7.value() settings += '<p>General window size, applied to all stats: <a href="self.trimAl_exe ' \ 'doubleSpinBox_7.setFocus() doubleSpinBox_7.selectAll()' \ ' factory.highlightWidgets(x.doubleSpinBox_7)">%s</a></p>' % w if self.checkBox_6.isChecked(): gw = self.doubleSpinBox_6.value() settings += '<p>Window size applied to Gaps: <a href="self.trimAl_exe ' \ 'doubleSpinBox_6.setFocus() doubleSpinBox_6.selectAll()' \ ' factory.highlightWidgets(x.doubleSpinBox_6)">%s</a></p>' % gw if self.checkBox_7.isChecked(): sw = self.doubleSpinBox_5.value() settings += '<p>Window size applied to Similarity: <a href="self.trimAl_exe ' \ 'doubleSpinBox_5.setFocus() doubleSpinBox_5.selectAll()' \ ' factory.highlightWidgets(x.doubleSpinBox_5)">%s</a></p>' % sw if self.checkBox_8.isChecked(): cw = self.doubleSpinBox_8.value() settings += '<p>Window size applied to Consistency: <a href="self.trimAl_exe ' \ 'doubleSpinBox_8.setFocus() doubleSpinBox_8.selectAll()' \ ' factory.highlightWidgets(x.doubleSpinBox_8)">%s</a></p>' % cw thread = self.comboBox_6.currentText() settings += '<p>Thread: <a href="self.trimAl_exe comboBox_6.showPopup()' \ ' factory.highlightWidgets(x.comboBox_6)">%s</a></p>' % thread return settings def isFileIn(self): return self.comboBox_4.count() def controlRefineText(self, bool_): if bool_: if not self.comboBox_4.count(): self.comboBox_4.lineEdit().switchColor( "No input files (Try to drag your file(s) and drop here)") else: self.comboBox_4.lineEdit().switchColor() else: self.comboBox_4.lineEdit().switchColor() def getAutoMethod(self): widgets = (self.gridLayout_8.itemAt(i).widget() for i in range(self.gridLayout_8.count())) checkedBox = [ widget for widget in widgets if isinstance(widget, QRadioButton) and widget.isChecked() ][0] return checkedBox.text()
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)))