diff --git a/info/dev_notes.txt b/info/dev_notes.txt index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..06d82cd9921be7093eadae8b5d46254d6f6b14a4 100644 --- a/info/dev_notes.txt +++ b/info/dev_notes.txt @@ -0,0 +1,35 @@ +# Current pgm: + +## Input data (naive_peaks.py): + - raw data files: binary data (required) + - metadata: csv file that holds the information of the PanelData tab in the according .xlsm file + - jason file with settings: + - raw data folder + - processing settings + +# Questions: + + - Should there be the possibility to store the current GUI settings (data and processing parameters, etc.) + - Should there be the possibility to load default parameters and/or previously saved parameters (e.g. from json file)? + - Is the json file required in the current version, e.g. for documentation (processing parameters, etc.) or further analysis? + - Are there any default values for the processing parameters? Should they be stored anywhere (e.g. json file)? + +## Processing: + - Is the input data (raw data) always organized in the same way, i.e.: Raw data folder that contains data files with the + file names containing the exper. name date and time? + - Where do we get the following parameters (listed in drop down menu) - from the filenames? + - Experiment name + - Date + - Time + - should directly the .xlsm file (PanelData tab) be used instead of an .csv file that holds identical information? + - What is the actual output of the lifescale devise? + - raw data? + - xlsm files with metadata? + +## For plotting and analysis: + - Is additional data required from other sources, e.g. the .xlsm files? + - What should be plotted/analyzed/calculated? + + + + diff --git a/lifescale/gui/MainWindow.py b/lifescale/gui/MainWindow.py index 73b28c42b8084a82c9882ede3c12191873bdbf76..e1d6bff8ecfb591c724f5d042341995d0d985ac8 100644 --- a/lifescale/gui/MainWindow.py +++ b/lifescale/gui/MainWindow.py @@ -12,21 +12,150 @@ from PyQt6 import QtCore, QtGui, QtWidgets class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") - MainWindow.resize(800, 600) + MainWindow.resize(992, 616) self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") + self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget) + self.verticalLayout.setObjectName("verticalLayout") + self.tabWidget_Main = QtWidgets.QTabWidget(self.centralwidget) + self.tabWidget_Main.setObjectName("tabWidget_Main") + self.tab_peaks = QtWidgets.QWidget() + self.tab_peaks.setObjectName("tab_peaks") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.tab_peaks) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.groupBox_data = QtWidgets.QGroupBox(self.tab_peaks) + self.groupBox_data.setObjectName("groupBox_data") + self.formLayout = QtWidgets.QFormLayout(self.groupBox_data) + self.formLayout.setObjectName("formLayout") + self.label_data_rawData = QtWidgets.QLabel(self.groupBox_data) + self.label_data_rawData.setObjectName("label_data_rawData") + self.formLayout.setWidget(0, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_data_rawData) + self.horizontalLayout_data_rawData = QtWidgets.QHBoxLayout() + self.horizontalLayout_data_rawData.setObjectName("horizontalLayout_data_rawData") + self.lineEdit_data_rawData = QtWidgets.QLineEdit(self.groupBox_data) + self.lineEdit_data_rawData.setObjectName("lineEdit_data_rawData") + self.horizontalLayout_data_rawData.addWidget(self.lineEdit_data_rawData) + self.pushButton_data_rawData = QtWidgets.QPushButton(self.groupBox_data) + self.pushButton_data_rawData.setObjectName("pushButton_data_rawData") + self.horizontalLayout_data_rawData.addWidget(self.pushButton_data_rawData) + self.formLayout.setLayout(0, QtWidgets.QFormLayout.ItemRole.FieldRole, self.horizontalLayout_data_rawData) + self.label_data_metadataFile = QtWidgets.QLabel(self.groupBox_data) + self.label_data_metadataFile.setObjectName("label_data_metadataFile") + self.formLayout.setWidget(1, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_data_metadataFile) + self.horizontalLayout_data_metadataFile = QtWidgets.QHBoxLayout() + self.horizontalLayout_data_metadataFile.setObjectName("horizontalLayout_data_metadataFile") + self.lineEdit_data_metadataFile = QtWidgets.QLineEdit(self.groupBox_data) + self.lineEdit_data_metadataFile.setObjectName("lineEdit_data_metadataFile") + self.horizontalLayout_data_metadataFile.addWidget(self.lineEdit_data_metadataFile) + self.pushButton_data_metadataFile = QtWidgets.QPushButton(self.groupBox_data) + self.pushButton_data_metadataFile.setObjectName("pushButton_data_metadataFile") + self.horizontalLayout_data_metadataFile.addWidget(self.pushButton_data_metadataFile) + self.formLayout.setLayout(1, QtWidgets.QFormLayout.ItemRole.FieldRole, self.horizontalLayout_data_metadataFile) + self.label_data_outputFolder = QtWidgets.QLabel(self.groupBox_data) + self.label_data_outputFolder.setObjectName("label_data_outputFolder") + self.formLayout.setWidget(2, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_data_outputFolder) + self.horizontalLayout_data_outputFolder = QtWidgets.QHBoxLayout() + self.horizontalLayout_data_outputFolder.setObjectName("horizontalLayout_data_outputFolder") + self.lineEdit_data_outputFolder = QtWidgets.QLineEdit(self.groupBox_data) + self.lineEdit_data_outputFolder.setObjectName("lineEdit_data_outputFolder") + self.horizontalLayout_data_outputFolder.addWidget(self.lineEdit_data_outputFolder) + self.pushButton_data_outputFolder = QtWidgets.QPushButton(self.groupBox_data) + self.pushButton_data_outputFolder.setObjectName("pushButton_data_outputFolder") + self.horizontalLayout_data_outputFolder.addWidget(self.pushButton_data_outputFolder) + self.formLayout.setLayout(2, QtWidgets.QFormLayout.ItemRole.FieldRole, self.horizontalLayout_data_outputFolder) + self.label_data_selectExperiment = QtWidgets.QLabel(self.groupBox_data) + self.label_data_selectExperiment.setObjectName("label_data_selectExperiment") + self.formLayout.setWidget(3, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_data_selectExperiment) + self.comboBox_data_selectExperiment = QtWidgets.QComboBox(self.groupBox_data) + self.comboBox_data_selectExperiment.setObjectName("comboBox_data_selectExperiment") + self.formLayout.setWidget(3, QtWidgets.QFormLayout.ItemRole.FieldRole, self.comboBox_data_selectExperiment) + self.verticalLayout_2.addWidget(self.groupBox_data) + self.groupBox_processingParameters = QtWidgets.QGroupBox(self.tab_peaks) + self.groupBox_processingParameters.setObjectName("groupBox_processingParameters") + self.formLayout_2 = QtWidgets.QFormLayout(self.groupBox_processingParameters) + self.formLayout_2.setObjectName("formLayout_2") + self.label_massTransformation = QtWidgets.QLabel(self.groupBox_processingParameters) + self.label_massTransformation.setObjectName("label_massTransformation") + self.formLayout_2.setWidget(0, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_massTransformation) + self.doubleSpinBox_massTransformation = QtWidgets.QDoubleSpinBox(self.groupBox_processingParameters) + self.doubleSpinBox_massTransformation.setDecimals(6) + self.doubleSpinBox_massTransformation.setObjectName("doubleSpinBox_massTransformation") + self.formLayout_2.setWidget(0, QtWidgets.QFormLayout.ItemRole.FieldRole, self.doubleSpinBox_massTransformation) + self.label_massCutoff = QtWidgets.QLabel(self.groupBox_processingParameters) + self.label_massCutoff.setObjectName("label_massCutoff") + self.formLayout_2.setWidget(1, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_massCutoff) + self.doubleSpinBox_massCutoff = QtWidgets.QDoubleSpinBox(self.groupBox_processingParameters) + self.doubleSpinBox_massCutoff.setDecimals(1) + self.doubleSpinBox_massCutoff.setObjectName("doubleSpinBox_massCutoff") + self.formLayout_2.setWidget(1, QtWidgets.QFormLayout.ItemRole.FieldRole, self.doubleSpinBox_massCutoff) + self.label_peakWidthCutoff = QtWidgets.QLabel(self.groupBox_processingParameters) + self.label_peakWidthCutoff.setObjectName("label_peakWidthCutoff") + self.formLayout_2.setWidget(2, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_peakWidthCutoff) + self.doubleSpinBox_peakWidthCutoff = QtWidgets.QDoubleSpinBox(self.groupBox_processingParameters) + self.doubleSpinBox_peakWidthCutoff.setDecimals(1) + self.doubleSpinBox_peakWidthCutoff.setObjectName("doubleSpinBox_peakWidthCutoff") + self.formLayout_2.setWidget(2, QtWidgets.QFormLayout.ItemRole.FieldRole, self.doubleSpinBox_peakWidthCutoff) + self.label_peakDistanceCutoff = QtWidgets.QLabel(self.groupBox_processingParameters) + self.label_peakDistanceCutoff.setObjectName("label_peakDistanceCutoff") + self.formLayout_2.setWidget(3, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_peakDistanceCutoff) + self.doubleSpinBox_peakDistanceCutoff = QtWidgets.QDoubleSpinBox(self.groupBox_processingParameters) + self.doubleSpinBox_peakDistanceCutoff.setDecimals(1) + self.doubleSpinBox_peakDistanceCutoff.setObjectName("doubleSpinBox_peakDistanceCutoff") + self.formLayout_2.setWidget(3, QtWidgets.QFormLayout.ItemRole.FieldRole, self.doubleSpinBox_peakDistanceCutoff) + self.verticalLayout_2.addWidget(self.groupBox_processingParameters) + self.pushButton_run = QtWidgets.QPushButton(self.tab_peaks) + self.pushButton_run.setObjectName("pushButton_run") + self.verticalLayout_2.addWidget(self.pushButton_run) + self.tabWidget_Main.addTab(self.tab_peaks, "") + self.tab_analysis = QtWidgets.QWidget() + self.tab_analysis.setObjectName("tab_analysis") + self.tabWidget_Main.addTab(self.tab_analysis, "") + self.verticalLayout.addWidget(self.tabWidget_Main) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) - self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 22)) + self.menubar.setGeometry(QtCore.QRect(0, 0, 992, 22)) self.menubar.setObjectName("menubar") + self.menuOptions = QtWidgets.QMenu(self.menubar) + self.menuOptions.setObjectName("menuOptions") MainWindow.setMenuBar(self.menubar) self.statusbar = QtWidgets.QStatusBar(MainWindow) self.statusbar.setObjectName("statusbar") MainWindow.setStatusBar(self.statusbar) + self.actionSave_current_GUI_parameters_json = QtGui.QAction(MainWindow) + self.actionSave_current_GUI_parameters_json.setObjectName("actionSave_current_GUI_parameters_json") + self.actionLoad_GUI_parameters_json = QtGui.QAction(MainWindow) + self.actionLoad_GUI_parameters_json.setObjectName("actionLoad_GUI_parameters_json") + self.actionLoad_default_parameters = QtGui.QAction(MainWindow) + self.actionLoad_default_parameters.setObjectName("actionLoad_default_parameters") + self.menuOptions.addAction(self.actionSave_current_GUI_parameters_json) + self.menuOptions.addAction(self.actionLoad_GUI_parameters_json) + self.menuOptions.addAction(self.actionLoad_default_parameters) + self.menubar.addAction(self.menuOptions.menuAction()) self.retranslateUi(MainWindow) + self.tabWidget_Main.setCurrentIndex(0) QtCore.QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "lifescale tools")) + self.groupBox_data.setTitle(_translate("MainWindow", "Data")) + self.label_data_rawData.setText(_translate("MainWindow", "Raw data folder")) + self.pushButton_data_rawData.setText(_translate("MainWindow", "Browse")) + self.label_data_metadataFile.setText(_translate("MainWindow", "Metadata file (optional)")) + self.pushButton_data_metadataFile.setText(_translate("MainWindow", "Browse")) + self.label_data_outputFolder.setText(_translate("MainWindow", "Output folder")) + self.pushButton_data_outputFolder.setText(_translate("MainWindow", "Browse")) + self.label_data_selectExperiment.setText(_translate("MainWindow", "Select Experiment")) + self.groupBox_processingParameters.setTitle(_translate("MainWindow", "Processing parameters")) + self.label_massTransformation.setText(_translate("MainWindow", "Mass transformation [fg/Hz]")) + self.label_massCutoff.setText(_translate("MainWindow", "Mass cutoff [fg]")) + self.label_peakWidthCutoff.setText(_translate("MainWindow", "Peak width cutoff [???]")) + self.label_peakDistanceCutoff.setText(_translate("MainWindow", "Peak distance cutoff [??]")) + self.pushButton_run.setText(_translate("MainWindow", "Run")) + self.tabWidget_Main.setTabText(self.tabWidget_Main.indexOf(self.tab_peaks), _translate("MainWindow", "Mass Peaks")) + self.tabWidget_Main.setTabText(self.tabWidget_Main.indexOf(self.tab_analysis), _translate("MainWindow", "Analysis")) + self.menuOptions.setTitle(_translate("MainWindow", "Options")) + self.actionSave_current_GUI_parameters_json.setText(_translate("MainWindow", "Save current GUI parameters (json)")) + self.actionLoad_GUI_parameters_json.setText(_translate("MainWindow", "Load GUI parameters (json)")) + self.actionLoad_default_parameters.setText(_translate("MainWindow", "Load default parameters")) diff --git a/lifescale/gui/MainWindow.ui b/lifescale/gui/MainWindow.ui index b8c1b1692ec5e9d4658de10826e20a2573cf08ff..8b01b72c078c0c7b11c1ad10d6185da8c5a62ca0 100644 --- a/lifescale/gui/MainWindow.ui +++ b/lifescale/gui/MainWindow.ui @@ -6,25 +6,225 @@ <rect> <x>0</x> <y>0</y> - <width>800</width> - <height>600</height> + <width>992</width> + <height>616</height> </rect> </property> <property name="windowTitle"> <string>lifescale tools</string> </property> - <widget class="QWidget" name="centralwidget"/> + <widget class="QWidget" name="centralwidget"> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QTabWidget" name="tabWidget_Main"> + <property name="currentIndex"> + <number>0</number> + </property> + <widget class="QWidget" name="tab_peaks"> + <attribute name="title"> + <string>Mass Peaks</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QGroupBox" name="groupBox_data"> + <property name="title"> + <string>Data</string> + </property> + <layout class="QFormLayout" name="formLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label_data_rawData"> + <property name="text"> + <string>Raw data folder</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_data_rawData"> + <item> + <widget class="QLineEdit" name="lineEdit_data_rawData"/> + </item> + <item> + <widget class="QPushButton" name="pushButton_data_rawData"> + <property name="text"> + <string>Browse</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_data_metadataFile"> + <property name="text"> + <string>Metadata file (optional)</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_data_metadataFile"> + <item> + <widget class="QLineEdit" name="lineEdit_data_metadataFile"/> + </item> + <item> + <widget class="QPushButton" name="pushButton_data_metadataFile"> + <property name="text"> + <string>Browse</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_data_outputFolder"> + <property name="text"> + <string>Output folder</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_data_outputFolder"> + <item> + <widget class="QLineEdit" name="lineEdit_data_outputFolder"/> + </item> + <item> + <widget class="QPushButton" name="pushButton_data_outputFolder"> + <property name="text"> + <string>Browse</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_data_selectExperiment"> + <property name="text"> + <string>Select Experiment</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QComboBox" name="comboBox_data_selectExperiment"/> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_processingParameters"> + <property name="title"> + <string>Processing parameters</string> + </property> + <layout class="QFormLayout" name="formLayout_2"> + <item row="0" column="0"> + <widget class="QLabel" name="label_massTransformation"> + <property name="text"> + <string>Mass transformation [fg/Hz]</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QDoubleSpinBox" name="doubleSpinBox_massTransformation"> + <property name="decimals"> + <number>6</number> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_massCutoff"> + <property name="text"> + <string>Mass cutoff [fg]</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QDoubleSpinBox" name="doubleSpinBox_massCutoff"> + <property name="decimals"> + <number>1</number> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_peakWidthCutoff"> + <property name="text"> + <string>Peak width cutoff [???]</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QDoubleSpinBox" name="doubleSpinBox_peakWidthCutoff"> + <property name="decimals"> + <number>1</number> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_peakDistanceCutoff"> + <property name="text"> + <string>Peak distance cutoff [??]</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QDoubleSpinBox" name="doubleSpinBox_peakDistanceCutoff"> + <property name="decimals"> + <number>1</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QPushButton" name="pushButton_run"> + <property name="text"> + <string>Run</string> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab_analysis"> + <attribute name="title"> + <string>Analysis</string> + </attribute> + </widget> + </widget> + </item> + </layout> + </widget> <widget class="QMenuBar" name="menubar"> <property name="geometry"> <rect> <x>0</x> <y>0</y> - <width>800</width> + <width>992</width> <height>22</height> </rect> </property> + <widget class="QMenu" name="menuOptions"> + <property name="title"> + <string>Options</string> + </property> + <addaction name="actionSave_current_GUI_parameters_json"/> + <addaction name="actionLoad_GUI_parameters_json"/> + <addaction name="actionLoad_default_parameters"/> + </widget> + <addaction name="menuOptions"/> </widget> <widget class="QStatusBar" name="statusbar"/> + <action name="actionSave_current_GUI_parameters_json"> + <property name="text"> + <string>Save current GUI parameters (json)</string> + </property> + </action> + <action name="actionLoad_GUI_parameters_json"> + <property name="text"> + <string>Load GUI parameters (json)</string> + </property> + </action> + <action name="actionLoad_default_parameters"> + <property name="text"> + <string>Load default parameters</string> + </property> + </action> </widget> <resources/> <connections/> diff --git a/lifescale/gui/gui_main.py b/lifescale/gui/gui_main.py index a59b42cea5c97cae3d085d150f320fb2624b9f39..4bfbecadb8f7a91011c94714f3e6c0adcc92de1f 100644 --- a/lifescale/gui/gui_main.py +++ b/lifescale/gui/gui_main.py @@ -22,7 +22,6 @@ class MainWindow(QMainWindow, Ui_MainWindow): # Init models: - def main(): """Main program to start the GUI.""" # Create the application diff --git a/lifescale/naive_peaks/__init__.py b/lifescale/mass_peak_caller/__init__.py similarity index 100% rename from lifescale/naive_peaks/__init__.py rename to lifescale/mass_peak_caller/__init__.py diff --git a/lifescale/mass_peak_caller/configure_peakcaller.py b/lifescale/mass_peak_caller/configure_peakcaller.py new file mode 100644 index 0000000000000000000000000000000000000000..5268d8c30172e67109ba4ec77dda84fab8bed97e --- /dev/null +++ b/lifescale/mass_peak_caller/configure_peakcaller.py @@ -0,0 +1,50 @@ +import os +import platform +import json + +DEFAULT_CONFIG = { + "mass_transformation": 0.00574, + "mass_cutoff": 20, + "peak_width_cutoff": 5, + "peak_distance_cutoff": 5, + "raw_data_folder": "~/research/lifescale_raw_data_test/development_raw_data_folder" +} + +LINUX_PATH = "./dev_config.json" +WINDOWS_PATH = r"C:\Users\LifeScale\Documents\peak_caller_config\peak_caller_config.json" + +def load_config(): + if platform.system() == "Linux": + try: + with open(LINUX_PATH, "r") as f: + config = json.load(f) + return config, None + except FileNotFoundError as e: + config = DEFAULT_CONFIG + with open(LINUX_PATH, "w") as f: + json.dump(config, f) + return config, LINUX_PATH + elif platform.system() == "Windows": + try: + with open(WINDOWS_PATH, "r") as f: + config = json.load(f) + return config, None + except FileNotFoundError as e: + config = DEFAULT_CONFIG + with open(WINDOWS_PATH, "w") as f: + json.dump(config, f) + return config, WINDOWS_PATH + +def configure_peakcaller(raw_data_folder, mass_transformation, mass_cutoff, peak_width_cutoff, peak_distance_cutoff, config, command): + print(locals()) + new_config = {k:v for k,v in locals().items() if k != "config" and k != "command" and v is not None} + print(new_config) + old_config = locals()["config"] + merged_config = {k:new_config[k] if k in new_config else old_config[k] for k in old_config} + if platform.system() == "Linux": + with open(LINUX_PATH, "w") as f: + json.dump(merged_config, f) + elif platform.system() == "Windows": + with open(WINDOWS_PATH, "w") as f: + json.dump(merged_config, f) + return merged_config diff --git a/lifescale/mass_peak_caller/dev_config.json b/lifescale/mass_peak_caller/dev_config.json new file mode 100644 index 0000000000000000000000000000000000000000..129888342d7db45b2f7919d3acdb0d7697625476 --- /dev/null +++ b/lifescale/mass_peak_caller/dev_config.json @@ -0,0 +1 @@ +{"mass_transformation": 0.00574, "mass_cutoff": 20.0, "peak_width_cutoff": 5.0, "peak_distance_cutoff": 5.0, "raw_data_folder": "/home/heller/pyProjects/gooey_lifescale/LSdata/raw_data"} \ No newline at end of file diff --git a/lifescale/mass_peak_caller/naive_peaks.py b/lifescale/mass_peak_caller/naive_peaks.py new file mode 100644 index 0000000000000000000000000000000000000000..fd51974faf6ab8bd5bd33082fb7d3c7b21cebbd3 --- /dev/null +++ b/lifescale/mass_peak_caller/naive_peaks.py @@ -0,0 +1,92 @@ +""" GUI application for processing LifeScale data. + +copyright 2019 Joseph Elsherbini +all rights reserved +""" + +import os +import struct +import json +import re +import datetime +from itertools import chain +from operator import itemgetter +import numpy as np +import pandas as pd +import scipy.signal + +NOW = datetime.datetime.now().strftime("%Y%m%d%H%M%S") + +def list_experiments(config): + raw_data_files = [f for f in os.listdir(os.path.expanduser(config["raw_data_folder"])) if re.search(r"(.+)_(\d{6})_(\d{6})", f) and os.path.splitext(f)[1] == ""] + unique_experiments = sorted(sorted(list(set([re.search(r"(.+)_(\d{6})_(\d{6})", f).groups() for f in raw_data_files])), + key=itemgetter(2), reverse=True), key=itemgetter(1), reverse=True) + return (["{} {}".format(e[0], get_date_time(e[1], e[2])) for e in unique_experiments], ["_".join(e) for e in unique_experiments]) + +def get_date_time(date, time): + fmt_string = "%m/%d/%Y %H:%M:%S" + return datetime.datetime(2000+int(date[0:2]), int(date[2:4]), int(date[4:6]), int(time[0:2]), int(time[2:4]), int(time[4:6])).strftime(fmt_string) + + +def call_peaks(experiment, output_folder, metadata_file, config, command): + update_now() + all_experiments= list_experiments(config) + exp_name = [e[1] for e in zip(all_experiments[0], all_experiments[1]) if e[0] == experiment][0] + exp_files = [os.path.join(os.path.expanduser(config["raw_data_folder"]), f) for f in os.listdir(os.path.expanduser(config["raw_data_folder"])) if exp_name in f and os.path.splitext(f)[1] == ""] + print(exp_name, exp_files) + peaks = write_peaks(exp_name, exp_files, output_folder, metadata_file, config) + write_summary(exp_name, peaks, output_folder) + # TODO write_plots(exp_name, peaks, output_folder, config) + write_config(exp_name, output_folder, config) + return config + +def update_now(): + global NOW + NOW = datetime.datetime.now().strftime("%Y%m%d%H%M%S") + +def parse_metadata(metadata_file): + return pd.read_csv(metadata_file)[["Id", "Well"]] + +def load_raw_data(exp_name, exp_files): + for f_path in exp_files: + m = re.search(r"(.+)_(\d{6})_(\d{6})_c(\d+)_v(\d+)", f_path) + exp_date, exp_time, exp_cycle, exp_measurement = m.group(2,3,4,5) + print(exp_name, exp_date, exp_time, exp_cycle, exp_measurement) + n_datapoints = int(os.path.getsize(f_path) / 8) + with open(f_path, "rb") as f: + content = f.read() + a = np.array(struct.unpack("d"*n_datapoints, content))[10:] + yield dict(zip(["exp_name", "exp_date", "exp_time", "exp_cycle", "exp_measurement", "data_array"], + [exp_name, exp_date, exp_time, exp_cycle, exp_measurement, a])) + +def generate_peaks(measurement, config): + filtered_signal = scipy.signal.savgol_filter(measurement["data_array"], window_length=5, polyorder=3) + peaks, _ = scipy.signal.find_peaks(-filtered_signal, width=config["peak_width_cutoff"], prominence=config["mass_cutoff"]*config["mass_transformation"], distance=config["peak_distance_cutoff"]) + masses = scipy.signal.peak_prominences(-filtered_signal, peaks)[0]*(1/config["mass_transformation"]) + for peak, mass in zip(peaks, masses): + yield dict(zip(["exp_name", "exp_date", "exp_time", "exp_cycle", "exp_measurement", "event_index","event_mass"], + [measurement["exp_name"], measurement["exp_date"],measurement["exp_time"],measurement["exp_cycle"],measurement["exp_measurement"], peak, mass])) + +def write_peaks(exp_name, exp_files, output_folder, metadata_file, config): + peaks = pd.DataFrame(chain.from_iterable([generate_peaks(measurement, config) for measurement in load_raw_data(exp_name, exp_files)])) + if metadata_file: + metadata = parse_metadata(metadata_file) + peaks = peaks.astype({'exp_measurement':'int32'}).merge(metadata.astype({'Id':'int32'}), how='left', left_on='exp_measurement', right_on='Id') + peaks["Well"] = ["".join([w[0],w[1:].zfill(2)]) for w in peaks["Well"]] + out_path = os.path.join(os.path.expanduser(output_folder), "{}_{}_peaks.csv".format(NOW, exp_name)) + peaks.to_csv(out_path, index=False) + return peaks + +def write_summary(exp_name, peaks, output_folder): + print(peaks.columns) + if "Well" in peaks.columns: + summary = peaks.groupby(["Well", "exp_cycle"])["event_mass"].describe() + else: + summary = peaks.groupby(["exp_measurement", "exp_cycle"])["event_mass"].describe() + out_path = os.path.join(os.path.expanduser(output_folder), "{}_{}_summary.csv".format(NOW, exp_name)) + summary.to_csv(out_path) + +def write_config(exp_name, output_folder, config): + output_path = os.path.join(os.path.expanduser(output_folder), "{}_{}_config.json".format(NOW, exp_name)) + with open(output_path, "w") as f: + json.dump(config, f) diff --git a/lifescale/mass_peak_caller/peak_caller_gui.py b/lifescale/mass_peak_caller/peak_caller_gui.py new file mode 100644 index 0000000000000000000000000000000000000000..d618f3514a25bb44291079c708006335cb7cd049 --- /dev/null +++ b/lifescale/mass_peak_caller/peak_caller_gui.py @@ -0,0 +1,60 @@ +import os +import re +from datetime import datetime +from functools import partial +from operator import itemgetter +from gooey import Gooey, GooeyParser +import naive_peaks +import configure_peakcaller + +DISPATCHER = { + "call_peaks": naive_peaks.call_peaks, + "config": configure_peakcaller.configure_peakcaller +} + +def show_error_modal(error_msg): + """ Spawns a modal with error_msg""" + # wx imported locally so as not to interfere with Gooey + import wx + app = wx.App() + dlg = wx.MessageDialog(None, error_msg, 'Error', wx.ICON_ERROR) + dlg.ShowModal() + dlg.Destroy() + +def add_call_peak_gui(subs, config): + p = subs.add_parser('call_peaks', prog='Call Mass Peaks', help='Get Mass Peaks from Raw Lifescale Data') + p.add_argument( + 'experiment', + metavar='Choose an Experiment', + help='Choose the name of an experiment', + widget='Dropdown', + choices=naive_peaks.list_experiments(config)[0]) + p.add_argument('output_folder', widget="DirChooser") + p.add_argument('--metadata_file', '-f', widget="FileChooser", help="If provided, convert vial ids to sample names. Should be the exported csv file called PanelData.csv.") + +def add_config_gui(subs, config): + p = subs.add_parser('config', prog="Configure Program", help="Options to change where this program looks for data, and the calibration used for frequency to mass conversion.") + p.add_argument('--raw_data_folder', widget="DirChooser", help="currently {}".format(config["raw_data_folder"])) + p.add_argument('--mass_transformation', type=float, help='currently {} Hz/fg'.format(config["mass_transformation"])) + p.add_argument('--mass_cutoff', '-m', type=float, default=20, help='currently {} fg - minimum mass of the peak (minimum 5fg recommended)'.format(config["mass_cutoff"])) + p.add_argument('--peak_width_cutoff', '-w', type=float, default=5, help='currently {} - width cutoff for peaks - minimum datapoints looking larger than noise'.format(config["peak_width_cutoff"])) + p.add_argument('--peak_distance_cutoff', '-d', type=float, default=5, help='currently {} - distance cutoff for peaks - minimum datapoints between peaks'.format(config["peak_distance_cutoff"])) + +@Gooey(program_name='Mass Peak Caller', image_dir='./images', required_cols=1) +def main(): + current_config, file_not_found = configure_peakcaller.load_config() + if file_not_found: + show_error_modal("No configuration file found at {}.\nWrote default configuration to that location.\nContinuing with default config.".format(file_not_found)) + + parser = GooeyParser(description='Get Mass Peaks from Raw Lifescale Data') + subs = parser.add_subparsers(help='commands', dest='command') + add_call_peak_gui(subs, current_config) + add_config_gui(subs, current_config) + + args = parser.parse_args() + opts = vars(args) + func = partial(DISPATCHER[args.command], config=current_config) + current_config = func(**opts) + +if __name__ == '__main__': + main()