diff --git a/.gitignore b/.gitignore
index 4a46d8f1d..d94baf7fb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
-.idea
-.pyc
-.DS_Store
+*.idea
+*.pyc
+*.DS_Store
+*.tmp
+**/__pycache__/
diff --git a/README.md b/README.md
index 3e49d309a..551201263 100644
--- a/README.md
+++ b/README.md
@@ -1,21 +1,24 @@
-pyOpenMS Extra
-=============
-
-pyOpenMS are the Python bindings to the OpenMS open-source C++ library for
-LC-MS data management and analyses. The Python bindings cover a large part of
-the OpenMS API to enable rapid algorithm development and workflow development.
-pyOpenMS supports the Proteomics Standard Initiative (PSI) formats for MS data.
-
-These utils contain documentation, installation instructions and example code
-that show the different functions of pyOpenMS.
-
-Installation
-=============
-
-Installation is best done through [PyPI](https://pypi.python.org/pypi/pyopenms)
-(the Python package index) where binary packages are provided for the release
-versions of OpenMS, covering Linux/Mac/Windows.
-
-Documentation
-=============
-The pyOpenMS documentation is also contained in this repository, see [docs/README.md](docs/README.md)
+# pyOpenMS Extra
+
+=============
+
+pyOpenMS are the Python bindings to the OpenMS open-source C++ library for
+LC-MS data management and analyses. The Python bindings cover a large part of
+the OpenMS API to enable rapid algorithm development and workflow development.
+pyOpenMS supports the Proteomics Standard Initiative (PSI) formats for MS data.
+
+These utils contain documentation, installation instructions and example code
+that show the different functions of pyOpenMS.
+
+## Installation
+
+=============
+
+Installation is best done through [PyPI](https://pypi.python.org/pypi/pyopenms)
+(the Python package index) where binary packages are provided for the release
+versions of OpenMS, covering Linux/Mac/Windows.
+
+## Documentation
+
+=============
+The pyOpenMS documentation is also contained in this repository, see [documentation/README.md](docs/README.md)
diff --git a/src/apps/FLASHDeconvViewer.py b/src/apps/FLASHDeconvViewer.py
index 14a14fccc..6f2741f77 100644
--- a/src/apps/FLASHDeconvViewer.py
+++ b/src/apps/FLASHDeconvViewer.py
@@ -39,7 +39,8 @@
QDialogButtonBox,
)
from matplotlib import cm
-from pyqtgraph import PlotWidget
+from pyqtgraph import PlotWidget # noqa: E402
+
sys.path.insert(0, "../view/")
from SpecViewer import ScanBrowserWidget, App
diff --git a/src/apps/IDViewer.py b/src/apps/IDViewer.py
index aed23cb74..20c174fd7 100644
--- a/src/apps/IDViewer.py
+++ b/src/apps/IDViewer.py
@@ -14,7 +14,7 @@
)
sys.path.insert(0, "../view")
-from ControllerWidget import ControllerWidget
+from ControllerWidget import ControllerWidget # noqa: E402
# structure for annotation (here for reference)
PeakAnnoStruct = namedtuple(
diff --git a/src/apps/Icons/IconOpenMS.png b/src/apps/Icons/IconOpenMS.png
new file mode 100644
index 000000000..b10846696
Binary files /dev/null and b/src/apps/Icons/IconOpenMS.png differ
diff --git a/src/apps/Icons/load_icon.png b/src/apps/Icons/load_icon.png
new file mode 100644
index 000000000..b33b8b508
Binary files /dev/null and b/src/apps/Icons/load_icon.png differ
diff --git a/src/apps/Icons/open.svg b/src/apps/Icons/open.svg
new file mode 100644
index 000000000..adca95fdf
--- /dev/null
+++ b/src/apps/Icons/open.svg
@@ -0,0 +1,11 @@
+
+
+
diff --git a/src/apps/Icons/run.svg b/src/apps/Icons/run.svg
new file mode 100644
index 000000000..5c491a1b9
--- /dev/null
+++ b/src/apps/Icons/run.svg
@@ -0,0 +1,10 @@
+
+
+
diff --git a/src/apps/Icons/run_icon.png b/src/apps/Icons/run_icon.png
new file mode 100644
index 000000000..3ceb75e1d
Binary files /dev/null and b/src/apps/Icons/run_icon.png differ
diff --git a/src/apps/Icons/save.svg b/src/apps/Icons/save.svg
new file mode 100644
index 000000000..43c514656
--- /dev/null
+++ b/src/apps/Icons/save.svg
@@ -0,0 +1,10 @@
+
+
+
diff --git a/src/apps/Icons/save_icon.png b/src/apps/Icons/save_icon.png
new file mode 100644
index 000000000..1564ebdfa
Binary files /dev/null and b/src/apps/Icons/save_icon.png differ
diff --git a/src/apps/ProteinQuantification.py b/src/apps/ProteinQuantification.py
new file mode 100644
index 000000000..ad3220d09
--- /dev/null
+++ b/src/apps/ProteinQuantification.py
@@ -0,0 +1,691 @@
+import sys, os, glob, platform
+from PyQt5.QtWidgets import QApplication, QMainWindow, QDesktopWidget, \
+ QTabWidget, QAction, QInputDialog, QMessageBox, QFileDialog, \
+ QWidget, QLabel, QVBoxLayout, QCheckBox, QHBoxLayout
+from PyQt5.QtGui import QIcon, QPalette, QColor, QFont, QPixmap
+from PyQt5.QtCore import Qt
+sys.path.append(os.getcwd()+'/../view')
+from GUI_FastaViewer import GUI_FastaViewer # noqa E402
+from ConfigView import ConfigView # noqa E402
+from mzMLTableView import mzMLTableView # noqa E402
+from MultipleSpecView import MultipleSpecView # noqa E402
+from mzTabTableWidget import Window as mzTabTableWidget # noqa E402
+sys.path.append(os.getcwd() + '/../model')
+from tableDataFrame import TableDataFrame as Tdf # noqa E402
+sys.path.append(os.getcwd() + '/../controller')
+from filehandler import FileHandler as fh # noqa E402
+sys.path.insert(0, '../examples')
+from descriptions import Descriptions as desc
+
+class ProteinQuantification(QMainWindow):
+ """
+ Application to use different Widgets in one Window:
+ First Tab: Welcome tab with information about how the GUI works.
+ Second Tab: Config view - here the .ini file can be viewed and edited
+ Third Tab: mzMLTable view - the experimental design can be
+ viewed and edited.
+ Fourth Tab: Fasta view - fasta files can be loaded and inspected
+ Fifth Tab: Spec view - ms spectras from loaded mzML files can be seen
+ and inspected
+ Sixth Tab: mzTabTable view - The result of the ProteomicsLFQ
+ is displayed
+ """
+
+ def __init__(self):
+ QMainWindow.__init__(self)
+ self.initUI()
+ self.initVars()
+ # flag for themetoggle
+ self.flag = False
+ self.setPalette(self.palette)
+ self.setTheme()
+ self.setAcceptDrops(True)
+
+ def initUI(self):
+ '''
+ Sets the window with all applications and widgets.
+ '''
+ descriptions = desc.descriptions
+ widgetlist = {"Welcome":[QWidget(),"welcome"],"XML-Viewer":[ConfigView(),"cview"],"Experimental-Design":[mzMLTableView(),"tview"],"Fasta-Viewer":[GUI_FastaViewer(),"fview"],"Spec-Viewer":[MultipleSpecView(),"sview"],"mzTab-Viewer":[mzTabTableWidget(),"xview"]}
+ self.view = QTabWidget()
+ for wname in widgetlist:
+ a = widgetlist[wname][0]
+ setattr(self, widgetlist[wname][1],a )
+ self.view.addTab(a, wname)
+
+ self.view.setTabEnabled(5, False)
+
+ self.palette = QPalette()
+
+ menubar = self.menuBar()
+ menubar.setNativeMenuBar(False)
+ projectMenu = menubar.addMenu('Project')
+ parametersMenu = menubar.addMenu('Parameters')
+ loadAction = QAction(QIcon("Icons/load_icon.png"),
+ "&Load Project", self)
+ loadAction.setShortcut("Ctrl+O")
+ saveAction = QAction(QIcon("Icons/save_icon.png"),
+ "&Save Project", self)
+ saveAction.setShortcut("Ctrl+S")
+ runAction = QAction(QIcon("Icons/run_icon.png"),
+ "&Run in Terminal", self)
+ runAction.setShortcut("Ctrl+R")
+ Threads = QAction("&Adjust the Threadnumber", self)
+ FDR = QAction("&Adjust the protein FDR", self)
+ Out = QAction("&Choose outputfiles", self)
+
+ projectMenu.addAction(loadAction)
+ projectMenu.addAction(saveAction)
+ projectMenu.addAction(runAction)
+ parametersMenu.addAction(Threads)
+ parametersMenu.addAction(FDR)
+ parametersMenu.addAction(Out)
+
+ runAction.triggered.connect(self.runFunction)
+ FDR.triggered.connect(self.adjustFDR)
+ Threads.triggered.connect(self.adjustThreads)
+ Out.triggered.connect(self.chooseOutputfiles)
+
+ saveAction.triggered.connect(self.saveFunction)
+ loadAction.triggered.connect(self.loadFunction)
+
+ #themeswitcher
+ settingsMenu = menubar.addMenu('Settings')
+ switchThemeAction = QAction('Change Theme', self)
+ settingsMenu.addAction(switchThemeAction)
+ switchThemeAction.triggered.connect(self.switchTheme)
+
+ # Welcome Tab
+ normalFont = QFont("Helvetica", 11)
+ welcome = QLabel()
+ welcome.setText(descriptions["welcome"])
+ welcome.setFont(normalFont)
+ welcome_layout = QVBoxLayout()
+ welcome_layout.addWidget(welcome, 2, Qt.AlignTop)
+
+ iconOpenMs = QPixmap("Icons/IconOpenMS.png")
+ iconLabel = QLabel()
+ iconLabel.setPixmap(iconOpenMs)
+
+ welcome_layout.addWidget(iconLabel, 4, Qt.AlignTop)
+ center_layout = QVBoxLayout()
+ view= self.view
+
+ for i in range(1,view.count()):
+ print(view.tabText(i))
+ label = QLabel()
+ label.setText(descriptions[view.tabText(i)])
+ label.setFont(normalFont)
+ if i==1:
+ center_layout.addWidget(label,4,Qt.AlignTop)
+ else:
+ center_layout.addWidget(label,4)
+
+ central_layout = QHBoxLayout()
+ central_layout.addLayout(welcome_layout, 5)
+ central_layout.addLayout(center_layout, 5)
+
+ self.welcome.setLayout(central_layout)
+
+ self.setCentralWidget(self.view)
+ self.resize(1280, 720)
+ self.center()
+ self.setWindowTitle('Protein Quantification')
+ self.show()
+ # print(self.view.count())
+ self.view.currentChanged.connect(self.onChange)
+
+ def initVars(self):
+ """
+ initiates usable variables
+ """
+ self.ini_loaded = False
+ self.tablefile_loaded = False
+ self.fasta_loaded = False
+ self.mztab_loaded = False
+ self.cxml_out = True
+ self.msstats_out = True
+
+ self.loaded_dir = ""
+ self.loaded_ini = ""
+ self.loaded_table = ""
+ self.loaded_fasta = ""
+ self.loaded_mztab = ""
+
+ self.threads = 1
+ self.fdr = 0.3
+ self.procdone = False
+
+ def center(self):
+ """
+ centers the widget to the screen
+ """
+ qr = self.frameGeometry()
+ cp = QDesktopWidget().availableGeometry().center()
+ qr.moveCenter(cp)
+ self.move(qr.topLeft())
+
+ def chooseOutputfiles(self):
+ """
+ Opens a popup window to choose the outputfiles
+ If output is generated checkbox is checked.
+ """
+ # Popup
+ self.outputCheckBoxWindow = PopupWindow()
+ mainWidget = QWidget()
+ mztabCheckbox = QCheckBox("Generate a mzTab outputfile")
+ cxmlCheckbox = QCheckBox("Generate a cxml outputfile")
+ msstatsCheckbox = QCheckBox("Generate a msstats outputfile")
+ layout = QVBoxLayout()
+ layout.addWidget(mztabCheckbox)
+ layout.addWidget(cxmlCheckbox)
+ layout.addWidget(msstatsCheckbox)
+ mainWidget.setLayout(layout)
+ self.outputCheckBoxWindow.setCentralWidget(mainWidget)
+ self.outputCheckBoxWindow.setTitle("Choose Outputfiles")
+
+ # Checkboxstates
+ mztabCheckbox.setChecked(True)
+ mztabCheckbox.setEnabled(False)
+
+ if self.cxml_out:
+ cxmlCheckbox.setChecked(True)
+
+ if self.msstats_out:
+ msstatsCheckbox.setChecked(True)
+
+ # Change Checkbox
+ cxmlCheckbox.clicked.connect(self.togglecxml)
+ msstatsCheckbox.clicked.connect(self.togglemsstats)
+
+ def togglecxml(self):
+ if self.cxml_out:
+ self.cxml_out = False
+ else:
+ self.cxml_out = True
+
+ def togglemsstats(self):
+ if self.msstats_out:
+ self.msstats_out = False
+ else:
+ self.msstats_out = True
+
+ def adjustFDR(self):
+ """
+ The user is allowed to change th FDR
+ """
+ newfdr, ok = QInputDialog.getDouble(
+ self, "Adjust the value for the protein FDR",
+ "Please specify a double as new FDR value")
+ if ok:
+ if newfdr > 0:
+ if newfdr <= 1:
+ self.fdr = newfdr
+ else:
+ QMessageBox.about(self, "Warning",
+ "Please specify a value" +
+ "between 0.0 and 1.0 for the FDR.")
+ else:
+ QMessageBox.about(self, "Warning",
+ "Please specify a positive" +
+ "value for the FDR.")
+
+ def adjustThreads(self):
+ """
+ The user is allowed to change the number of threads
+ """
+ newthreads, ok = QInputDialog.getInt(
+ self, "Adjust the number of threads",
+ "Please specify the number of threads for the processing")
+ if ok:
+ if newthreads > 0:
+ self.threads = newthreads
+ else:
+ QMessageBox.about(self, "Warning",
+ "Please specify a positive" +
+ "number of threads.")
+
+ def runFunction(self):
+ """
+ runs the processing from the GUI in a Terminal
+ based on the ProteomicsLFQ command of OpenMS
+ """
+ self.view.setTabEnabled(5, True)
+ self.saveFunction()
+ self.procdone = False
+ outfileprefix, ok = QInputDialog.getText(self,
+ "Prefix for outputfiles",
+ "Please specify a prefix " +
+ "for the outputfiles")
+ if ok:
+
+ projectfolder = self.loaded_dir
+
+ mzMLExpLayout = self.tview.getDataFrame()
+ try:
+ mzMLfiles = mzMLExpLayout['Spectra_Filepath']
+ idXMLfiles = []
+ for mzML in mzMLfiles:
+ temp = mzML.split(".")
+ idXML = temp[0] + ".idXML"
+ idXMLfiles.append(idXML)
+ mzMLidXMLdefined = True
+ except KeyError:
+ QMessageBox.about(self, "Warning", "Please load or " +
+ "create an Experimental Design first")
+ mzMLidXMLdefined = False
+
+ expdesign = self.loaded_table
+ dbfasta = self.loaded_fasta
+ inifile = self.loaded_ini
+
+ if mzMLidXMLdefined:
+ runcall = "ProteomicsLFQ "
+ mzMLs = "-in " + " ".join(mzMLfiles)
+ idXMLs = " -ids " + " ".join(idXMLfiles)
+ design = " -design " + expdesign + " "
+ refdb = "-fasta " + dbfasta + " "
+ configini = "-ini " + inifile + " "
+ threads = "-threads " + str(self.threads) + " "
+ fdr = "-proteinFDR " + str(self.fdr) + " "
+ out = ""
+ if self.cxml_out:
+ out += "-out_cxml " + outfileprefix + ".consensusXML.tmp "
+ if self.msstats_out:
+ out += "-out_msstats " + outfileprefix + ".csv.tmp "
+
+ out += "-out " + outfileprefix + ".mzTab.tmp"
+
+ command = (runcall + mzMLs + idXMLs + design +
+ refdb + configini + threads + fdr + out)
+ os.chdir(projectfolder)
+ os.system(command)
+ self.procdone = True
+ QMessageBox.about(self, "Information", "Processing has been " +
+ "performed and outputfiles saved to " +
+ "projectfolder")
+ mztabfile = outfileprefix + ".mzTab.tmp"
+ print(mztabfile)
+ try:
+ self.xview.readFile(mztabfile)
+ self.loaded_mztab = mztabfile
+ self.mztab_loaded = True
+ self.view.setCurrentWidget(self.xview)
+ except FileNotFoundError:
+ QMessageBox.about(self, "Warning", "Some Error occurred " +
+ "and no mzTab could be found.")
+
+
+ def saveFunction(self):
+ """
+ saves all work from the GUI in chosen folder
+ the prefix of the outputfiles can be choosen
+ """
+ dlg = QFileDialog(self)
+ filePath = dlg.getExistingDirectory()
+
+ if filePath:
+ filePath = filePath + "/"
+ tablePath = ""
+ ok = False
+ table_empty = self.tview.table.rowCount() <= 0
+
+ if table_empty:
+ tablePath = ""
+ self.tablefile_loaded = False
+ self.tview.tablefile_loaded = False
+
+ if self.tablefile_loaded is False and table_empty is False \
+ and self.tview.tablefile_loaded is False:
+ prefix, ok = QInputDialog.getText(
+ self, "Prefix for outputfiles",
+ "Please specify a prefix " +
+ "for the outputfiles")
+ if ok:
+ tablePath = filePath + prefix + "_design.tsv"
+ self.loaded_table = prefix + "_design.tsv"
+ self.tablefile_loaded = True
+
+ if self.tablefile_loaded and self.tview.tablefile_loaded is False:
+ tablePath = filePath + self.loaded_table
+
+ if self.tview.tablefile_loaded:
+ tablePath = filePath + self.tview.loaded_table
+
+ if (ok or self.tablefile_loaded or self.tview.tablefile_loaded) \
+ and table_empty is False:
+ df = Tdf.getTable(self.tview)
+ fh.exportTable(self.tview, df, tablePath, "tsv")
+
+ xmlPath = filePath + self.loaded_ini
+ try:
+ self.cview.tree.write(xmlPath)
+ except TypeError:
+ print("No Config loaded to be saved!")
+
+ if self.loaded_ini != "" and tablePath.split("/")[-1] != "":
+ QMessageBox.about(self, "Successfully saved!",
+ "Files have been saved as: " +
+ self.loaded_ini + ", " +
+ tablePath.split("/")[-1])
+ elif self.loaded_ini != "":
+ QMessageBox.about(self, "Successfully saved!",
+ "ini has been saved as: " +
+ self.loaded_ini)
+ elif tablePath.split("/")[-1] != "":
+ QMessageBox.about(self, "Successfully saved!",
+ "Table has been saved as: " +
+ tablePath.split("/")[-1])
+
+ def loadFunction(self, filePath: str=""):
+ """
+ loads all files (.tsv .ini, .fasta) from a given
+ directory.
+ If .tsv file is not present the experimental design is
+ filled with mzMl files
+
+ If no .ini file is present default ini file is written
+ and is loaded automatically
+ """
+ if not filePath:
+ dlg = QFileDialog(self)
+ filePath = dlg.getExistingDirectory()
+ self.loaded_dir = filePath
+ self.sview.fillTable(filePath)
+ if filePath:
+ try:
+ self.tsvfiles = glob.glob('*.tsv')
+ if len(self.tsvfiles) > 1:
+ QMessageBox.about(self, "Sorry!",
+ "There are multiple '.tsv-'"
+ "files in the specified folder. "
+ "Please choose the one you intent "
+ "to use.")
+ dial = QFileDialog(self)
+ newFilePath = dial.getOpenFileName(self,
+ "Choose .tsv",
+ filePath,
+ "Tables (*.tsv)")
+ if newFilePath[0] != '':
+ newFile = newFilePath[0].split("/")[-1]
+ self.tsvfiles = [newFile]
+ else:
+ QMessageBox.about(self, "Sorry!",
+ "Nothing was choosen. "
+ "Therefore no '.tsv'-file was "
+ "loaded. ")
+ self.tsvfiles = []
+ for file in self.tsvfiles:
+ df = fh.importTable(self.tview, file)
+ Tdf.setTable(self.tview, df)
+ self.tview.drawTable()
+ self.loaded_table = file
+ self.tablefile_loaded = True
+
+ except TypeError:
+ "No '.tsv' or '.csv'-file could be loaded."
+
+ if self.tablefile_loaded is False:
+ try:
+ self.tview.loadDir(filePath)
+ self.tablefile_loaded = True
+ except TypeError:
+ print("Could not load '.mzMl'-files.")
+
+ try:
+ self.iniFiles = glob.glob('*.ini')
+ if len(self.iniFiles) > 1:
+ QMessageBox.about(self, "Sorry!",
+ "There are multiple '.ini'-"
+ "files in the specified folder. "
+ "Please choose the one you intent "
+ "to use.")
+ dial = QFileDialog(self)
+ newFilePath = dial.getOpenFileName(self,
+ "Choose .ini",
+ filePath,
+ "Config (*.ini)")
+ if newFilePath[0] != '':
+ newFile = newFilePath[0].split("/")[-1]
+ self.iniFiles = [newFile]
+ else:
+ QMessageBox.about(self, "Sorry!",
+ "Nothing was choosen. "
+ "Therefore no '.ini'-file was "
+ "loaded. ")
+ self.iniFiles = []
+ for file in self.iniFiles:
+ self.cview.generateTreeModel(file)
+ self.loaded_ini = file
+ self.ini_loaded = True
+ except TypeError:
+ print("Could not load .ini file.")
+
+ if self.ini_loaded is False:
+ try:
+ runcall = "ProteomicsLFQ "
+ writeIniFile = "-write_ini "
+ out = "Config.ini"
+ command = (runcall + writeIniFile + out)
+ os.chdir(filePath)
+ os.system(command)
+ iniFiles = glob.glob('*.ini')
+ for file in iniFiles:
+ self.cview.generateTreeModel(file)
+ self.loaded_ini = file
+ self.ini_loaded = True
+ except TypeError:
+ print("Could not write and load default '.ini'-file.")
+
+ try:
+ self.fastafiles = glob.glob('*fasta')
+ if len(self.fastafiles) > 1:
+ QMessageBox.about(self, "Sorry!",
+ "There are multiple '.fasta'-"
+ "files in the specified folder. "
+ "Please choose the one you intent "
+ "to use.")
+ dial = QFileDialog(self)
+ newFilePath = dial.getOpenFileName(self,
+ "Choose .fasta",
+ filePath,
+ "Proteindata (*.fasta)")
+ if newFilePath[0] != '':
+ newFile = newFilePath[0].split("/")[-1]
+ self.fastafiles = [newFile]
+ else:
+ QMessageBox.about(self, "Sorry!",
+ "Nothing was choosen. "
+ "Therefore, no '.fasta'-file "
+ "was loaded. ")
+ self.fastafiles = []
+ for file in self.fastafiles:
+ self.fview.loadFile(file)
+ self.loaded_fasta = file
+ self.fasta_loaded = True
+ except TypeError:
+ print("Could not load '.fasta'-file.")
+
+ def setTheme(self):
+ """
+ Sets theme based on flag state, light or dark modes are possible.
+ Default is light theme.
+ """
+ p = self.palette
+ if not self.flag:
+ # lightmode
+ p.setColor(QPalette.Window, Qt.white)
+ p.setColor(QPalette.Background, Qt.white)
+ p.setColor(QPalette.WindowText, Qt.black)
+ p.setColor(QPalette.Base, Qt.white)
+ p.setColor(QPalette.AlternateBase, Qt.white)
+ p.setColor(QPalette.ToolTipBase, Qt.black)
+ p.setColor(QPalette.ToolTipText, Qt.black)
+ p.setColor(QPalette.Text, Qt.black)
+ p.setColor(QPalette.Button, Qt.white)
+ p.setColor(QPalette.ButtonText, Qt.black)
+ p.setColor(QPalette.BrightText, Qt.red)
+ p.setColor(QPalette.Link, QColor(213, 125, 37))
+ p.setColor(QPalette.Highlight, QColor(213, 125, 37))
+ p.setColor(QPalette.HighlightedText, Qt.white)
+ else:
+ # darkmode
+ p.setColor(QPalette.Window, QColor(53, 53, 53))
+ p.setColor(QPalette.WindowText, Qt.white)
+ p.setColor(QPalette.Base, QColor(25, 25, 25))
+ p.setColor(QPalette.AlternateBase, QColor(53, 53, 53))
+ p.setColor(QPalette.ToolTipBase, Qt.white)
+ p.setColor(QPalette.ToolTipText, Qt.white)
+ p.setColor(QPalette.Text, Qt.white)
+ p.setColor(QPalette.Button, QColor(53, 53, 53))
+ p.setColor(QPalette.ButtonText, Qt.white)
+ p.setColor(QPalette.BrightText, Qt.red)
+ p.setColor(QPalette.Link, QColor(42, 130, 218))
+ p.setColor(QPalette.Highlight, QColor(42, 130, 218))
+ p.setColor(QPalette.HighlightedText, Qt.black)
+ self.setPalette(p)
+
+ def dragEnterEvent(self, event):
+ e = event
+ data = e.mimeData()
+ urls = data.urls()
+
+ if urls and urls[0].scheme() == "file":
+ e.acceptProposedAction()
+ else:
+ e.ignore()
+
+ def dragMoveEvent(self, event):
+ e = event
+ data = e.mimeData()
+ urls = data.urls()
+
+ if urls and urls[0].scheme() == "file":
+ e.acceptProposedAction()
+ else:
+ e.ignore()
+
+ def dropEvent(self, event):
+ e = event
+ data = e.mimeData()
+ urls = data.urls()
+
+ if urls and urls[0].scheme() == "file":
+ #welcome page
+ if self.view.currentIndex() == 0:
+ filetype = "directory"
+ filepath = self.urlHandler(urls[0].path())
+ if os.path.isdir(filepath):
+ self.loadFunction(filepath)
+ else:
+ self.displayDragNDropError(filetype)
+ #xmlviewer
+ elif self.view.currentIndex() == 1:
+ files = [self.urlHandler(u.path()) for u in urls]
+ self.cview.dragDropEvent(files)
+ #experimental design
+ elif self.view.currentIndex() == 2:
+ filetype = ["mzML","tsv","csv"]
+ filepath = self.urlHandler(urls[0].path())
+ if filepath[-4:] == filetype[0]:
+ self.tview.loadFile(filepath)
+ elif (filepath[-3:] == filetype[1]) or (filepath[-3:] ==filetype[2]):
+ self.tview.importBtn(filepath)
+ else:
+ self.displayDragNDropError("",filetype)
+ #fasta viewer
+ elif self.view.currentIndex() == 3:
+ filepath = self.urlHandler(urls[0].path())
+ filetype = "fasta"
+ if filepath[-5:] == filetype:
+ self.fview.loadFile(filepath)
+ else:
+ self.displayDragNDropError(filetype)
+
+ #specviewer
+ elif self.view.currentIndex() == 4:
+ filetype = "mzML"
+ filepath = self.urlHandler(urls[0].path())
+ if filepath[-4:] == filetype:
+ self.sview.sview.openFileDialog(filepath)
+ else:
+ self.displayDragNDropError(filetype)
+ else:
+ e.ignore()
+ def displayDragNDropError(self, filetype:str, mul:list=[]):
+ """
+ displays an error message in a messagebox detailing what went wrong
+ """
+ message = ""
+ if not mul:
+ if filetype == "directory":
+ message = "a directory to load a project"
+ else:
+ message = "'."+filetype +"'-files"
+ else:
+ message += "'."+mul[0]+"'"
+ for file in mul[1:-1]:
+ message += ", '."+file +"'"
+ message += " or "+mul[-1] + "'-files"
+ dialog = QMessageBox()
+ dialog.setWindowTitle("Error: Invalid File")
+ dialog.setText("Please only use " + message)
+ dialog.setIcon(QMessageBox.Warning)
+ dialog.exec_()
+
+
+ def urlHandler(self, url):
+ opsys = platform.system()
+ if(opsys == "Linux"):
+ return str(url)
+ if(opsys == "Windows"):
+ return str(url)[1:]
+ if(opsys == "Darwin"):
+ return str(url) # to be tested
+
+ def onChange(self):
+ """
+ this function detects if a tab has been changed.
+ for debugging purposes.
+ """
+ print(self.view.currentIndex())
+
+ def switchTheme(self):
+ """
+ Toggles between dark and light theme.
+ """
+
+ self.flag = not self.flag
+ self.setTheme()
+
+
+class PopupWindow(QMainWindow):
+ """
+ Popup window with checkboxes for outputfiles.
+ """
+
+ def __init__(self):
+ QMainWindow.__init__(self)
+ self.resize(300, 100)
+ self.center()
+ self.show()
+
+ def center(self):
+ """
+ Centers the widget to the screen.
+ """
+ qr = self.frameGeometry()
+ cp = QDesktopWidget().availableGeometry().center()
+ qr.moveCenter(cp)
+ self.move(qr.topLeft())
+
+ def setTitle(self, title: str):
+ self.setWindowTitle(title)
+
+
+if __name__ == '__main__':
+
+ app = QApplication(sys.argv)
+ ex = ProteinQuantification()
+ app.setStyle("Fusion")
+ sys.exit(app.exec_())
diff --git a/src/apps/SpecViewer.py b/src/apps/SpecViewer.py
index cba916291..82459924d 100644
--- a/src/apps/SpecViewer.py
+++ b/src/apps/SpecViewer.py
@@ -1,5 +1,16 @@
import sys
-import pyopenms
+from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, \
+ QHBoxLayout, QWidget, QDesktopWidget, \
+ QAction, QFileDialog, QTableView, QSplitter, \
+ QMenu, QAbstractItemView, QPushButton
+from PyQt5.QtCore import Qt, QAbstractTableModel, pyqtSignal, \
+ QItemSelectionModel, QSortFilterProxyModel, QSignalMapper, QPoint, QRegExp
+
+import pyqtgraph as pg
+from pyqtgraph import PlotWidget
+
+import numpy as np
+
from collections import namedtuple
import pyqtgraph as pg
@@ -13,8 +24,9 @@
QFileDialog,
)
-sys.path.insert(0, "../view/")
-from ScanBrowserWidget import ScanBrowserWidget
+sys.path.insert(0, "../view")
+from ScanBrowserWidget import ScanBrowserWidget # noqa: E402
+
# structure for annotation (here for reference)
PeakAnnoStruct = namedtuple(
@@ -28,11 +40,12 @@
text_label_list color",
)
-pg.setConfigOption("background", "w") # white background
-pg.setConfigOption("foreground", "k") # black peaks
+pg.setConfigOption('background', 'w') # white background
+pg.setConfigOption('foreground', 'k') # black peaks
+
+class Specviewer(QMainWindow):
-class App(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.resize(1000, 700) # window size
@@ -42,11 +55,23 @@ def initUI(self):
self.setWindowTitle("pyOpenMSViewer")
# self.center()
- # layout
- self.setMainMenu()
self.centerWidget = QWidget(self)
self.setCentralWidget(self.centerWidget)
- self.windowLay = QVBoxLayout(self.centerWidget)
+ # main
+ mainLayout = QVBoxLayout(self.centerWidget)
+
+ # buttons
+ buttons = QVBoxLayout()
+ loadbtn = QPushButton('Load')
+ loadbtn.setMaximumWidth(80)
+ loadbtn.clicked.connect(self.openFileDialog)
+ buttons.addWidget(loadbtn)
+
+ mainLayout.addLayout(buttons)
+
+ # graph
+ self.windowLay = QVBoxLayout()
+ mainLayout.addLayout(self.windowLay)
# default widget <- per spectrum
self.setScanBrowserWidget()
@@ -57,42 +82,43 @@ def setScanBrowserWidget(self):
self.scanbrowser = ScanBrowserWidget(self)
self.windowLay.addWidget(self.scanbrowser)
- def setMainMenu(self):
- mainMenu = self.menuBar()
- mainMenu.setNativeMenuBar(False)
-
- self.titleMenu = mainMenu.addMenu("PyOpenMS")
- self.fileMenu = mainMenu.addMenu("File")
- # helpMenu = mainMenu.addMenu('Help')
- self.toolMenu = mainMenu.addMenu("Tools")
-
- self.setTitleMenu()
- self.setFileMenu()
- self.setToolMenu()
-
- def setTitleMenu(self):
- self.setExitButton()
-
- def setFileMenu(self):
- # open mzml file
- mzmlOpenAct = QAction("Open file", self)
- mzmlOpenAct.setShortcut("Ctrl+O")
- mzmlOpenAct.setStatusTip("Open new file")
- mzmlOpenAct.triggered.connect(self.openFileDialog)
- self.fileMenu.addAction(mzmlOpenAct)
-
- def setToolMenu(self):
- # for overriding
- return
+ # def setMainMenu(self):
+ # mainMenu = self.menuBar()
+ # mainMenu.setNativeMenuBar(False)
+ #
+ # self.titleMenu = mainMenu.addMenu('PyOpenMS')
+ # self.fileMenu = mainMenu.addMenu('File')
+ # # helpMenu = mainMenu.addMenu('Help')
+ # self.toolMenu = mainMenu.addMenu('Tools')
+ #
+ # self.setTitleMenu()
+ # self.setFileMenu()
+ # self.setToolMenu()
+ #
+ # def setTitleMenu(self):
+ # self.setExitButton()
+ #
+ # def setFileMenu(self):
+ # # open mzml file
+ # mzmlOpenAct = QAction('Open file', self)
+ # mzmlOpenAct.setShortcut('Ctrl+O')
+ # mzmlOpenAct.setStatusTip('Open new file')
+ # mzmlOpenAct.triggered.connect(self.openFileDialog)
+ # self.fileMenu.addAction(mzmlOpenAct)
+ #
+ # def setToolMenu(self):
+ # # for overriding
+ # return
def clearLayout(self, layout):
for i in reversed(range(layout.count())):
layout.itemAt(i).widget().setParent(None)
- def openFileDialog(self):
- fileName, _ = QFileDialog.getOpenFileName(
- self, "Open File ", "", "mzML Files (*.mzML)"
- )
+ def openFileDialog(self, fileName: str = ""):
+ if not fileName:
+ fileName, _ = QFileDialog.getOpenFileName(self,
+ "Open File ", "", "mzML Files (*.mzML)")
+
if fileName:
print("opening...", fileName)
self.setScanBrowserWidget()
@@ -122,7 +148,7 @@ def closeEvent(self, event):
event.accept()
-if __name__ == "__main__":
+if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
ex.show()
diff --git a/src/apps/TableEditor.py b/src/apps/TableEditor.py
new file mode 100644
index 000000000..8b383751d
--- /dev/null
+++ b/src/apps/TableEditor.py
@@ -0,0 +1,82 @@
+import sys, os
+import timeit
+from PyQt5.QtWidgets import QApplication, QMainWindow, QDesktopWidget, \
+ QMessageBox
+sys.path.append(os.getcwd()+'/../view')
+from mzMLTableView import mzMLTableView # noqa E402
+
+
+class TableEditor(QMainWindow):
+
+ def __init__(self):
+ self.testForTime = False
+ if self.testForTime:
+ starttime = timeit.default_timer()
+ print("Starttime of overall Initiation : ", starttime)
+ QMainWindow.__init__(self)
+ self.initUI()
+ self.setAcceptDrops(True)
+
+ def initUI(self):
+ '''
+ sets the window with all applications and widgets
+ '''
+ self.view = mzMLTableView(self)
+
+ self.setCentralWidget(self.view)
+
+ self.resize(1280, 720)
+ self.center()
+ self.setWindowTitle('ExperimentalDesign')
+ self.show()
+
+ def center(self):
+ """
+ centers the widget to the screen
+ """
+ qr = self.frameGeometry()
+ cp = QDesktopWidget().availableGeometry().center()
+ qr.moveCenter(cp)
+ self.move(qr.topLeft())
+
+ def dragEnterEvent(self, event):
+ e = event
+ data = e.mimeData()
+ urls = data.urls()
+ if urls and urls[0].scheme() == "file":
+ e.acceptProposedAction()
+ else:
+ e.ignore()
+
+ def dragMoveEvent(self, event):
+ e = event
+ data = e.mimeData()
+ urls = data.urls()
+ if urls and urls[0].scheme() == "file":
+ e.acceptProposedAction()
+ else:
+ e.ignore()
+
+ def dropEvent(self, event):
+ e = event
+ data = e.mimeData()
+ urls = data.urls()
+ if urls and urls[0].scheme() == "file":
+ filepath = str(urls[0].path())[1:]
+ if filepath[-4:] == "mzML":
+ self.view.loadFile(filepath)
+ else:
+ dialog = QMessageBox()
+ dialog.setWindowTitle("Error: Invalid File")
+ dialog.setText("Please only use .mzML files")
+ dialog.setIcon(QMessageBox.Warning)
+ dialog.exec_()
+ else:
+ e.ignore()
+
+
+if __name__ == '__main__':
+
+ app = QApplication(sys.argv)
+ ex = TableEditor()
+ sys.exit(app.exec_())
diff --git a/src/apps/XMLViewer.py b/src/apps/XMLViewer.py
new file mode 100644
index 000000000..8a8cac671
--- /dev/null
+++ b/src/apps/XMLViewer.py
@@ -0,0 +1,56 @@
+import sys
+import os
+from PyQt5.QtWidgets import QApplication, QMainWindow, \
+ QDesktopWidget
+sys.path.append(os.getcwd()+'/../view')
+from ConfigView import ConfigView # noqa E402
+
+
+class XMLViewer(QMainWindow):
+ """
+ Widget for visualizing configuration data
+ """
+
+ def __init__(self):
+ QMainWindow.__init__(self)
+ self.initUI()
+ self.setAcceptDrops(True)
+
+ def initUI(self):
+ '''
+ sets the window with all applications and widgets
+ which are loaded from the ConfigView.py file
+ '''
+ self.cview = ConfigView()
+
+ self.setCentralWidget(self.cview)
+ self.resize(800, 1000)
+ self.center()
+ self.setWindowTitle('ini File Viewer')
+ self.show()
+
+ def center(self):
+ """
+ centers the widget to the screen
+ """
+ qr = self.frameGeometry()
+ cp = QDesktopWidget().availableGeometry().center()
+ qr.moveCenter(cp)
+ self.move(qr.topLeft())
+
+ def dragEnterEvent(self, event):
+ if event.mimeData().hasUrls():
+ event.accept()
+ else:
+ event.ignore()
+
+ def dropEvent(self, event):
+ files = [u.path() for u in event.mimeData().urls()]
+ self.cview.dragDropEvent(files)
+
+
+if __name__ == '__main__':
+
+ app = QApplication(sys.argv)
+ ex = XMLViewer()
+ sys.exit(app.exec_())
diff --git a/src/controller/descriptions.py b/src/controller/descriptions.py
new file mode 100644
index 000000000..612982377
--- /dev/null
+++ b/src/controller/descriptions.py
@@ -0,0 +1,90 @@
+import os
+import sys
+import glob
+import pandas as pd
+import re
+
+
+class Descriptions:
+ """
+ Provides Descriptions for proteinquantification
+ """
+ def __init__(self, descriptions):
+ self.descriptions = descriptions
+
+ descriptions = {"welcome":
+ """
+
Welcome: Get started with your
+ Proteinquantifcation!
+
This application performs a
+ ProteomicsLFQ - Proteinquantification.
+ The files, needed to
+ run the application have to be loaded into
+ XML-Viewer,
+ Experimental-Design and Fasta-Viewer-Tabs,
+ respectively. Finally, the mzTab-Viewer-Tab
+ will show the result.
+
+ You can look at the spectra used for the
+ quantification in the Spec-Viewer-Tab.
+ """,
+ "XML-Viewer":
+ """
+
XML Viewer: In this
+ tab you can load, edit
+ and save your experiments config-file.
+ If no config-file is provided,
+ a default file will be generated,
+ which you can modify for the quantification.
+ This application only accepts '.ini'-files as
+ config-files.
+ """,
+ "Experimental-Design":
+ """
+
Experimental
+ Design: The Experimental-Design-Tab
+ enables you to load and edit an
+ experimental design.
+ You can edit specifics like number
+ of files, fractions or groups. '.tsv'
+ -files can be directly loaded as a
+ table. You can also add single
+ '.mzml'-files to the table, either
+ via drag'n'drop, or with the 'add file'
+ -button. You can filter the files
+ using the textfield in the upper right
+ corner.
+ """,
+ "Fasta-Viewer":
+ """
+
Fasta Viewer: In
+ this tab you can load a fasta file and search
+ for information about the sequences such
+ as protein-IDs and accession-numbers.
+ """,
+ "Spec-Viewer":
+ """
+
Spectrum Viewer: This
+ tab enables you to look at the mzML-spectra
+ your Mass-Spectrometer measured for your
+ samples. It loads all spectra files,
+ which are
+ located in your project folder.
+ """,
+ "mzTab-Viewer":
+ """
+
MzTab Viewer: The
+ results of your analysis is visualized
+ in the mzTab-Viewer.
+ Information about the identified sequences, such as
+ retention time, charge or mass to charge ratio
+ are listed in the table.
+ """,
+ }
+
+ def getDescription(self, widgetname:str) -> str:
+ """
+ Scans a provided directory and returns a list of all
+ the mzML files.
+ """
+ return self.descriptions.widgetname
\ No newline at end of file
diff --git a/src/controller/filehandler.py b/src/controller/filehandler.py
new file mode 100644
index 000000000..f5f030205
--- /dev/null
+++ b/src/controller/filehandler.py
@@ -0,0 +1,136 @@
+import os
+import sys
+import glob
+import pandas as pd
+import re
+
+files = []
+
+
+class FileHandler:
+ """
+ Filehandler class that provides support for file and directory interaction.
+ """
+ def __init__(self, path: str, files: list, delimiter: str):
+ self.path = path
+ self.files = files
+ self.delimiter = delimiter
+
+ def getFiles(self, path: str) -> list:
+ """
+ Scans a provided directory and returns a list of all
+ the mzML files.
+ """
+ os.chdir(path)
+ files = glob.glob('*.mzML')
+ return files
+
+ def tagfiles(self, files: list, delimiter: str = "_"):
+ """
+ Parses filenames in a list of files by a provided delimiter.
+ Default delimiter is _ .
+ Returns a list of tags.
+ """
+ tagproperty = {}
+ for file in files:
+ name = file
+ tags = []
+ file = file.split(".")[0]
+ tags = file.split(delimiter)
+ tagproperty[name] = tags
+ return tagproperty
+
+ def importTable(self, file: str) -> pd.DataFrame:
+ """
+ Imports a csv or tsv file and returns a panda-dataframe.
+ Returns false if filetype is not tsv or csv.
+ """
+ ftype = file.split(".")[1]
+
+ if ftype == "csv":
+ fimport = pd.read_csv(file)
+
+ elif ftype == "tsv":
+ fimport = pd.read_csv(file, sep='\t')
+ else:
+ return False
+ return fimport
+
+ def exportTable(self, table: pd.DataFrame,
+ filename: str, ftype: str = 'csv') -> bool:
+ """
+ Exports a panda-dataframe to the specified filetype.
+ builds a path from a provided filename, a provided path,
+ and the provided filetype.
+
+ Defaults:
+ If no path is specified, default is the current working directory.
+ If no filetype is specified, default is csv.
+
+ Returns:
+ Returns false if specified filetype is not supported.
+ Returns true if file was successfully written.
+ Returns an error if writing the file failed.
+ """
+
+ fullpath = filename
+ encodingOption = 'utf-8'
+ if ftype == 'tsv':
+ separator = "\t"
+ elif ftype == 'csv':
+ separator = ","
+ else:
+ return False
+ try:
+ with open(fullpath, 'w') as fileToWrite:
+ fileToWrite.write(table.to_csv(
+ sep=separator, index=False, encoding=encodingOption))
+ return True
+ except IOError:
+ e = sys.exc_info()[0]
+ return e
+
+ def createRawTable(self, tagdict: dict, inputdir: str):
+ """
+ Creates pandas-dataframe from prepared files dictionary, which
+ contains the filename and its tags (delimiter is _). Dataframes index
+ is integer based, header are the column names. Searches the
+ tags for regular expressions to automatically include fraction and
+ fraction group from filename.
+ Default regex:
+ Fraction-Group: FG or G
+ Fraction: F
+ """
+ columnregex = ["FG[0-9]+", "G[0-9]+", "F[0-9]+"]
+ header = ['Fraction_Group', 'Fraction',
+ 'Spectra_Filepath', 'Label', 'Sample']
+ index = []
+ rows = []
+ filenames = tagdict.keys()
+ for file in filenames:
+ index.append(file)
+ filtered_tags = {'Fraction_Group': 0, 'Fraction': 0,
+ 'Spectra_Filepath': str(inputdir + "/" + file),
+ 'Label': 0, 'Sample': 0}
+ filetags = tagdict[file]
+ for tag in filetags:
+ if re.match(columnregex[0], tag):
+ filtered_tags['Fraction_Group'] = tag.split('FG')[1]
+ elif re.match(columnregex[1], tag):
+ filtered_tags['Fraction_Group'] = tag.split('G')[1]
+ elif re.match(columnregex[2], tag):
+ filtered_tags['Fraction'] = tag.split('F')[1]
+ else:
+ continue
+ rows.append([filtered_tags[i]for i in header])
+ rawtable = pd.DataFrame(rows, columns=header)
+ return rawtable
+
+
+# just for testing
+# i = input("filepath: ")
+# delimiters = ["_"]
+# files = getFiles(i)
+# preparedfiles = tagfiles(files, delimiters[0])
+# rawtable = createRawTable(preparedfiles, i)
+# print(rawtable)
diff --git a/src/examples/GUI_ErrorWidget.py b/src/examples/GUI_ErrorWidget.py
index 2361cb33e..8f142cfa6 100644
--- a/src/examples/GUI_ErrorWidget.py
+++ b/src/examples/GUI_ErrorWidget.py
@@ -5,7 +5,7 @@
from PyQt5.QtWidgets import QApplication
sys.path.insert(0, "../view")
-from ErrorWidget import ErrorWidget
+from ErrorWidget import ErrorWidget # noqa: E402
if __name__ == "__main__":
app = QApplication(sys.argv)
diff --git a/src/examples/GUI_MS1MapWidget.py b/src/examples/GUI_MS1MapWidget.py
index 9c631c2b2..273c81e26 100644
--- a/src/examples/GUI_MS1MapWidget.py
+++ b/src/examples/GUI_MS1MapWidget.py
@@ -5,7 +5,7 @@
from PyQt5.QtWidgets import QApplication
sys.path.insert(0, "../view")
-from MS1MapWidget import MS1MapWidget
+from MS1MapWidget import MS1MapWidget # noqa: E402
if __name__ == "__main__":
app = QApplication(sys.argv)
diff --git a/src/examples/GUI_ScanBrowserWidget.py b/src/examples/GUI_ScanBrowserWidget.py
index 723cb7d8b..55e3f634a 100644
--- a/src/examples/GUI_ScanBrowserWidget.py
+++ b/src/examples/GUI_ScanBrowserWidget.py
@@ -5,7 +5,7 @@
from PyQt5.QtWidgets import QApplication
sys.path.insert(0, "../view")
-from ScanBrowserWidget import ScanBrowserWidget
+from ScanBrowserWidget import ScanBrowserWidget # noqa: E402
if __name__ == "__main__":
app = QApplication(sys.argv)
diff --git a/src/examples/GUI_ScanTableWidget.py b/src/examples/GUI_ScanTableWidget.py
index 10046746d..6b698754f 100644
--- a/src/examples/GUI_ScanTableWidget.py
+++ b/src/examples/GUI_ScanTableWidget.py
@@ -5,7 +5,7 @@
from PyQt5.QtWidgets import QApplication
sys.path.insert(0, "../view")
-from ScanTableWidget import ScanTableWidget
+from ScanTableWidget import ScanTableWidget # noqa: E402
if __name__ == "__main__":
app = QApplication(sys.argv)
diff --git a/src/examples/GUI_SequenceIonsWidget.py b/src/examples/GUI_SequenceIonsWidget.py
index 4c363227a..1885211d0 100644
--- a/src/examples/GUI_SequenceIonsWidget.py
+++ b/src/examples/GUI_SequenceIonsWidget.py
@@ -2,7 +2,7 @@
from GUI_EXAMPLE_BASE import GUI_EXAMPLE_BASE
from PyQt5.QtWidgets import QApplication
sys.path.insert(0, "../view")
-from SequenceIonsWidget import SequenceIonsWidget
+from SequenceIonsWidget import SequenceIonsWidget # noqa: E402
if __name__ == "__main__":
app = QApplication(sys.argv)
diff --git a/src/examples/GUI_SpectrumWidget.py b/src/examples/GUI_SpectrumWidget.py
index 4fed25a2e..807562ef1 100644
--- a/src/examples/GUI_SpectrumWidget.py
+++ b/src/examples/GUI_SpectrumWidget.py
@@ -5,7 +5,7 @@
from PyQt5.QtWidgets import QApplication
sys.path.insert(0, "../view")
-from SpectrumWidget import SpectrumWidget
+from SpectrumWidget import SpectrumWidget # noqa: E402
if __name__ == "__main__":
app = QApplication(sys.argv)
diff --git a/src/examples/GUI_TICWidget.py b/src/examples/GUI_TICWidget.py
index 2bfdc657f..b7f056f1e 100644
--- a/src/examples/GUI_TICWidget.py
+++ b/src/examples/GUI_TICWidget.py
@@ -6,7 +6,7 @@
from PyQt5.QtWidgets import QApplication
sys.path.insert(0, "../view")
-from TICWidget import TICWidget
+from TICWidget import TICWidget # noqa: E402
# mock object to test the mouse click signal by TICWidget
diff --git a/src/examples/PhosphoScoring.py b/src/examples/PhosphoScoring.py
index b1a951ed7..dc0e88d05 100644
--- a/src/examples/PhosphoScoring.py
+++ b/src/examples/PhosphoScoring.py
@@ -29,7 +29,6 @@
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
import re
-
import pyopenms
@@ -124,7 +123,7 @@ def simplisticBinnedScoring(self, phit, spectrum):
possibilities.append([comp_score, new_aaseq])
# Sort the result by score, return the best scoring result
- possibilities.sort(lambda x, y: -cmp(x[0], y[0]))
+ possibilities.sort(lambda x, y: -cmp(x[0], y[0])) # noqa
return possibilities[0]
def compare_binnedSpectra(self, sp1, sp2):
diff --git a/src/examples/data/OpenPepXLLF_input2.ini b/src/examples/data/OpenPepXLLF_input2.ini
new file mode 100644
index 000000000..34e89df0a
--- /dev/null
+++ b/src/examples/data/OpenPepXLLF_input2.ini
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/model/tableDataFrame.py b/src/model/tableDataFrame.py
new file mode 100644
index 000000000..83b71106f
--- /dev/null
+++ b/src/model/tableDataFrame.py
@@ -0,0 +1,103 @@
+import pandas as pd
+
+
+class TableDataFrame():
+
+ def __init__(self, df: pd.DataFrame):
+ self.df = df
+
+ def getTable(self) -> pd.DataFrame:
+ """
+ Returns the current dataframe
+ """
+ return self.df
+
+ def setTable(self, df: pd.DataFrame):
+ """
+ Sets the dataframe to the given
+ """
+ self.df = df
+
+ def modifyGroup(self, rows: list, groupnum: int):
+ """
+ Let the user set the group for a list of selected rows.
+ Needs a list of selected rows and the integer to which
+ the group is set.
+ """
+ for row in rows:
+ self.df.at[row, 'Fraction_Group'] = groupnum
+
+ def modifyFraction(self, rows: list, *argv: int):
+ """
+ Let the user set the fraction for a list of selected rows.
+ when only one number is given in argv one fraction is set
+ to all selected entries, else it will count to from min to max
+ and sets the group accordingly.
+ Takes a list of selected rows and the number(s) how the fractions
+ should be set.
+ """
+ if len(argv) == 1:
+ for row in rows:
+ self.df.at[row, 'Fraction'] = argv[0]
+ elif len(argv) == 2:
+ count = 0
+ for row in rows:
+ fracnum = argv[0] + count
+ self.df.at[row, 'Fraction'] = fracnum
+ if fracnum >= argv[1]:
+ count = 0
+ else:
+ count += 1
+
+ def modifyLabelSample(self, labelnum: int, continuous: bool):
+ """
+ Let the user change the multiplicity of the selected rows
+ continuous should be boolean if true samplenumber counts
+ through for all fraction groups otherwise it will start at
+ 1 for each fraction group
+ Takes the number of labels and a boolean, which sets the option
+ of continuing the samplenumber over groups.
+ """
+ # generate new dataframe from rows and copied rows of labels
+ ndf = []
+ for row in self.df.index:
+ for cl in range(labelnum):
+ label = cl+1
+ self.df.at[row, 'Label'] = label
+ dfrow = self.df.loc[[row]]
+ ndf.append(dfrow)
+ ndf = pd.concat(ndf, ignore_index=True)
+
+ # associate the samplenum to the label
+ if continuous:
+ prevsample = 0
+ for row in ndf.index:
+ if (row == 0):
+ samplenum = 1
+ ndf.at[row, 'Sample'] = samplenum
+ elif(ndf.at[row, 'Fraction_Group'] !=
+ ndf.at[row-1, 'Fraction_Group']):
+ prevsample = ndf.at[row-1, 'Sample']
+ samplenum = ndf.at[row, 'Label'] + prevsample
+ ndf.at[row, 'Sample'] = samplenum
+ else:
+ samplenum = ndf.at[row, 'Label'] + prevsample
+ ndf.at[row, 'Sample'] = samplenum
+ else:
+ for row in ndf.index:
+ samplenum = ndf.at[row, 'Label']
+ ndf.at[row, 'Sample'] = samplenum
+
+ self.df = ndf
+
+ def rmvRow(self, rows: list):
+ """
+ Let the user remove a list of rows.
+ Takes list of selected rows.
+ """
+ self.df.drop(rows, inplace=True)
+ self.df.reset_index(drop=True, inplace=True)
+
+ def modifyField(self, row: int, column: int, newvalue: int):
+ col = self.df.columns[column]
+ self.df.at[row, col] = newvalue
diff --git a/src/view/ConfigView.py b/src/view/ConfigView.py
new file mode 100644
index 000000000..c434bae1d
--- /dev/null
+++ b/src/view/ConfigView.py
@@ -0,0 +1,394 @@
+from PyQt5.QtWidgets import QWidget, QVBoxLayout, QTreeWidget, \
+ QTreeWidgetItem, QFileDialog, QPushButton, QHBoxLayout, \
+ QPlainTextEdit, QCheckBox, QHeaderView, QMessageBox, \
+ QInputDialog
+from PyQt5.QtCore import Qt
+import xml.etree.ElementTree as ET
+from defusedxml.ElementTree import parse
+from functools import partial
+
+
+class ConfigView(QWidget):
+ def __init__(self, *args):
+ QWidget.__init__(self, *args)
+ self.initTreeModel()
+ self.initTreeWidget()
+ self.initUI()
+
+ def initTreeModel(self):
+ """
+ Initialise the tree model using a fixed header
+ Columns are defined as constants
+ """
+ self.tree = ET.ElementTree
+ self.header = ['Name', 'Value', 'Type', 'Restrictions']
+ self.NAMECOL = 0
+ self.VALUECOL = 1
+ self.TYPECOL = 2
+ self.RESTRICTIONCOL = 3
+ self.descriptions = {}
+ self.drawTree = False
+
+ def initTreeWidget(self):
+ """
+ Initialise the TreeWidget, with corresponding header
+ A change listener is implemented and initialised here too
+ """
+ self.treeWidget = QTreeWidget(self)
+ self.treeWidget.setHeaderLabels(self.header)
+
+ self.header = self.treeWidget.header()
+ self.header.resizeSection(0, 150)
+ self.header.resizeSection(1, 150)
+ self.header.resizeSection(2, 150)
+
+ self.treeWidget.itemSelectionChanged.connect(self.loadDescription)
+ self.changeListener()
+
+ def initUI(self):
+ """
+ Initialise the GUI with buttons, checkbox, textbox and treewidget
+ """
+ self.loadbtn = QPushButton('Load')
+ self.loadbtn.setMaximumWidth(80)
+ self.loadbtn.clicked.connect(self.openXML)
+ self.loadbtn.setToolTip("Load a .ini file to display and " +
+ "modify the configuration of your processing.")
+
+ self.savebtn = QPushButton('Save')
+ self.savebtn.setMaximumWidth(80)
+ self.savebtn.clicked.connect(self.saveFile)
+ self.savebtn.setToolTip("Save the modified " +
+ "configuration as .ini file.")
+
+ self.checkbox = QCheckBox('Show advanced parameters')
+ self.checkbox.setChecked(True)
+ self.checkbox.stateChanged.connect(self.drawTreeInit)
+ self.checkbox.setToolTip("Shows or hides parameters, " +
+ "which are tagged as advance in " +
+ "the .ini configuration file.")
+
+ self.textbox = QPlainTextEdit(self)
+ self.textbox.setReadOnly(True)
+
+ btnlayout = QHBoxLayout()
+ layout = QVBoxLayout()
+
+ btns = QWidget(self)
+ btnlayout.addWidget(self.loadbtn)
+ btnlayout.addWidget(self.savebtn)
+ btnlayout.addWidget(self.checkbox)
+ btns.setLayout(btnlayout)
+ btns.setFixedWidth(500)
+
+ layout.addWidget(self.treeWidget, 6)
+ layout.addWidget(self.textbox, 1)
+ layout.addWidget(btns, 0.5)
+
+ self.setLayout(layout)
+ self.resize(500, 720)
+
+ def dragDropEvent(self, files: list):
+ """
+ Gets the input from the main Application.
+ The function chooses if the dragged files are
+ valid for the ConfigView Widget
+ """
+ if len(files) > 1:
+ QMessageBox.about(self, "Warning",
+ "Please only use one file")
+ else:
+ if ".ini" not in files[0]:
+ QMessageBox.about(self, "Warning",
+ "Please only use .ini files.")
+ else:
+ file = str(files[0])
+ self.generateTreeModel(file)
+
+ def openXML(self):
+ """
+ Loads a XML file with .ini tag, parses the xml into ET.ElementTree
+ calls the drawTree function to draw a Tree with the loaded xml
+ """
+ file, _ = QFileDialog.getOpenFileName(
+ self, "QFileDialog.getOpenFileName()", "",
+ "All Files (*);;ini (*.ini)")
+ if file:
+ self.generateTreeModel(file)
+
+ def generateTreeModel(self, file: str):
+ """
+ Function to parse the xml .ini file to a tree model
+ Also initialises the TreeWidget
+ """
+ self.tree = parse(file)
+ self.root = self.tree.getroot()
+ self.drawTreeInit()
+ self.header.setSectionResizeMode(QHeaderView.ResizeToContents)
+
+ def generateTreeWidgetItem(self, item: ET.Element) -> QTreeWidgetItem:
+ """
+ generates a QTreeWidgetItem with each column for an
+ ET.Element (e.g. root)
+ """
+ treeitem = QTreeWidgetItem()
+ treeitem.setFlags(treeitem.flags() | Qt.ItemIsEditable)
+ try:
+ treeitem.setText(0, item.attrib['name'])
+ except KeyError:
+ pass
+ try:
+ treeitem.setText(1, item.attrib['value'])
+ except KeyError:
+ pass
+ try:
+ treeitem.setText(2, item.attrib['type'])
+ except KeyError:
+ pass
+ try:
+ treeitem.setText(3, item.attrib['restrictions'])
+ except KeyError:
+ pass
+ try:
+ self.descriptions[item.attrib['name']] = item.attrib['description']
+ except KeyError:
+ pass
+
+ return treeitem
+
+ def drawTreeInit(self):
+ """
+ Initialises the treewidget, add the top level item
+ and starts the main recursion
+ """
+ self.drawTreeActive = True
+ self.additembtns = {}
+ self.treeWidget.clear()
+ root = self.tree.getroot()
+ for child in root:
+ if self.checkbox.isChecked():
+ childitem = self.generateTreeWidgetItem(child)
+ self.treeWidget.addTopLevelItem(childitem)
+ if len(child.getchildren()) > 0:
+ self.drawTreeRecursive(childitem, child)
+ else:
+ try:
+ if child.attrib['advanced'] == 'false':
+ childitem = self.generateTreeWidgetItem(child)
+ self.treeWidget.addTopLevelItem(childitem)
+ if len(child.getchildren()) > 0:
+ self.drawTreeRecursive(childitem, child)
+ except KeyError:
+ childitem = self.generateTreeWidgetItem(child)
+ self.treeWidget.addTopLevelItem(childitem)
+ if len(child.getchildren()) > 0:
+ self.drawTreeRecursive(childitem, child)
+ self.treeWidget.expandAll()
+ self.header.setSectionResizeMode(QHeaderView.ResizeToContents)
+ for btn in self.additembtns.keys():
+ self.additembtns[btn].clicked.connect(
+ partial(self.addItemToItemList, btn))
+ self.drawTreeActive = False
+
+ def drawTreeRecursive(self, nodeitem: QTreeWidgetItem, node: ET.Element):
+ """
+ Draws a tree for the loaded XML file
+ The checkbox "show advanced options" is implemented here as well
+ it will only draw those items, which have the according advanced flag
+ """
+ for subnode in node:
+ if self.checkbox.isChecked():
+ subitem = self.generateTreeWidgetItem(subnode)
+ nodeitem.addChild(subitem)
+ if len(subnode.getchildren()) > 0:
+ self.drawTreeRecursive(subitem, subnode)
+ else:
+ try:
+ if subnode.attrib['advanced'] == 'false':
+ subitem = self.generateTreeWidgetItem(subnode)
+ nodeitem.addChild(subitem)
+ if len(subnode.getchildren()) > 0:
+ self.drawTreeRecursive(subitem, subnode)
+ except KeyError:
+ subitem = self.generateTreeWidgetItem(subnode)
+ nodeitem.addChild(subitem)
+ if len(subnode.getchildren()) > 0:
+ self.drawTreeRecursive(subitem, subnode)
+
+ if subnode.tag == "ITEMLIST":
+ newbtn = QPushButton('Add New')
+ newbtn.setFixedSize(100, 20)
+ newbtn.setToolTip("Add new Item to the Itemlist, " +
+ "according to type and restrictions.")
+ listname = subnode.attrib['name']
+ self.additembtns[listname] = newbtn
+ self.treeWidget.setItemWidget(subitem, 1,
+ self.additembtns[listname])
+
+ def loadDescription(self):
+ """
+ Shows the description of the configuration parameter in the textbox
+ """
+ getSelected = self.treeWidget.selectedItems()
+ if getSelected:
+ try:
+ node = getSelected[0].text(0)
+ self.textbox.setPlainText(self.descriptions[node])
+ except KeyError:
+ node = getSelected[0].parent().text(0)
+ self.textbox.setPlainText(self.descriptions[node])
+
+ def changeListener(self):
+ """
+ Change Listener for the tree widget
+ to connect the model with the widget
+ """
+ self.treeWidget.itemChanged.connect(self.editField)
+
+ def editField(self):
+ """
+ Fields in model are edited upon change of treewidget
+ Edit is only allowed if restrictions are checked and type is correct
+ """
+ if not self.drawTreeActive:
+ if self.treeWidget.currentColumn() == self.VALUECOL:
+ itemchanged = self.treeWidget.currentItem()
+ itemparent = itemchanged.parent()
+ changeditemindex = itemparent.indexOfChild(itemchanged)
+ parentname = itemparent.text(self.NAMECOL)
+ parentres = itemparent.text(self.RESTRICTIONCOL)
+ parenttype = itemparent.text(self.TYPECOL)
+ itemname = itemchanged.text(self.NAMECOL)
+ newvalue = itemchanged.text(self.VALUECOL)
+ restrictions = itemchanged.text(self.RESTRICTIONCOL)
+ types = itemchanged.text(self.TYPECOL)
+
+ parentcheck = False
+ for itemlist in self.tree.iter('ITEMLIST'):
+ if str(itemlist.attrib['name']) == str(parentname):
+ parentcheck = True
+
+ if parentcheck:
+ reschecked = self.checkRestrictionString(
+ newvalue, parentres)
+ typechecked = self.checkTypeRestrictions(
+ newvalue, parenttype)
+ else:
+ reschecked = self.checkRestrictionString(
+ newvalue, restrictions)
+ typechecked = self.checkTypeRestrictions(
+ newvalue, types)
+
+ if reschecked and typechecked:
+ for parent in self.tree.iter('NODE'):
+ if parent.attrib['name'] == parentname:
+ for child in parent:
+ if child.attrib['name'] == itemname:
+ child.attrib['value'] = newvalue
+ for parent in self.tree.iter('ITEMLIST'):
+ if parent.attrib['name'] == parentname:
+ for child, childindex in zip(parent,
+ range(len(parent))):
+ if childindex == changeditemindex:
+ child.attrib['value'] = newvalue
+ elif typechecked and not reschecked:
+ QMessageBox.about(self, "Warning", "Please only, " +
+ "modify according to Restrictions")
+ else:
+ QMessageBox.about(self, "Warning", "Please only, " +
+ "modify according to Typerestrictions")
+ else:
+ QMessageBox.about(self, "Warning", "Please only, " +
+ "modify the Column: value")
+
+ self.drawTreeInit()
+
+ def checkRestrictionString(self,
+ newvalue: type, restrictions: str) -> bool:
+ """
+ Checks the restrictions of a item are matched when edited
+ """
+ if restrictions != "":
+ if newvalue not in restrictions:
+ reschecked = False
+ else:
+ reschecked = True
+ else:
+ reschecked = True
+
+ return reschecked
+
+ def checkTypeRestrictions(self, newvalue: type, types: str) -> bool:
+ """
+ Checks if the edit is corresponding to the type
+ """
+ if types != "":
+ if "-file" in types:
+ typechecked = True
+ else:
+ try:
+ float(newvalue)
+ if len(newvalue.split('.')) == 2:
+ valtype = "double"
+ else:
+ valtype = "int"
+ except ValueError:
+ valtype = "string"
+ if valtype not in types:
+ typechecked = False
+ else:
+ typechecked = True
+ else:
+ typechecked = True
+
+ return typechecked
+
+ def addItemToItemList(self, parentnodename: str):
+ """
+ Adds new Item to a ItemList parent, both in etree model and QTreeWidget
+ """
+ newdata, ok = QInputDialog.getText(self, "Add new row to List",
+ "Please input the new Parameter," +
+ "which should be added.")
+ if ok:
+ if newdata != "":
+ for itemlist in self.tree.iter('ITEMLIST'):
+ if itemlist.attrib['name'] == parentnodename:
+ try:
+ restrictions = itemlist.attrib['restrictions']
+ reschecked = self.checkRestrictionString(
+ newdata, restrictions)
+ except KeyError:
+ reschecked = True
+ try:
+ types = itemlist.attrib['type']
+ typechecked = self.checkTypeRestrictions(
+ newdata, types)
+ except KeyError:
+ typechecked = True
+ if reschecked and typechecked:
+ newelement = ET.Element(
+ "LISTITEM", {'value': newdata})
+ itemlist.append(newelement)
+ elif typechecked and not reschecked:
+ QMessageBox.about(
+ self, "Warning", "Please only, " +
+ "modify according to Restrictions")
+ else:
+ QMessageBox.about(
+ self, "Warning", "Please only, " +
+ "modify according to Typerestrictions")
+ self.drawTreeInit()
+
+ def saveFile(self):
+ """
+ Saves current tree model as .ini file
+ """
+ file, _ = QFileDialog.getSaveFileName(
+ self, "QFileDialog.getSaveFileName()", "",
+ "All Files (*);;ini (*.ini)")
+ if file:
+ temp = file.split(".")
+ if len(temp) < 2:
+ file = file + ".ini"
+ self.tree.write(file)
diff --git a/src/view/MultipleSpecView.py b/src/view/MultipleSpecView.py
new file mode 100644
index 000000000..d7e5a35ce
--- /dev/null
+++ b/src/view/MultipleSpecView.py
@@ -0,0 +1,57 @@
+import os
+import sys
+import glob
+from PyQt5.QtWidgets import QWidget, QTableWidget, \
+ QTableWidgetItem, QVBoxLayout, QHeaderView
+sys.path.append(os.getcwd()+'/../view')
+from SpecViewer import Specviewer # noqa E402
+
+
+class MultipleSpecView(QWidget):
+ """
+ Displays the SpecviewerWidget and additionally a
+ table containing all files from working dir
+ """
+
+ def __init__(self, *args):
+ QWidget.__init__(self, *args)
+ self.table = QTableWidget()
+ self.sview = Specviewer()
+ self.table.setRowCount(0)
+ self.table.setSortingEnabled(True)
+ self.table.setColumnCount(1)
+ self.header = ['Filename']
+ self.table.setHorizontalHeaderLabels(self.header)
+ self.header = self.table.horizontalHeader()
+ self.header.setSectionResizeMode(0, QHeaderView.Stretch)
+ self.table.selectionModel().selectionChanged.connect(self.loadmzML)
+
+ layout = QVBoxLayout()
+ layout.addWidget(self.sview, 10)
+ layout.addWidget(self.table, 4)
+
+ self.setLayout(layout)
+ self.resize(1280, 720)
+
+ def fillTable(self, projectdir: str):
+ """
+ Uses the project directory to access all mzML files
+ Loads all mzML files in the table
+ """
+ directory = projectdir
+ os.chdir(directory)
+ files = glob.glob('*.mzML')
+ numfiles = len(files)
+ self.table.setRowCount(numfiles)
+ if numfiles > 0:
+ for file, row in zip(files, range(numfiles)):
+ self.table.setItem(row, 0, QTableWidgetItem(file))
+
+ def loadmzML(self):
+ """
+ By selection of a mzML file the specviewer updates his file
+ """
+ selected = self.table.selectedItems()[0]
+ selectedFile = selected.text()
+ self.sview.setScanBrowserWidget()
+ self.sview.scanbrowser.loadFile(selectedFile)
diff --git a/src/view/SpectrumWidget.py b/src/view/SpectrumWidget.py
index 7dfac8bba..4be20ec33 100644
--- a/src/view/SpectrumWidget.py
+++ b/src/view/SpectrumWidget.py
@@ -194,6 +194,7 @@ def _clear_ladder_item(self, key):
anno.clear()
for pos in self._ladder_anno_labels[key]:
pos.setPos(0, 0)
+
del self._ladder_anno_lines[key]
del self._ladder_anno_labels[key]
diff --git a/src/view/mzMLTableView.py b/src/view/mzMLTableView.py
new file mode 100644
index 000000000..9b0c99a6a
--- /dev/null
+++ b/src/view/mzMLTableView.py
@@ -0,0 +1,468 @@
+import os
+import sys
+import timeit
+import pandas as pd
+import math
+from PyQt5 import Qt
+from PyQt5.QtWidgets import QHBoxLayout, QWidget, QFileDialog, \
+ QTableWidget, QTableWidgetItem, QHeaderView, QPushButton, \
+ QVBoxLayout, QInputDialog, QLineEdit, QMessageBox, \
+ QAbstractItemView
+sys.path.append(os.getcwd() + '/../controller')
+from filehandler import FileHandler as fh # noqa E402
+sys.path.append(os.getcwd() + '/../model')
+from tableDataFrame import TableDataFrame as Tdf # noqa E402
+
+
+class mzMLTableView(QWidget):
+ """
+ Main Widget of the TableEditor app
+ """
+
+ def __init__(self, *args):
+ # set variable self.testForTime to True to see Runtimes
+ # the following 2 if constructs can be used to determine
+ # timing
+ # just put them around whatever should be timed
+
+ # self.testForTime = False
+ # if self.testForTime:
+ # starttime = timeit.default_timer()
+ # print("Starttime of overall Initiation : ", starttime)
+
+ # if self.testForTime:
+ # rt = timeit.default_timer() - starttime
+ # print("Runtime of overall Initiation was : ", rt)
+
+ QWidget.__init__(self, *args)
+
+ self.df = pd.DataFrame()
+ self.tdf = Tdf
+ self.drawtableactive = False
+
+ self.initTable()
+ self.initButtons()
+ self.changeListener()
+
+ """
+ Layout for the entire View
+ """
+ layout = QVBoxLayout()
+ layout.addWidget(self.buttons)
+ layout.addWidget(self.table)
+
+ self.setLayout(layout)
+ self.resize(1280, 720)
+
+ def initTable(self):
+ """
+ initializes Table
+ """
+ self.tablefile_loaded = False
+ self.loaded_table = ""
+ self.table = QTableWidget()
+ self.table.setRowCount(0)
+ self.table.setSortingEnabled(True)
+ self.header = ['Group', 'Fraction',
+ 'Spectra Filepath', 'Label', 'Sample']
+ self.table.setColumnCount(len(self.header))
+ self.table.setHorizontalHeaderLabels(self.header)
+ self.header = self.table.horizontalHeader()
+
+ for col in range(len(self.header)):
+ if col != 2:
+ self.header.setSectionResizeMode(col,
+ QHeaderView.ResizeToContents)
+ else:
+ self.header.setSectionResizeMode(col, QHeaderView.Stretch)
+
+ def initButtons(self):
+ """
+ initializes Buttons
+ """
+ self.buttons = QWidget()
+ self.textbox = QLineEdit(self)
+ self.textbox.move(20, 20)
+ self.textbox.setFixedHeight(20)
+ self.textbox.setToolTip("Filter the experimental layout " +
+ "according to Spectra Filepath " +
+ "column. It will be dynamically \n" +
+ "updated as soon as 2 characters " +
+ "are inserted.")
+
+ Buttons = [QPushButton('Load Project'), QPushButton('Load Table'),
+ QPushButton('Save Table'), QPushButton('Add File'),
+ QPushButton('Remove File'), QPushButton('Group'),
+ QPushButton('Fraction'), QPushButton('Label'),
+ QPushButton('Select All')]
+
+ # Buttonlayout
+ buttonlayout = QHBoxLayout()
+ for button in Buttons:
+ buttonlayout.addWidget(button)
+
+ buttonlayout.addWidget(self.textbox)
+ self.buttons.setLayout(buttonlayout)
+
+ # Connections for Buttons and their apropriate functions
+ Buttons[0].clicked.connect(self.loadBtnFn)
+ Buttons[0].setToolTip("Load a directory with .mzML files to " +
+ "generate your own eperimental layout. " +
+ "For mzML filenames, \"F\" is the regular \n" +
+ "expression for fraction, while \"G\" or " +
+ "\"FG\"is the regular expression for the " +
+ "fraction groups.")
+ Buttons[1].clicked.connect(self.importBtn)
+ Buttons[1].setToolTip("Load an existing experimental layout, as " +
+ ".csv or .tsv to display and modify it.")
+ Buttons[2].clicked.connect(self.exportBtn)
+ Buttons[2].setToolTip("Save the experimental layout as .csv or " +
+ ".tsv file. .csv is the default option")
+ Buttons[3].clicked.connect(self.loadFile)
+ Buttons[3].setToolTip("Load an additional single .mzML file " +
+ "to the experimental layout.")
+ Buttons[4].clicked.connect(self.RemoveBtn)
+ Buttons[4].setToolTip("Remove one or more selected .mzML " +
+ "files from the experimental layout.")
+ Buttons[5].clicked.connect(self.GroupBtn)
+ Buttons[5].setToolTip("Set the fraction group of selected rows " +
+ "to a given number.")
+ Buttons[6].clicked.connect(self.FractionBtn)
+ Buttons[6].setToolTip("Set the fraction of selected rows to a " +
+ "specific number or use a range to define " +
+ "multiple fractions. This function is \n" +
+ "also able to work over multiple fraction " +
+ "groups and sets the group according to the " +
+ "fraction number.")
+ Buttons[7].clicked.connect(self.LabelBtn)
+ Buttons[7].setToolTip("Set the number of labels, the program will " +
+ "generate the necessary rows and will also " +
+ "define the samplenumber for you. You can \n" +
+ "apply the option to continue samplenumbers " +
+ "over mutliple fraction groups to combine " +
+ "two sample preparations.")
+ Buttons[8].clicked.connect(self.SelectAllBtn)
+
+ # init changelistener on textbox
+ self.textbox.textChanged[str].connect(self.filterTable)
+
+ def getDataFrame(self):
+ return self.tdf.getTable(self)
+
+ def drawTable(self):
+ """
+ draws a table with the dataframe table model in tableDataFrame
+ """
+ self.drawtableactive = True
+
+ tabledf = Tdf.getTable(self)
+ # print(tabledf) # For debugging
+ rowcount = len(tabledf.index)
+ colcount = len(tabledf.columns)
+ self.table.setRowCount(rowcount)
+ for r in range(rowcount):
+ row = tabledf.index[r]
+ for c in range(colcount):
+ col = tabledf.columns[c]
+ if col == 'Spectra_Filepath':
+ path = tabledf.at[row, col].split("/")
+ name = path[len(path)-1]
+ self.table.setItem(r, c, QTableWidgetItem(name))
+ else:
+ item = str(tabledf.at[row, col])
+ self.table.setItem(r, c, QTableWidgetItem(item))
+
+ self.drawtableactive = False
+
+ def importBtn(self, file: str = ""):
+ """
+ Imports table files, currently working are csv and tsv
+ """
+ options = QFileDialog.Options()
+ if not file:
+ file, _ = QFileDialog.getOpenFileName(
+ self, "QFileDialog.getOpenFileName()", "",
+ "All Files (*);;tsv (*.tsv);; csv (*.csv)", options=options)
+
+ if file:
+ df = fh.importTable(self, file)
+ Tdf.setTable(self, df)
+ self.drawTable()
+ self.tablefile_loaded = True
+ file = file.split("/")[-1]
+ self.loaded_table = file
+
+ def exportBtn(self):
+ """
+ Exports the table to csv or tsv;default is csv
+ """
+ options = QFileDialog.Options()
+ file, _ = QFileDialog.getSaveFileName(
+ self, "QFileDialog.getSaveFileName()", "",
+ "All Files (*);;tsv (*.tsv);; csv (*.csv)", options=options)
+
+ if file:
+ self.tablefile_loaded = True
+ fpath = file.split("/")[-1]
+ self.loaded_table = fpath
+ df = Tdf.getTable(self)
+ temp = file.split("/")
+ fileName = temp[len(temp)-1]
+ length = len(fileName)
+ if length < 4:
+ ftype = "csv"
+ file = file + ".csv"
+ elif fileName.find('.csv', length-4) != -1:
+ ftype = "csv"
+ elif fileName.find('.tsv', length-4) != -1:
+ ftype = "tsv"
+ else:
+ ftype = "csv"
+ file = file + ".csv"
+
+ fh.exportTable(self, df, file, ftype)
+
+ def loadBtnFn(self):
+ """
+ provides a dialog to get the path for a directory
+ and load the directory into the table.
+ """
+ dlg = QFileDialog(self)
+ filePath = dlg.getExistingDirectory()
+
+ if filePath != '':
+ self.loadDir(filePath)
+
+ def loadDir(self, filepath: str):
+ Files = fh.getFiles(self, filepath)
+ delimiters = ["_"]
+ preparedFiles = fh.tagfiles(self, Files, delimiters[0])
+ rawTable = fh.createRawTable(self, preparedFiles, filepath)
+ Tdf.setTable(self, rawTable)
+ self.drawTable()
+
+ def loadFile(self, file: str = ""):
+ """
+ provides a filedialog to load an additional file to the dataframe
+ """
+ options = QFileDialog.Options()
+ if not file:
+ file, _ = QFileDialog.getOpenFileName(
+ self, "QFileDialog.getOpenFileName()", "",
+ "All Files (*);;mzML Files (*.mzML)", options=options)
+
+ if file:
+ cdf = Tdf.getTable(self)
+ filelist = []
+ filePath = file.rsplit("/", 1)[0]
+ temp = file.split("/")
+ fileName = temp[len(temp)-1]
+ if file:
+ # print(file)
+ filelist.append(fileName)
+ tagged_file = fh.tagfiles(self, filelist)
+ df = fh.createRawTable(self, tagged_file, filePath)
+
+ ndf = cdf.append(df, ignore_index=True)
+
+ Tdf.setTable(self, ndf)
+ self.drawTable()
+ else:
+ return False
+
+ def getSelRows(self) -> list:
+ """
+ Function which returns a list of the Indexes of selected Rows
+ todo: needs to be adjusted to fit the datamodel:
+ so far index of table is only matching index of dataframe
+ in first iteration, as soon as remove is called twice it crashes.
+ """
+ selindexes = self.table.selectionModel().selectedRows()
+ selrows = []
+ for index in sorted(selindexes):
+ row = index.row()
+ selrows.append(row)
+ return selrows
+
+ def GroupBtn(self):
+ """
+ Enables the user to change the group of selected rows to a given
+ number.
+ """
+ selrows = self.getSelRows()
+
+ groupnum, ok = QInputDialog.getInt(self,
+ "Group Number",
+ "Enter Integer Groupnumber")
+
+ if ok:
+ Tdf.modifyGroup(self, selrows, groupnum)
+ self.drawTable()
+
+ def RemoveBtn(self):
+ """
+ Enables the user to remove selected rows
+ """
+ selrows = self.getSelRows()
+ Tdf.rmvRow(self, selrows)
+ self.drawTable()
+
+ def FractionBtn(self):
+ """
+ Enables the user to change the Fraction of selected rows to a given
+ number or give a range.
+ """
+ selrows = self.getSelRows()
+
+ # first inputdialog
+ fracmin, ok = QInputDialog.getInt(self,
+ "Fraction",
+ "Enter minimal Fractionnumber " +
+ "or single Fractionnumber")
+ # second inputdialog if first is accepted
+ if ok:
+ fracmax, ok = QInputDialog.getInt(self,
+ "Fraction",
+ "Enter maximal Fractionnumber " +
+ "or 0 for single Fractionnumber")
+ if ok:
+ # decision if multiple fractions are set or just one
+ if fracmax != 0:
+ if fracmax > fracmin:
+ # third messagedialog
+ rep = QMessageBox.question(self, "Fraction Group?",
+ "Do you want to infer a " +
+ "Fraction Group from the " +
+ "given range?",
+ (QMessageBox.Yes |
+ QMessageBox.No),
+ QMessageBox.No)
+
+ # when confirmed the fraction froup is set
+ # when max fraction is reached.
+ if rep == QMessageBox.Yes:
+ Tdf.modifyFraction(self, selrows, fracmin, fracmax)
+ fractions = fracmax-fracmin + 1
+ numgroups = math.ceil(len(selrows)/fractions)
+ splicelist = [0]
+ for g in range(1, numgroups+1):
+ splicelist.append(g*fractions)
+ splicelist.append(len(selrows))
+ for group in range(1, numgroups+1):
+ indexa = splicelist[group-1]
+ indexb = splicelist[group]
+ subrows = selrows[indexa:indexb]
+ Tdf.modifyGroup(self, subrows, group)
+ else:
+ Tdf.modifyFraction(self, selrows, fracmin, fracmax)
+
+ elif fracmax == fracmin:
+ Tdf.modifyFraction(self, selrows, fracmin)
+
+ else:
+ QMessageBox.warning(self, "Error", "Please use " +
+ "a higher integer " +
+ "number for the maximum " +
+ "fractionnumber.")
+
+ else:
+ Tdf.modifyFraction(self, selrows, fracmin)
+ self.drawTable()
+
+ def LabelBtn(self):
+ """
+ Let the user choose the number of labels, it will generate
+ the labels for the copied rows and also links the sample to
+ the label. Gives an option to continue the samplecount
+ over fraction groups.
+ """
+ labelnum, ok = QInputDialog.getInt(self, "Label",
+ "Please specify the multiplicity " +
+ "of the selected rows")
+ if ok:
+ rep = QMessageBox.question(self, "Continuous Sample",
+ "Does the samplenumber " +
+ "continue over multiple " +
+ "fraction groups?",
+ (QMessageBox.Yes |
+ QMessageBox.No),
+ QMessageBox.No)
+ if rep == QMessageBox.Yes:
+ try:
+ Tdf.modifyLabelSample(self, labelnum, True)
+ except ValueError:
+ QMessageBox.about(self, "Warning", "Unfortunaly, " +
+ "your Number was <1")
+ else:
+ try:
+ Tdf.modifyLabelSample(self, labelnum, False)
+ except ValueError:
+ QMessageBox.about(self, "Warning", "Unfortunaly, " +
+ "your Number was <1")
+ self.drawTable()
+
+ def SelectAllBtn(self):
+ """
+ Selects all Rows of the Table
+ """
+ self.table.setSelectionMode(QAbstractItemView.MultiSelection)
+
+ for i in range(self.table.rowCount()):
+ selected = self.getSelRows()
+
+ for j in range(len(selected)):
+ if i == selected[j]:
+ self.table.selectRow(i)
+
+ self.table.selectRow(i)
+
+ self.table.setSelectionMode(QAbstractItemView.ExtendedSelection)
+
+ def updateTableView(self, rows):
+ tabledf = Tdf.getTable(self)
+ rowcount = len(tabledf.index)
+ for i in range(rowcount):
+ self.table.setRowHidden(i, True)
+ for i in rows:
+ self.table.setRowHidden(i, False)
+
+ def filterTable(self):
+ """
+ get changes from textbox and update table when
+ more than 3 characters are given.
+ then update table with the rows that
+ contain the input in the give column.
+ """
+ tb = self.textbox
+ givencolumn = "Spectra_Filepath"
+ tbinput = tb.text()
+ ft = Tdf.getTable(self)
+ validDf = not(ft.empty or ft.dropna().empty)
+ # print(validDf) # for debugging
+ # print(type(ft)) # for debugging
+ if len(tbinput) >= 2:
+ rowstoshow = ft[ft[givencolumn].str.contains(tbinput)]
+ self.updateTableView(rowstoshow.index)
+ else:
+ self.updateTableView(ft.index)
+
+ def changeListener(self):
+ self.table.itemChanged.connect(self.editField)
+
+ def editField(self):
+ if len(self.table.selectedItems()) == 1:
+ if not self.drawtableactive:
+ itemchanged = self.table.currentItem()
+ newvalue = itemchanged.text()
+ row = itemchanged.row()
+ column = itemchanged.column()
+ if column != 2:
+ Tdf.modifyField(self, row, column, newvalue)
+ self.drawTable()
+ else:
+ QMessageBox.about(self, "Warning", "Please only, " +
+ "modify attribute columns," +
+ "not the filepath.\n" +
+ "To change the filepath," +
+ "use remove and add file.")
+ self.drawTable()