diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/PythonGUI_apps/DataBrowser.py b/PythonGUI_apps/DataBrowser.py index 5599146..2ab2b25 100644 --- a/PythonGUI_apps/DataBrowser.py +++ b/PythonGUI_apps/DataBrowser.py @@ -6,6 +6,7 @@ """ # system imports +import sys from pathlib import Path import pyqtgraph as pg @@ -13,6 +14,10 @@ from Lifetime_analysis import Lifetime_plot_fit from Spectrum_analysis import Spectra_plot_fit +from FLIM_analysis import FLIM_plot +from UV_Vis_analysis import uv_vis_analysis +from PLQE_analysis import plqe_analysis +from H5_Pkl import h5_pkl_view, h5_view_and_plot pg.mkQApp() pg.setConfigOption('background', 'w') @@ -25,37 +30,49 @@ WindowTemplate, TemplateBaseClass = pg.Qt.loadUiType(uiFile) class MainWindow(TemplateBaseClass): - - def __init__(self): - TemplateBaseClass.__init__(self) - - # Create the main window - self.ui = WindowTemplate() - self.ui.setupUi(self) - self.ui.select_comboBox.addItems(["Lifetime Analysis", "Spectrum Analysis", "UV-Vis Analysis"]) - self.ui.load_pushButton.clicked.connect(self.load_app) - - self.show() - - - def load_app(self): - - analysis_software = self.ui.select_comboBox.currentText() - - if analysis_software == "Lifetime Analysis": - self.lifetime_window = Lifetime_plot_fit.MainWindow() - self.lifetime_window.show() - - elif analysis_software == "Spectrum Analysis": - self.spectrum_window = Spectra_plot_fit.MainWindow() - self.spectrum_window.show() - - else: - print("not yet linked -- coming soon") + + def __init__(self): + TemplateBaseClass.__init__(self) + + # Create the main window + self.ui = WindowTemplate() + self.ui.setupUi(self) + self.ui.select_comboBox.addItems(["Lifetime Analysis", "Spectrum Analysis", "FLIM Analysis", + "UV-Vis Analysis", "PLQE Analysis", "H5 View/Plot", "H5/PKL Viewer"]) + self.ui.load_pushButton.clicked.connect(self.load_app) + + self.show() + + + def load_app(self): + + analysis_software = self.ui.select_comboBox.currentText() + + if analysis_software == "Lifetime Analysis": + self.lifetime_window = Lifetime_plot_fit.MainWindow() + self.lifetime_window.show() + elif analysis_software == "Spectrum Analysis": + self.spectrum_window = Spectra_plot_fit.MainWindow() + self.spectrum_window.show() + elif analysis_software == "FLIM Analysis": + self.flim_window = FLIM_plot.MainWindow() + self.flim_window.show() + elif analysis_software == "UV-Vis Analysis": + self.uv_vis_window = uv_vis_analysis.MainWindow() + self.uv_vis_window.show() + elif analysis_software == "PLQE Analysis": + self.plqe_window = plqe_analysis.MainWindow() + self.plqe_window.show() + elif analysis_software == "H5 View/Plot": + app = h5_view_and_plot.H5ViewPlot(sys.argv) + #sys.exit(app.exec_()) + elif analysis_software == "H5/PKL Viewer": + app = h5_pkl_view.H5PklView(sys.argv) + #sys.exit(app.exec_()) def run(): - win = MainWindow() - QtGui.QApplication.instance().exec_() - return + win = MainWindow() + QtGui.QApplication.instance().exec_() + return run() \ No newline at end of file diff --git a/PythonGUI_apps/DataBrowser.spec b/PythonGUI_apps/DataBrowser.spec new file mode 100644 index 0000000..d04cc9d --- /dev/null +++ b/PythonGUI_apps/DataBrowser.spec @@ -0,0 +1,36 @@ +# -*- mode: python -*- + +block_cipher = None + + +a = Analysis(['DataBrowser.py'], + pathex=['C:\\Users\\lindat18\\Dropbox\\Ginger_Lab\\Data_Analysis\\PythonGUI_apps'], + binaries=[], + datas=[], + hiddenimports=[], + hookspath=[], + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False) +pyz = PYZ(a.pure, a.zipped_data, + cipher=block_cipher) +exe = EXE(pyz, + a.scripts, + [], + exclude_binaries=True, + name='DataBrowser', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=True ) +coll = COLLECT(exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=True, + name='DataBrowser') diff --git a/PythonGUI_apps/FLIM_analysis/FLIM_plot.py b/PythonGUI_apps/FLIM_analysis/FLIM_plot.py new file mode 100644 index 0000000..a3d3ea3 --- /dev/null +++ b/PythonGUI_apps/FLIM_analysis/FLIM_plot.py @@ -0,0 +1,251 @@ +import sys +import h5py +from pathlib import Path +import os.path +import pyqtgraph as pg +from pyqtgraph.Qt import QtCore, QtGui, QtWidgets#, QColorDialog +import numpy as np +import matplotlib.pyplot as plt +import pickle +import time +from lmfit.models import GaussianModel +import customplotting.mscope as cpm +# local modules + +pg.mkQApp() +pg.setConfigOption('background', 'w') +pg.setConfigOption('imageAxisOrder', 'row-major') + +base_path = Path(__file__).parent +file_path = (base_path / "flim_plot_gui.ui").resolve() + +uiFile = file_path + +WindowTemplate, TemplateBaseClass = pg.Qt.loadUiType(uiFile) + +class MainWindow(TemplateBaseClass): + + def __init__(self): + super(TemplateBaseClass, self).__init__() + + # Create the main window + self.ui = WindowTemplate() + self.ui.setupUi(self) + + #set up ui signals + self.ui.load_scan_pushButton.clicked.connect(self.open_pkl_file) + self.ui.plot_intensity_sums_pushButton.clicked.connect(self.plot_intensity_sums) + self.ui.plot_raw_hist_data_pushButton.clicked.connect(self.plot_raw_scan) + self.ui.save_intensities_image_pushButton.clicked.connect(self.save_intensities_image) + self.ui.save_intensities_array_pushButton.clicked.connect(self.save_intensities_array) + self.ui.compare_checkBox.stateChanged.connect(self.switch_compare) + self.ui.intensity_sums_viewBox.roi.sigRegionChanged.connect(self.line_profile_update_plot) + self.ui.import_pkl_pushButton.clicked.connect(self.import_pkl_to_convert) + self.ui.pkl_to_h5_pushButton.clicked.connect(self.pkl_to_h5) + + self.show() + + def open_pkl_file(self): + """ Open FLIM scan file """ + try: + self.filename = QtWidgets.QFileDialog.getOpenFileName(self) + self.pkl_file = pickle.load(open(self.filename[0], 'rb')) + except Exception as err: + print(format(err)) + + def import_pkl_to_convert(self): + """ Open pkl file to convert to h5 """ + try: + self.pkl_to_convert = QtWidgets.QFileDialog.getOpenFileName(self) + self.ui.result_textBrowser.append("Done Loading - .pkl to convert") + except: + pass + + def plot_intensity_sums(self): + try: + data = self.pkl_file + self.numb_pixels_X = int((data['Scan Parameters']['X scan size (um)'])/(data['Scan Parameters']['X step size (um)'])) + self.numb_pixels_Y = int((data['Scan Parameters']['Y scan size (um)'])/(data['Scan Parameters']['Y step size (um)'])) + self.x_step_size = float(data['Scan Parameters']['X step size (um)']) + self.x_scan_size = float(data['Scan Parameters']['X scan size (um)']) + self.y_step_size = float(data['Scan Parameters']['Y step size (um)']) + self.y_scan_size = float(data['Scan Parameters']['Y scan size (um)']) + + hist_data = data["Histogram data"] + hist_data = np.reshape(hist_data, newshape=(hist_data.shape[0], self.numb_pixels_X*self.numb_pixels_Y)) + self.intensity_sums = np.sum(hist_data, axis=0) #sum intensities for each pixel + self.intensity_sums = np.reshape(self.intensity_sums, newshape=(self.numb_pixels_X, self.numb_pixels_Y)) + self.ui.intensity_sums_viewBox.view.invertY(False) # stop y axis invert + self.ui.intensity_sums_viewBox.setImage(self.intensity_sums, scale= + (data['Scan Parameters']['X step size (um)'], + data['Scan Parameters']['Y step size (um)'])) + self.ui.intensity_sums_viewBox.roi.setSize([self.x_scan_size, self.y_step_size]) #line roi + scale = pg.ScaleBar(size=1,suffix='um') + scale.setParentItem(self.ui.intensity_sums_viewBox.view) + scale.anchor((1, 1), (1, 1), offset=(-30, -30)) + except Exception as err: + print(format(err)) + + def line_profile_update_plot(self): + """ Handle line profile for intensity sum viewbox """ + if hasattr(self, "intensity_sums"): + roiPlot = self.ui.intensity_sums_viewBox.getRoiPlot() + roiPlot.clear() + roi = self.ui.intensity_sums_viewBox.roi + + image = self.ui.intensity_sums_viewBox.getProcessedImage() + + # Extract image data from ROI + axes = (self.ui.intensity_sums_viewBox.axes['x'], self.ui.intensity_sums_viewBox.axes['y']) + data, coords = roi.getArrayRegion(image.view(np.ndarray), self.ui.intensity_sums_viewBox.imageItem, axes, returnMappedCoords=True) + + #calculate sums along columns in region + sums_to_plot = np.sum(data, axis=0) + + #get scan x-coordinates in region + x_values = coords[1][0] + + try: + roiPlot.plot(x_values, sums_to_plot) + except: + pass + + def plot_raw_scan(self): + try: + data = self.pkl_file + self.numb_pixels_X = int((data['Scan Parameters']['X scan size (um)'])/(data['Scan Parameters']['X step size (um)'])) + self.numb_pixels_Y = int((data['Scan Parameters']['Y scan size (um)'])/(data['Scan Parameters']['Y step size (um)'])) + self.x_step_size = float(data['Scan Parameters']['X step size (um)']) + self.x_scan_size = float(data['Scan Parameters']['X scan size (um)']) + self.y_step_size = float(data['Scan Parameters']['Y step size (um)']) + self.y_scan_size = float(data['Scan Parameters']['Y scan size (um)']) + # TODO test line scan plots + hist_data = data['Histogram data'] + + self.hist_image = np.reshape(hist_data, newshape=(hist_data.shape[0],self.numb_pixels_X,self.numb_pixels_Y)) + time_data = data['Time data'] + self.times = time_data[:, 0, 0]*1e-3 + self.ui.raw_hist_data_viewBox.view.invertY(False) # stops y-axis invert + self.ui.raw_hist_data_viewBox.setImage(self.hist_image, scale= + (data['Scan Parameters']['X step size (um)'], + data['Scan Parameters']['Y step size (um)']), xvals=self.times) + self.ui.raw_hist_data_viewBox.roi.setSize([self.x_scan_size, self.y_scan_size]) + # if self.ui.compare_checkBox.isChecked(): + # self.ui.imv2.setImage(self.hist_image, scale= (data['Scan Parameters']['X step size (um)'], + # data['Scan Parameters']['Y step size (um)']), xvals=self.times) + self.switch_compare() + self.ui.raw_hist_data_viewBox.ui.roiBtn.clicked.connect(self.switch_compare) + scale = pg.ScaleBar(size=1,suffix='um') + scale.setParentItem(self.ui.raw_hist_data_viewBox.view) + scale.anchor((1, 1), (1, 1), offset=(-30, -30)) + + except Exception as err: + print(format(err)) + + def switch_compare(self): + """ + Handles compare checkbox. If checked, show second ROI on raw histogram data that user can use for comparison to first ROI. + """ + if self.ui.compare_checkBox.isChecked() and hasattr(self, "hist_image"): + if not hasattr(self, "roi2"): #create roi if doesn't exist yet + self.roi2 = pg.ROI(pos=[0,0], size=[int(self.x_scan_size/2), int(self.y_scan_size/2)], movable=True, pen='r') + self.roi2.addScaleHandle([1, 1], [0, 0]) + self.roi2.addRotateHandle([0, 0], [1, 1]) + self.roi2.sigRegionChanged.connect(self.update_roi2_plot) + self.ui.raw_hist_data_viewBox.addItem(self.roi2) + self.update_roi2_plot() + self.roi2.hide() + self.roi2_plot.hide() + if self.ui.raw_hist_data_viewBox.ui.roiBtn.isChecked(): + self.roi2.show() + self.roi2_plot.show() + else: + self.roi2.hide() + self.roi2_plot.hide() + else: #if not checked, hide roi + if hasattr(self, "roi2"): + self.roi2.hide() + self.roi2_plot.hide() + + def update_roi2_plot(self): + """ Update plot corresponding to second roi """ + #Adapted from pyqtgraph imageview sourcecode + + image = self.ui.raw_hist_data_viewBox.getProcessedImage() + + # Extract image data from ROI + axes = (self.ui.raw_hist_data_viewBox.axes['x'], self.ui.raw_hist_data_viewBox.axes['y']) + data, coords = self.roi2.getArrayRegion(image.view(np.ndarray), self.ui.raw_hist_data_viewBox.imageItem, axes, returnMappedCoords=True) + if data is None: + return + + # Average data within entire ROI for each frame + data = data.mean(axis=max(axes)).mean(axis=min(axes)) + xvals = self.ui.raw_hist_data_viewBox.tVals + if hasattr(self, "roi2_plot"): + self.roi2_plot.clear() + self.roi2_plot = self.ui.raw_hist_data_viewBox.getRoiPlot().plot(xvals, data, pen='r') + + def save_intensities_image(self): + try: + filename_ext = os.path.basename(self.filename[0]) + filename = os.path.splitext(filename_ext)[0] #get filename without extension + save_to = os.getcwd() + "\\" + filename + "_intensity_sums.png" + cpm.plot_confocal(self.intensity_sums, stepsize=np.abs(self.pkl_file['Scan Parameters']['X step size (um)'])) + cpm.plt.savefig(save_to, bbox_inches='tight', dpi=300) + except: + pass + + def save_intensities_array(self): + try: + filename_ext = os.path.basename(self.filename[0]) + filename = os.path.splitext(filename_ext)[0] #get filename without extension + save_to = os.getcwd() + "\\" + filename + "_intensity_sums.txt" + np.savetxt(save_to, self.intensity_sums.T, fmt='%f') #save transposed intensity sums, as original array handles x in cols and y in rows + except: + pass + + def pkl_to_h5(self): + #Convert scan .pkl file into h5 + try: + folder = os.path.dirname(self.pkl_to_convert[0]) + filename_ext = os.path.basename(self.pkl_to_convert[0]) + filename = os.path.splitext(filename_ext)[0] #get filename without extension + pkl_file = pickle.load(open(self.pkl_to_convert[0], 'rb')) + + h5_filename = folder + "/" + filename + ".h5" + h5_file = h5py.File(h5_filename, "w") + self.traverse_dict_into_h5(pkl_file, h5_file) + except Exception as err: + print(format(err)) + + def traverse_dict_into_h5(self, dictionary, h5_output): + #Create an h5 file using .pkl with scan data and params + for key in dictionary: + if type(dictionary[key]) == dict: #if subdictionary, create a group + group = h5_output.create_group(key) + previous_dict = dictionary[key] + self.traverse_dict_into_h5(dictionary[key], group) #traverse subdictionary + else: + if key == "Histogram data" or key == "Time data": + h5_output.create_dataset(key, data=dictionary[key]) + else: + h5_output.attrs[key] = dictionary[key] #if not dataset, create attribute + + def close_application(self): + choice = QtGui.QMessageBox.question(self, 'EXIT!', + "Do you want to exit the app?", + QtGui.QMessageBox.Yes | QtGui.QMessageBox.No) + if choice == QtGui.QMessageBox.Yes: + sys.exit() + else: + pass + +"""Run the Main Window""" +def run(): + win = MainWindow() + QtGui.QApplication.instance().exec_() + return win + +#Uncomment below if you want to run this as standalone +#run() \ No newline at end of file diff --git a/PythonGUI_apps/FLIM_analysis/flim_plot_gui.ui b/PythonGUI_apps/FLIM_analysis/flim_plot_gui.ui new file mode 100644 index 0000000..5daa01e --- /dev/null +++ b/PythonGUI_apps/FLIM_analysis/flim_plot_gui.ui @@ -0,0 +1,171 @@ + + + Form + + + + 0 + 0 + 738 + 876 + + + + Form + + + + + + 0 + + + + Analysis + + + + + + + + Load Scan + + + + + + + + + Plot + + + + + + + Save intensities array + + + + + + + Save intensities image + + + + + + + + + + 500 + 300 + + + + + + + + + 12 + + + + Histogram Intensity Sums + + + + + + + + 12 + + + + Raw Histogram Data + + + + + + + + 500 + 300 + + + + + + + + + + Plot + + + + + + + Compare ROIs + + + + + + + + + + + + .pkl to .h5 + + + + + 20 + 20 + 171 + 34 + + + + Import .pkl file + + + + + + 20 + 80 + 171 + 34 + + + + .pkl to .h5 + + + + + + + + + + ImageView + QGraphicsView +
pyqtgraph
+
+
+ + +
diff --git a/PythonGUI_apps/H5_Pkl/h5_pkl_view.py b/PythonGUI_apps/H5_Pkl/h5_pkl_view.py new file mode 100644 index 0000000..7be99a5 --- /dev/null +++ b/PythonGUI_apps/H5_Pkl/h5_pkl_view.py @@ -0,0 +1,124 @@ +from __future__ import division, print_function, absolute_import +from ScopeFoundry import BaseApp +from ScopeFoundry.helper_funcs import load_qt_ui_file, sibling_path +from collections import OrderedDict +import os +from qtpy import QtCore, QtWidgets, QtGui +import pyqtgraph as pg +import pyqtgraph.dockarea as dockarea +import numpy as np +from ScopeFoundry.logged_quantity import LQCollection +from scipy.stats import spearmanr +import argparse +from .h5_tree import H5TreeSearchView +from .pkl_tree import PklTreeSearchView + +pg.setConfigOption('imageAxisOrder', 'row-major') + +class H5PklView(BaseApp): + + name = "h5_pkl_view" + + def __init__(self, argv): + BaseApp.__init__(self, argv) + self.setup() + parser = argparse.ArgumentParser() + for lq in self.settings.as_list(): + parser.add_argument("--" + lq.name) + args = parser.parse_args() + for lq in self.settings.as_list(): + if lq.name in args: + val = getattr(args,lq.name) + if val is not None: + lq.update_value(val) + + def setup(self): + self.ui_filename = sibling_path(__file__, "h5_pkl_view_gui.ui") + self.ui = load_qt_ui_file(self.ui_filename) + self.ui.show() + self.ui.raise_() + + self.views = OrderedDict() + + self.settings.New('data_filename', dtype='file') + self.settings.New('auto_select_view',dtype=bool, initial=True) + self.settings.New('view_name', dtype=str, initial='0', choices=('0',)) + + self.settings.data_filename.add_listener(self.on_change_data_filename) + + # UI Connections/ + self.settings.data_filename.connect_to_browse_widgets(self.ui.data_filename_lineEdit, + self.ui.data_filename_browse_pushButton) + + # set views + self.h5treeview = H5TreeSearchView(self) + self.load_view(self.h5treeview) + self.pkltreeview = PklTreeSearchView(self) + self.load_view(self.pkltreeview) + + self.settings.view_name.add_listener(self.on_change_view_name) + + self.current_view = None + + self.ui.show() + + def load_view(self, new_view): + # add to views dict + self.views[new_view.name] = new_view + + self.ui.dataview_page.layout().addWidget(new_view.ui) + new_view.ui.hide() + + # update choices for view_name + self.settings.view_name.change_choice_list(list(self.views.keys())) + return new_view + + def on_change_data_filename(self): + #Handle file change + try: + fname = self.settings.data_filename.val + if not self.settings['auto_select_view']: + self.current_view.on_change_data_filename(fname) + else: + view_name = self.auto_select_view(fname) + if self.current_view is None or view_name != self.current_view.name: + # update view (automatically calls on_change_data_filename) + self.settings['view_name'] = view_name + else: + # force update + if os.path.isfile(fname): + self.current_view.on_change_data_filename(fname) + except: + pass + + def on_change_view_name(self): + #Handle view change - happens when filetype changes + self.ui.dataview_placeholder.hide() + previous_view = self.current_view + + self.current_view = self.views[self.settings['view_name']] + # hide current view + # (handle the initial case where previous_view is None ) + if previous_view: + previous_view.ui.hide() + + # show new view + self.current_view.ui.show() + + # set datafile for new (current) view + fname = self.settings['data_filename'] + if os.path.isfile(fname): + self.current_view.on_change_data_filename(self.settings['data_filename']) + + def auto_select_view(self, fname): + #return the name of the last supported view for the given fname + for view_name, view in list(self.views.items())[::-1]: + if view.is_file_supported(fname): + return view_name + # return default file_info view if no others work + return "h5_tree_search" + +# if __name__ == '__main__': +# import sys +# app = H5PklView(sys.argv) +# sys.exit(app.exec_()) diff --git a/PythonGUI_apps/H5_Pkl/h5_pkl_view_gui.ui b/PythonGUI_apps/H5_Pkl/h5_pkl_view_gui.ui new file mode 100644 index 0000000..08ec7ff --- /dev/null +++ b/PythonGUI_apps/H5_Pkl/h5_pkl_view_gui.ui @@ -0,0 +1,89 @@ + + + MainWindow + + + + 0 + 0 + 856 + 925 + + + + ScopeFoundry Data Browser + + + + + + + Qt::Horizontal + + + + File + + + + + + + + + File: + + + + + + + Browse... + + + + + + + true + + + 0 + + + + Data View + + + + + + Once a file is loaded, data will show up here. + + + + + + + + + + + + + + + + + 0 + 0 + 856 + 31 + + + + + + + + diff --git a/PythonGUI_apps/H5_Pkl/h5_tree.py b/PythonGUI_apps/H5_Pkl/h5_tree.py new file mode 100644 index 0000000..c087f78 --- /dev/null +++ b/PythonGUI_apps/H5_Pkl/h5_tree.py @@ -0,0 +1,99 @@ +from ScopeFoundry.data_browser import DataBrowserView +from qtpy import QtWidgets +import h5py + +class H5TreeSearchView(DataBrowserView): + + name = 'h5_tree_search' + + def is_file_supported(self, fname): + return ('.h5' in fname) + + def setup(self): + #self.settings.New('search_text', dtype=str, initial="") + + self.ui = QtWidgets.QWidget() + self.ui.setLayout(QtWidgets.QVBoxLayout()) + self.search_lineEdit = QtWidgets.QLineEdit() + self.search_lineEdit.setPlaceholderText("Search") + self.tree_textEdit = QtWidgets.QTextEdit("") + + self.ui.layout().addWidget(self.search_lineEdit) + self.ui.layout().addWidget(self.tree_textEdit) + + #self.settings.search_text.connect_to_widget(self.search_lineEdit) + #self.settings.search_text.add_listener(self.on_new_search_text) + self.search_text = "" + + self.search_lineEdit.textChanged.connect(self.on_new_search_text) + + def on_change_data_filename(self, fname=None): + """ Handle file change """ + self.tree_textEdit.setText("loading {}".format(fname)) + try: + #if using h5_plot_and_view + if hasattr(self.databrowser.ui, "dataset_listWidget"): + self.dataset_dict = {} + self.databrowser.ui.dataset_listWidget.clear() + + self.fname = fname + self.f = h5py.File(fname, 'r') + self.on_new_search_text() + self.databrowser.ui.statusbar.showMessage("") + + except Exception as err: + msg = "Failed to load %s:\n%s" %(fname, err) + self.databrowser.ui.statusbar.showMessage(msg) + self.tree_textEdit.setText(msg) + raise(err) + + def on_new_search_text(self, x=None): + if x is not None: + self.search_text = x.lower() + old_scroll_pos = self.tree_textEdit.verticalScrollBar().value() + self.tree_str = "" + self.f.visititems(self._visitfunc) + + self.tree_text_html = \ + """{}
+
+ {} +
+ """.format(self.fname, self.tree_str) + + self.tree_textEdit.setText(self.tree_text_html) + self.tree_textEdit.verticalScrollBar().setValue(old_scroll_pos) + + def _visitfunc(self, name, node): + level = len(name.split('/')) + indent = ' '*4*(level-1) + + #indent = ''.format(level*4) + localname = name.split('/')[-1] + + #search_text = self.settings['search_text'].lower() + search_text = self.search_text + if search_text and (search_text in localname.lower()): #highlight terms that contain search text + localname = """{}""".format(localname) + + if isinstance(node, h5py.Group): + self.tree_str += indent +"|> {}/
".format(localname) + elif isinstance(node, h5py.Dataset): + self.tree_str += indent +"|D {}: {} {}
".format(localname, node.shape, node.dtype) + + #if using h5_plot_and_view + if hasattr(self.databrowser.ui, "dataset_listWidget"): + item_name = "{}: {} {}".format(localname, node.shape, node.dtype) + self.databrowser.ui.dataset_listWidget.addItem(item_name) + if not hasattr(self, "dataset_dict"): + self.dataset_dict = {} + self.dataset_dict[item_name] = node + + + for key, val in node.attrs.items(): #highlight terms that contain search text + if search_text: + if search_text in str(key).lower(): + key = """{}""".format(key) + if search_text in str(val).lower(): + val = """{}""".format(val) + self.tree_str += indent+"     |- {} = {}
".format(key, val) \ No newline at end of file diff --git a/PythonGUI_apps/H5_Pkl/h5_view_and_plot.py b/PythonGUI_apps/H5_Pkl/h5_view_and_plot.py new file mode 100644 index 0000000..4b162f6 --- /dev/null +++ b/PythonGUI_apps/H5_Pkl/h5_view_and_plot.py @@ -0,0 +1,136 @@ +from __future__ import division, print_function, absolute_import +from ScopeFoundry import BaseApp +from ScopeFoundry.helper_funcs import load_qt_ui_file, sibling_path +from collections import OrderedDict +import os +from qtpy import QtCore, QtWidgets, QtGui +import pyqtgraph as pg +import pyqtgraph.dockarea as dockarea +import numpy as np +from ScopeFoundry.logged_quantity import LQCollection +from scipy.stats import spearmanr +import argparse +from .h5_tree import H5TreeSearchView +from .pkl_tree import PklTreeSearchView + +pg.setConfigOption('imageAxisOrder', 'row-major') + +class H5ViewPlot(BaseApp): + + name = "h5_view_plot" + + def __init__(self, argv): + BaseApp.__init__(self, argv) + self.setup() + parser = argparse.ArgumentParser() + for lq in self.settings.as_list(): + parser.add_argument("--" + lq.name) + args = parser.parse_args() + for lq in self.settings.as_list(): + if lq.name in args: + val = getattr(args,lq.name) + if val is not None: + lq.update_value(val) + + def setup(self): + self.ui_filename = sibling_path(__file__, "h5_view_and_plot_gui.ui") + self.ui = load_qt_ui_file(self.ui_filename) + self.ui.show() + self.ui.raise_() + + self.settings.New('data_filename', dtype='file') + + self.settings.data_filename.add_listener(self.on_change_data_filename) + + self.settings.New('view_name', dtype=str, initial='0', choices=('0',)) + + # UI Connections + self.settings.data_filename.connect_to_browse_widgets(self.ui.data_filename_lineEdit, + self.ui.data_filename_browse_pushButton) + self.ui.plot_pushButton.clicked.connect(self.plot_dataset) + self.ui.dataset_listWidget.currentItemChanged.connect(self.on_data_selection) + self.ui.plot_radioButton.toggled.connect(self.update_data_widget) + self.ui.image_radioButton.toggled.connect(self.update_data_widget) + + #set up image item for 2d array + self.data_img_layout = pg.GraphicsLayoutWidget() + self.ui.imageItem_page.layout().addWidget(self.data_img_layout) + self.data_img_layout = self.data_img_layout.addViewBox() + self.data_img = pg.ImageItem() + self.data_img_layout.addItem(self.data_img) + + #set up image view for 3d array + self.ui.data_imageView.getView().invertY(False) + + self.h5treeview = H5TreeSearchView(self) + self.ui.dataview_page.layout().addWidget(self.h5treeview.ui) + self.h5treeview.ui.hide() + self.ui.show() + + def on_change_data_filename(self): + """ Handle file change """ + try: + fname = self.settings.data_filename.val + if os.path.isfile(fname): + self.h5treeview.on_change_data_filename(fname) + self.ui.dataview_placeholder.hide() + self.h5treeview.ui.show() + except: + pass + + def plot_dataset(self): + """ Plot data set depending on dataset shape and plot type option. """ + self.plot = self.ui.data_plotWidget.getPlotItem() + self.plot.clear() + + data = self.dataset[()] + if self.dataset_shape == 1: + x_start = self.ui.plotWidget_x_start_spinBox.value() + x_end = self.ui.plotWidget_x_end_spinBox.value() + num_points = self.dataset.shape[0] + x_values = np.linspace(x_start, x_end, num_points) + self.plot.plot(x_values, data) + elif self.dataset_shape == 2 and self.ui.plot_radioButton.isChecked(): + self.plot.plot(data[0], data[1]) # TODO check and test this + elif self.dataset_shape == 2 and self.ui.image_radioButton.isChecked(): + self.data_img.setImage(data) + elif self.dataset_shape == 3: + x_start = self.ui.imageView_x_start_spinBox.value() + x_end = self.ui.imageView_x_end_spinBox.value() + num_points = self.dataset.shape[0] + x_values = np.linspace(x_start, x_end, num_points) #scale x axis + self.ui.data_imageView.setImage(data, xvals=x_values) + + def on_data_selection(self): + """ Handle dataset selection """ + try: + dataset_name = self.ui.dataset_listWidget.currentItem().text() + self.dataset = self.h5treeview.dataset_dict[dataset_name] + self.dataset_shape = len(self.dataset[()].shape) + self.update_data_widget() + if self.dataset_shape == 1: + self.ui.plot_type_groupBox.setEnabled(False) + self.ui.plot_radioButton.setChecked(True) + elif self.dataset_shape == 2: + self.ui.plot_type_groupBox.setEnabled(True) + elif self.dataset_shape == 3: + self.ui.plot_type_groupBox.setEnabled(False) + self.ui.image_radioButton.setChecked(True) + except: + pass + + def update_data_widget(self): + """ Decide which widget to display based on dataset shape and plot type option. """ + if self.dataset_shape == 1: + self.ui.data_stackedWidget.setCurrentIndex(0) + elif self.dataset_shape == 2 and self.ui.plot_radioButton.isChecked(): + self.ui.data_stackedWidget.setCurrentIndex(0) + elif self.dataset_shape == 2 and self.ui.image_radioButton.isChecked(): + self.ui.data_stackedWidget.setCurrentIndex(1) + elif self.dataset_shape == 3: + self.ui.data_stackedWidget.setCurrentIndex(2) + +# if __name__ == '__main__': +# import sys +# app = H5ViewPlot(sys.argv) +# sys.exit(app.exec_()) \ No newline at end of file diff --git a/PythonGUI_apps/H5_Pkl/h5_view_and_plot_gui.ui b/PythonGUI_apps/H5_Pkl/h5_view_and_plot_gui.ui new file mode 100644 index 0000000..de399cb --- /dev/null +++ b/PythonGUI_apps/H5_Pkl/h5_view_and_plot_gui.ui @@ -0,0 +1,280 @@ + + + MainWindow + + + + 0 + 0 + 856 + 925 + + + + ScopeFoundry Data Browser + + + + + + + Qt::Horizontal + + + + File + + + + + + + + + File: + + + + + + + Browse... + + + + + + + true + + + 0 + + + + Data View + + + + + + Once a file is loaded, data will show up here. + + + + + + + + true + + + Plot + + + + + + Datasets + + + + + + + + + + + + Plot data + + + + + + 0 + + + + + + + + + + + + + 203 + 16777215 + + + + 9999999.000000000000000 + + + 0.000000000000000 + + + + + + + X start + + + + + + + 9999999.000000000000000 + + + + + + + X end + + + + + + + + + + + + + + + + + + + + + 9999999.000000000000000 + + + + + + + + 203 + 16777215 + + + + 9999999.000000000000000 + + + 0.000000000000000 + + + + + + + X start + + + + + + + X end + + + + + + + + + + + + + Plot + + + + + + + true + + + Type + + + + + + true + + + Plot + + + true + + + + + + + true + + + Image + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 856 + 31 + + + + + + + + PlotWidget + QGraphicsView +
pyqtgraph
+
+ + ImageView + QGraphicsView +
pyqtgraph
+
+
+ + +
diff --git a/PythonGUI_apps/H5_Pkl/pkl_tree.py b/PythonGUI_apps/H5_Pkl/pkl_tree.py new file mode 100644 index 0000000..3a24c76 --- /dev/null +++ b/PythonGUI_apps/H5_Pkl/pkl_tree.py @@ -0,0 +1,100 @@ +from ScopeFoundry.data_browser import DataBrowserView +from qtpy import QtWidgets +import h5py +import pickle +import numpy as np + +class PklTreeSearchView(DataBrowserView): + + name = 'pkl_tree_search' + + def is_file_supported(self, fname): + return ('.pkl' in fname) + + def setup(self): + #self.settings.New('search_text', dtype=str, initial="") + + self.ui = QtWidgets.QWidget() + self.ui.setLayout(QtWidgets.QVBoxLayout()) + self.search_lineEdit = QtWidgets.QLineEdit() + self.search_lineEdit.setPlaceholderText("Search") + self.tree_textEdit = QtWidgets.QTextEdit("") + + self.ui.layout().addWidget(self.search_lineEdit) + self.ui.layout().addWidget(self.tree_textEdit) + + #self.settings.search_text.connect_to_widget(self.search_lineEdit) + #self.settings.search_text.add_listener(self.on_new_search_text) + self.search_text = "" + + self.search_lineEdit.textChanged.connect(self.on_new_search_text) + + def on_change_data_filename(self, fname=None): + """ Handle file change """ + self.tree_textEdit.setText("loading {}".format(fname)) + try: + self.fname = fname + #self.f = h5py.File(fname, 'r') + self.dictionary = pickle.load(open(self.fname, 'rb')) + self.on_new_search_text() + self.databrowser.ui.statusbar.showMessage("") + + except Exception as err: + msg = "Failed to load %s:\n%s" %(fname, err) + self.databrowser.ui.statusbar.showMessage(msg) + self.tree_textEdit.setText(msg) + raise(err) + + def on_new_search_text(self, x=None): + if x is not None: + self.search_text = x.lower() + old_scroll_pos = self.tree_textEdit.verticalScrollBar().value() + self.tree_str = "" + #self.f.visititems(self._visitfunc) + self.traverse_dict(self.dictionary, self.dictionary, 0) + + + self.tree_text_html = \ + """{}
+
+ {} +
+ """.format(self.fname, self.tree_str) + + self.tree_textEdit.setText(self.tree_text_html) + self.tree_textEdit.verticalScrollBar().setValue(old_scroll_pos) + + def traverse_dict(self, dictionary, previous_dict, level): + """ + Visit all values in the dictionary and its subdictionaries. + + dictionary -- dictionary to traverse + previous_dict -- dictionary one level up + level -- track how far to indent + """ + for key in dictionary: + if key not in previous_dict: + level -=1 + indent = " "*4*(level) + + if type(dictionary[key]) == dict: + print_string = key + if self.search_text and self.search_text in print_string: + self.tree_str += indent + """{}""".format(print_string) + else: + self.tree_str += indent + "|> {}/
".format(print_string) + level += 1 + previous_dict = dictionary[key] + self.traverse_dict(dictionary[key], previous_dict, level) + else: + value = dictionary[key] + if type(value) == np.ndarray or type(value)==np.memmap: + value = str(value.shape) + " " + str(value.dtype) + # if type(value) == list and len(value) > 5: ##account for data stored in lists + # value = str(np.asarray(value).shape) + " " + str(type(value[0])) + + print_string = key + " = " + str(value) + if self.search_text and self.search_text in print_string: + self.tree_str += indent + """{}""".format(print_string) + else: + self.tree_str += indent + "|- {}
".format(print_string) \ No newline at end of file diff --git a/PythonGUI_apps/Lifetime_analysis/Fit_functions_with_irf.py b/PythonGUI_apps/Lifetime_analysis/Fit_functions_with_irf.py new file mode 100644 index 0000000..5b454af --- /dev/null +++ b/PythonGUI_apps/Lifetime_analysis/Fit_functions_with_irf.py @@ -0,0 +1,329 @@ +import numpy as np +import matplotlib.pyplot as plt +from scipy.optimize import fmin_tnc, differential_evolution +from scipy.special import gamma +from scipy.signal import fftconvolve +from scipy.integrate import odeint + + +"""Fit TCSPC data to a model by reconvolution with the IRF +Convolution is done in time domain with np.convolve() +Convolution can be done in the frequency domain of np.convolve() is replaced by scipy.signal.fftconvolve() + +For a good tutorial on numerical convolution, see section 13.1 of Numerical Recipes: + +Press, W. H.; Teukolsky, S. A.; Vetterling, W. T.; Flannery, B. P., +Numerical Recipes 3rd Edition: The Art of Scientific Computing. 3 ed.; +Cambridge University Press: New York, 2007 + +***Note that algorithm given in Numerical Recipes does convolution in the frequency +domain using the FFT. However the discussion of convolution in 13.1 applies to the time +domain and can be used to understand this Python code.*** + +-MZ, 2/2017 +""" + +def convolve_sig_resp(signal_array, response_array, t_array, tstep): + + def normalize_response(response_array, t_array): + area = np.trapz(response_array, x = t_array) + return response_array / area + + def array_zeropad_neg(array, pad_length): + + return np.pad(array, (pad_length, 0), 'constant', constant_values = (0,0)) + + def array_zeropad_pos(array, pad_length): + return np.pad(array, (0, pad_length), 'constant', constant_values = (0,0)) + +# def array_symmetricpad_neg(array, pad_length): +# +# return np.pad(array, (pad_length, 0), 'symmetric') + + def signal_and_resp_forconv(signal_array, response_array): + resp_pad_negtime = array_zeropad_neg(normalize_response(response_array, t_array), len(response_array) - 1) + sig_pad_negtime = array_zeropad_neg(signal_array, len(signal_array) - 1) + sig_pad_postime = array_zeropad_pos(sig_pad_negtime, len(response_array)) + return [resp_pad_negtime, sig_pad_postime] + + resp, sig = signal_and_resp_forconv(signal_array, response_array) + convolution = tstep * fftconvolve(sig, resp, mode = 'same')#np.convolve(resp, sig, mode = 'same') + + return convolution[len(signal_array) - 1 : (2*len(signal_array)) - 1] + +def convolution_plusnoise(signal_array, response_array, t_array, tstep, noiselevel): + return convolve_sig_resp(signal_array, response_array, t_array, tstep) + noiselevel + +def herz_ode(t, n0, params): + a = params[0] + k1 = params[1] + k2 = params[2] + k3 = params[3] + def odefun(n, t, k1, k2, k3): + dndt = -(k1 * n) - (k2 * (n ** 2.0)) - (k3 * (n ** 3.0)) + return dndt + ode_sol = odeint(odefun, n0, t, args = (k1, k2, k3))[:,0] + pl = k2 * (ode_sol ** 2.0) + return a*pl + +def fit_herz_ode_global_3traces_fmin_tnc(t1, t2, t3, tstep, d1, d2, d3, irf, init_params, bounds, n0array): + time_array1 = t1 + time_array2 = t2 + time_array3 = t3 + data_array1 = d1 + data_array2 = d2 + data_array3 = d3 + n0 = n0array[0] + n1 = n0array[1] + n2 = n0array[2] + def min_fit_decay(params): + #Minimize chi-squre for data set with Poisson distribution () + + a0 = params[0] + a1 = params[1] + a2 = params[2] + k1 = params[3] + k2 = params[4] + k3 = params[5] + noise1 = params[6] + noise2 = params[7] + noise3 = params[8] + decaymodel1 = herz_ode(time_array1, n0, np.array([a0,k1,k2,k3])) + decaymodel2 = herz_ode(time_array2, n1, np.array([a1,k1,k2,k3])) + decaymodel3 = herz_ode(time_array3, n2, np.array([a2,k1,k2,k3])) + model1 = convolution_plusnoise(decaymodel1, irf, time_array1, tstep, noise1) + model2 = convolution_plusnoise(decaymodel2, irf, time_array2, tstep, noise2) + + model3 = convolution_plusnoise(decaymodel3, irf, time_array3, tstep, noise3) + + data_fit_idx1 = np.nonzero(data_array1) + data_fit_idx2 = np.nonzero(data_array2) + data_fit_idx3 = np.nonzero(data_array3) + data_array_fit1 = data_array1[data_fit_idx1] + data_array_fit2 = data_array2[data_fit_idx2] + data_array_fit3 = data_array3[data_fit_idx3] + + model_fit1 = model1[data_fit_idx1] + model_fit2 = model2[data_fit_idx2] + model_fit3 = model3[data_fit_idx3] + + min1 = np.sum(((data_array_fit1 - model_fit1)** 2.0) / (np.sqrt(data_array_fit1) ** 2.0)) + min2 = np.sum(((data_array_fit2 - model_fit2)** 2.0) / (np.sqrt(data_array_fit2) ** 2.0)) + min3 = np.sum(((data_array_fit3 - model_fit3)** 2.0) / (np.sqrt(data_array_fit3) ** 2.0)) + return np.sqrt((min1 ** 2.0) + (min2 ** 2.0) + (min3 ** 2.0)) + bestfit_params = fmin_tnc(min_fit_decay, init_params, approx_grad = True, bounds = bounds)[0] + def bestfit_decay(params): + a0 = params[0] + a1 = params[1] + a2 = params[2] + k1 = params[3] + k2 = params[4] + k3 = params[5] + noise1 = params[6] + # noise2 = params[7] + # noise3 = params[8] + decaymodel1 = herz_ode(time_array, n0, np.array([a0,k1,k2,k3])) + decaymodel2 = herz_ode(time_array, n1, np.array([a1,k1,k2,k3])) + decaymodel3 = herz_ode(time_array, n2, np.array([a2,k1,k2,k3])) + + model1 = convolution_plusnoise(decaymodel1, irf, time_array, tstep, noise1) + model2 = convolution_plusnoise(decaymodel2, irf, time_array, tstep, noise2) + model3 = convolution_plusnoise(decaymodel3, irf, time_array, tstep, noise3) + return [model1, model2, model3] + + bestfit_model = bestfit_decay(bestfit_params) + # plt.figure() + # plt.ylabel('PL Counts') + # plt.xlabel('Time (ns)') + # plt.semilogy(time_array1, data_array1,'b', label = 'Data') + # plt.semilogy(time_array1, bestfit_model[0], 'r', label = 'Fit') + # plt.semilogy(time_array2, data_array2,'b', label = 'Data') + # plt.semilogy(time_array2, bestfit_model[1], 'r', label = 'Fit') + # plt.semilogy(time_array3, data_array3,'b', label = 'Data') + # plt.semilogy(time_array3, bestfit_model[2], 'r', label = 'Fit') + # plt.legend(loc = 'best') + return bestfit_params, bestfit_model, data_array, time_array, irf + +def multi_exp(t, params, num_exp): + exp_array = np.empty((len(t), num_exp)) + + i = 0 + while (iMainWindow - + + + + + + + true + + + + 10 + + + + Fitting Controls + + + false + + + + + + + + + 10 + + + + Fit with IRF + + + true + + + false + + + + + + + + 10 + + + + Fitting Function + + + + + + + + 10 + + + + + + + + true + + + + 10 + + + + Fitting method + + + + + + + + 10 + + + + + + + + + + + + + + + + + 0 + + + + + + + true + + + + 10 + + + + Bounds + + + + + + 4 + + + 9999999.000000000000000 + + + 1.100000000000000 + + + + + + + 4 + + + 9999999.000000000000000 + + + 0.900000000000000 + + + + + + + 4 + + + 1.000000000000000 + + + + + + + 4 + + + 9999999.000000000000000 + + + 10000.000000000000000 + + + + + + + 4 + + + 9999999.000000000000000 + + + + + + + tc (ns) + + + + + + + 4 + + + 9999999.000000000000000 + + + + + + + beta + + + + + + + noise + + + + + + + 4 + + + 1.000000000000000 + + + 1.000000000000000 + + + + + + + a + + + + + + + 4 + + + 9999999.000000000000000 + + + 10000.000000000000000 + + + + + + + min + + + + + + + max + + + + + + + + + + false + + + + 10 + + + + Initial Guess + + + + + + a + + + + + + + noise + + + + + + + beta + + + + + + + tc (ns) + + + + + + + 4 + + + 9999999.000000000000000 + + + 5.000000000000000 + + + + + + + 4 + + + 9999999.000000000000000 + + + 1.000000000000000 + + + + + + + 4 + + + 1.000000000000000 + + + 0.500000000000000 + + + + + + + 4 + + + 9999999.000000000000000 + + + 0.100000000000000 + + + + + + + + + + + + + + Bounds + + + + + + min + + + + + + + 4 + + + 9999999.000000000000000 + + + + + + + max + + + + + + + a1 + + + + + + + + + + + + + + noise + + + + + + + 4 + + + 9999999.000000000000000 + + + + + + + 4 + + + 9999999.000000000000000 + + + + + + + 4 + + + 9999999.000000000000000 + + + 300.000000000000000 + + + + + + + 4 + + + 9999999.000000000000000 + + + 10000.000000000000000 + + + + + + + tau2 (ns) + + + + + + + 4 + + + 9999999.000000000000000 + + + + + + + 4 + + + 9999999.000000000000000 + + + 10000.000000000000000 + + + + + + + a2 + + + + + + + 4 + + + 9999999.000000000000000 + + + 10000.000000000000000 + + + + + + + tau1 (ns) + + + + + + + 4 + + + 9999999.000000000000000 + + + + + + + 4 + + + 9999999.000000000000000 + + + 10.000000000000000 + + + + + + + + + + Initial Guess + + + + + + 4 + + + 9999999.000000000000000 + + + 1.000000000000000 + + + + + + + a1 + + + + + + + 4 + + + 9999999.000000000000000 + + + 1.000000000000000 + + + + + + + 4 + + + 9999999.000000000000000 + + + 20.000000000000000 + + + + + + + a2 + + + + + + + 4 + + + 9999999.000000000000000 + + + 0.100000000000000 + + + + + + + tau2 (ns) + + + + + + + noise + + + + + + + tau1 (ns) + + + + + + + 4 + + + 9999999.000000000000000 + + + 1.000000000000000 + + + + + + + + + + + + + + Bounds + + + + + + 4 + + + 9999999.000000000000000 + + + + + + + max + + + + + + + 4 + + + 9999999.000000000000000 + + + 10.000000000000000 + + + + + + + 4 + + + 9999999.000000000000000 + + + + + + + 4 + + + 9999999.000000000000000 + + + + + + + + + + + + + + noise + + + + + + + a + + + + + + + 4 + + + 9999999.000000000000000 + + + 10000.000000000000000 + + + + + + + tau (ns) + + + + + + + 4 + + + 9999999.000000000000000 + + + 10000.000000000000000 + + + + + + + min + + + + + + + + + + Initial Guess + + + + + + 4 + + + 9999999.000000000000000 + + + 1.000000000000000 + + + + + + + a + + + + + + + tau (ns) + + + + + + + 4 + + + 9999999.000000000000000 + + + 1.000000000000000 + + + + + + + noise + + + + + + + 4 + + + 9999999.000000000000000 + + + 0.100000000000000 + + + + + + + + + + + + + + + + + + 10 + + + + Get Lifetime! + + + + + + + true + + + + 10 + + + + Calculate SRV + + + true + + + false + + + + + + Bulk Lifetime (ns) + + + + + + + Thickness (nm) + + + + + + + 100000.000000000000000 + + + 250.000000000000000 + + + + + + + 1000000.000000000000000 + + + 3.000000000000000 + + + + + + + 0 + + + + + + + SRV (cm/s) + + + + + + + Diffusion Coefficient (cm2/s) + + + + + + + SRV1 = SRV2 + + + + + + + Calculate + + + + + + + 100000.000000000000000 + + + 8000.000000000000000 + + + + + + + Surface Lifetime (ns) + + + + + + + 0 + + + + + + + Average Lifetime (ns) + + + + + + + 4 + + + 9999999.000000000000000 + + + + + + + + - + + + + 2 + 0 + + + @@ -29,108 +1027,57 @@ Settings - - + + 12 - Fitting Controls + Export Settings - - - + + + 12 - Fitting function + Save with Fit - - - - - - - - - Configure Fit Settings + + true - - + + 12 + 50 + false - Get Lifetime! - - - - - - - - 12 - + Export figure - - - - - 12 - - - - Export Plot Settings + + + + Export data - - - - - - 12 - 50 - false - - - - Export Publication -Ready Figure! - - - - - - - - 12 - - - - Save with Fit - - - true - - - - - + @@ -141,7 +1088,7 @@ Ready Figure! Plot Controls - + @@ -155,19 +1102,21 @@ Ready Figure! - - - - - 12 - + + + + Y Axis Log + + + + - Y axis Log + Plot color - + @@ -180,9 +1129,37 @@ Ready Figure! + + + + + + + + + Normalize + + + + + + + Clear plot everytime + + + + + + + + 12 + + + + @@ -195,7 +1172,7 @@ Ready Figure! - + @@ -207,27 +1184,63 @@ Ready Figure! - - + + + + + 12 + + + + 512 + + + 1 + + + + + 12 - Channel No + IRF Channel No - + 12 - - 512 + + Data Channel No + + + + + + + + 12 + + + + + + + + + 12 + + + + Load separate IRF file @@ -242,7 +1255,7 @@ Ready Figure! 0 0 1985 - 38 + 31 @@ -250,6 +1263,7 @@ Ready Figure! File + @@ -271,6 +1285,14 @@ Ready Figure! Save + + + false + + + Open IRF File + + diff --git a/PythonGUI_apps/Lifetime_analysis/Lifetime_plot_fit.py b/PythonGUI_apps/Lifetime_analysis/Lifetime_plot_fit.py index 5cf9ace..ec792f2 100644 --- a/PythonGUI_apps/Lifetime_analysis/Lifetime_plot_fit.py +++ b/PythonGUI_apps/Lifetime_analysis/Lifetime_plot_fit.py @@ -7,6 +7,7 @@ # system imports import sys +import os from pathlib import Path # module imports @@ -19,8 +20,10 @@ try: from Lifetime_analysis.Fit_functions import stretch_exp_fit, double_exp_fit, single_exp_fit from Lifetime_analysis.picoharp_phd import read_picoharp_phd + from Lifetime_analysis.Fit_functions_with_irf import fit_exp_stretch_diffev, fit_exp_stretch_fmin_tnc, fit_multi_exp_diffev, fit_multi_exp_fmin_tnc except: from Fit_functions import stretch_exp_fit, double_exp_fit, single_exp_fit + from Fit_functions_with_irf import fit_exp_stretch_diffev, fit_exp_stretch_fmin_tnc, fit_multi_exp_diffev, fit_multi_exp_fmin_tnc from picoharp_phd import read_picoharp_phd """Recylce params for plotting""" @@ -52,31 +55,59 @@ def __init__(self): self.ui.setupUi(self) self.ui.Res_comboBox.addItems(["0.004","0.008","0.016","0.032","0.064","0.128","0.256","0.512"]) self.ui.FittingFunc_comboBox.addItems(["Stretched Exponential","Double Exponential", "Single Exponential"]) + self.ui.FittingMethod_comboBox.addItems(["diff_ev", "fmin_tnc"]) + #set up file menu self.ui.actionOpen.triggered.connect(self.open_file) + self.ui.actionOpen_IRF_File.triggered.connect(self.open_irf_file) self.ui.actionSave.triggered.connect(self.save_file) self.ui.actionExit.triggered.connect(self.close_application) + #set up ui signals self.ui.plot_pushButton.clicked.connect(self.plot) - self.ui.log_pushButton.clicked.connect(self.make_semilog) - self.ui.fit_pushButton.clicked.connect(self.fit_and_plot) + self.ui.fit_pushButton.clicked.connect(self.call_fit_and_plot) self.ui.clear_pushButton.clicked.connect(self.clear_plot) self.ui.export_plot_pushButton.clicked.connect(self.pub_ready_plot_export) - + self.ui.calculate_srv_pushButton.clicked.connect(self.calculate_srv) + + self.ui.log_checkBox.stateChanged.connect(self.make_semilog) + self.ui.fit_with_irf_checkBox.stateChanged.connect(self.switch_fit_settings) + self.ui.FittingFunc_comboBox.currentTextChanged.connect(self.switch_function_tab) + self.ui.FittingMethod_comboBox.currentTextChanged.connect(self.switch_init_params_groupBox) + self.ui.separate_irf_checkBox.stateChanged.connect(self.switch_open_irf) + self.ui.export_data_pushButton.clicked.connect(self.export_data) + + #set up plot color button + self.plot_color_button = pg.ColorButton(color=(255,0,0)) + self.ui.plot_color_button_container.layout().addWidget(self.plot_color_button) + self.plot_color = self.plot_color_button.color() + self.plot_color_button.sigColorChanged.connect(self.plot_color_changed) + self.file = None self.out = None # output file after fitting - + self.data_list = [] self.show() def open_file(self): + """ Open data file """ # try: - filename = QtWidgets.QFileDialog.getOpenFileName(self) + self.filename = QtWidgets.QFileDialog.getOpenFileName(self) try: - self.file = np.loadtxt(filename[0], skiprows=10) + self.file = np.loadtxt(self.filename[0], skiprows=10) # except ValueError: # self.file = np.loadtxt(filename[0], skiprows=10) except UnicodeDecodeError: - self.file = read_picoharp_phd(filename[0]) + self.file = read_picoharp_phd(self.filename[0]) + except: + pass + + def open_irf_file(self): + """ Open file with irf - enabled if 'load separate irf' is checled """ + filename = QtWidgets.QFileDialog.getOpenFileName(self) + try: + self.irf_file = np.loadtxt(filename[0], skiprows=10) + except UnicodeDecodeError: + self.irf_file = read_picoharp_phd(filename[0]) except: pass @@ -86,13 +117,72 @@ def save_file(self): np.savetxt(filename[0], self.out, fmt = '%.5f', header = 'Time, Raw_PL, Sim_PL', delimiter = ' ') except: pass + + def switch_open_irf(self): + """ Handle 'load separate irf' checkbox """ + self.ui.actionOpen_IRF_File.setEnabled(self.ui.separate_irf_checkBox.isChecked()) + + def switch_fit_settings(self): + """ Enable bounds/initial guess groupboxes only when 'Fit with IRF' is checked """ + checked = self.ui.fit_with_irf_checkBox.isChecked() + for func in "str de se".split(" "): + boundsGb = eval("self.ui."+func+"_bounds_groupBox") + #initGb = eval("self.ui."+func+"_init_groupBox") + boundsGb.setEnabled(checked) + #initGb.setEnabled(checked) + if checked == True: + self.switch_init_params_groupBox() + else: + initGb = eval("self.ui."+func+"_init_groupBox") + initGb.setEnabled(checked) + self.ui.FittingMethod_comboBox.setEnabled(checked) + + def switch_function_tab(self): + """ Switch bounds groupbox contents depending on selected fit function """ + fitting_func = self.ui.FittingFunc_comboBox.currentText() + if fitting_func == "Stretched Exponential": + self.ui.fitting_params_stackedWidget.setCurrentIndex(0) + elif fitting_func == "Double Exponential": + self.ui.fitting_params_stackedWidget.setCurrentIndex(1) + elif fitting_func == "Single Exponential": + self.ui.fitting_params_stackedWidget.setCurrentIndex(2) + + def switch_init_params_groupBox(self): + """ Enable initial guess groupbox only when fmin_tnc fit method selected """ + if self.ui.FittingMethod_comboBox.currentText() == "diff_ev": + for func in "str de se".split(" "): + initGb = eval("self.ui."+func+"_init_groupBox") + initGb.setEnabled(False) + #initGb.setEnabled(checked) + elif self.ui.FittingMethod_comboBox.currentText() == "fmin_tnc": + for func in "str de se".split(" "): + initGb = eval("self.ui."+func+"_init_groupBox") + initGb.setEnabled(True) + + def plot_color_changed(self): + """ Grab new plot_color when color button value is changed """ + self.plot_color = self.plot_color_button.color() - def acquire_settings(self): - resolution = float(self.ui.Res_comboBox.currentText()) - channel = int(self.ui.Channel_spinBox.value()) + def acquire_settings(self, mode="data"): + """ + Acquire data or irf from channel specified in spinbox. + + mode -- string specifying whether to use data or irf channel (default "data") + """ + self.resolution = float(self.ui.Res_comboBox.currentText()) + if mode == "data": + channel = int(self.ui.Data_channel_spinBox.value()) + elif mode == "irf": + channel = int(self.ui.irf_channel_spinBox.value()) try: - try: - y = self.file[:,channel] + try: # + if self.ui.separate_irf_checkBox.isChecked() and mode=="irf": #if separate irf, get from irf file + try: + y = self.irf_file[:,channel] + except: + y = self.irf_file.get_curve(channel)[1] + else: #otherwise, get data/irf from data file + y = self.file[:,channel] except: res, y = self.file.get_curve(channel) # TO DO - check if res read in is the same as selected @@ -100,7 +190,7 @@ def acquire_settings(self): y = y[0:time_window] length = np.shape(y)[0] - x = np.arange(0, length, 1) * resolution + x = np.arange(0, length, 1) * self.resolution return x,y except Exception as e: @@ -108,8 +198,12 @@ def acquire_settings(self): def plot(self): try: - x,y = self.acquire_settings() - self.ui.plot.plot(x, y, clear=False, pen='r') + x,y = self.acquire_settings() #get data + if self.ui.normalize_checkBox.isChecked(): + y = y / np.amax(y) + + self.ui.plot.plot(x, y, clear=self.ui.clear_plot_checkBox.isChecked(), pen=pg.mkPen(self.plot_color)) + try: self.ui.Result_textBrowser.setText("Integral Counts :\n" "{:.2E}".format( self.file.get_integral_counts(int(self.ui.Channel_spinBox.value())))) @@ -120,79 +214,258 @@ def plot(self): self.ui.plot.setLabel('left', 'Intensity', units='a.u.') self.ui.plot.setLabel('bottom', 'Time (ns)') - def make_semilog(self): - self.ui.plot.setLogMode(False,True) + """ Switch y-log on/off """ + self.ui.plot.setLogMode(False,self.ui.log_checkBox.isChecked()) def clear_plot(self): self.ui.plot.clear() self.ui.Result_textBrowser.clear() def fit_and_plot(self): + """ Fit and plot without IRF """ try: - x,y = self.acquire_settings() - - y_norm = y/np.max(y) + x,y = self.acquire_settings() #get data + y_norm = y/np.max(y) #normalized y + # find the max intensity in the array and start things from there find_max_int = np.nonzero(y_norm == 1) y = y[np.asscalar(find_max_int[0]):] - - resolution = float(self.ui.Res_comboBox.currentText()) - # x = x[np.asscalar(find_max_int[0]):] - x = np.arange(0, len(y), 1) * resolution - + x = x[np.asscalar(find_max_int[0]):] + t = x - - time_fit = t + time_fit = t TRPL_interp = np.interp(time_fit, t, y) fit_func = self.ui.FittingFunc_comboBox.currentText() - self.ui.plot.plot(t, y, clear=True, pen='r') + self.ui.plot.plot(t, y, clear=self.ui.clear_plot_checkBox.isChecked(), pen=pg.mkPen(self.plot_color)) - if fit_func == "Stretched Exponential": + if fit_func == "Stretched Exponential": #stretch exponential tab tc, beta, a, avg_tau, PL_fit = stretch_exp_fit(TRPL_interp, t) self.out = np.empty((len(t), 3)) self.out[:,0] = t #time self.out[:,1] = TRPL_interp #Raw PL self.out[:,2] = PL_fit # PL fit - self.ui.plot.plot(t, PL_fit, clear=False, pen='k') + self.ui.plot.plot(t, PL_fit, clear=self.ui.clear_plot_checkBox.isChecked(), pen='k') self.ui.Result_textBrowser.setText("Fit Results:\n\nFit Function: Stretched Exponential" + "\nFit Method: " + "diff_ev" + #TODO : change when diff_ev and fmin_tnc implemented for non-irf "\nAverage Lifetime = " + str(avg_tau)+ " ns" "\nCharacteristic Tau = " + str(tc)+" ns" "\nBeta = "+str(beta)) + self.ui.average_lifetime_spinBox.setValue(avg_tau) - elif fit_func == "Double Exponential": + elif fit_func == "Double Exponential": #double exponential tab tau1, a1, tau2, a2, avg_tau, PL_fit = double_exp_fit(TRPL_interp, t) self.out = np.empty((len(t), 3)) self.out[:,0] = t #time self.out[:,1] = TRPL_interp #Raw PL self.out[:,2] = PL_fit # PL fit - self.ui.plot.plot(t, PL_fit, clear=False, pen='k') + self.ui.plot.plot(t, PL_fit, clear=self.ui.clear_plot_checkBox.isChecked(), pen='k') self.ui.Result_textBrowser.setText("Fit Results:\n\nFit Function: Double Exponential" + "\nFit Method: " + "diff_ev" + "\nAverage Lifetime = " + str(avg_tau)+ " ns" "\nTau 1 = " + str(tau1)+" ns" "\nA 1 = " + str(a1)+ "\nTau 2 = " + str(tau2)+" ns" "\nA 2 = " + str(a2)) + #TODO - once tau_avg implemented, set average lifetime spinbox to tau_avg value - elif fit_func == "Single Exponential": + elif fit_func == "Single Exponential": #single exponential tab tau, a, PL_fit = single_exp_fit(TRPL_interp, t) self.out = np.empty((len(t), 3)) self.out[:,0] = t #time self.out[:,1] = TRPL_interp #Raw PL self.out[:,2] = PL_fit # PL fit - self.ui.plot.plot(t, PL_fit, clear=False, pen='k') + self.ui.plot.plot(t, PL_fit, clear=self.ui.clear_plot_checkBox.isChecked(), pen='k') self.ui.Result_textBrowser.setText("Fit Results:\n\nFit Function: Single Exponential" + "\nFit Method: " + "diff_ev" + "\nLifetime = " + str(tau)+ " ns" "\nA = " + str(a)) - + self.ui.average_lifetime_spinBox.setValue(tau) + + #add fit params to data_list + self.data_list.append("Data Channel: " + str(self.ui.Data_channel_spinBox.value()) + "\n" + self.ui.Result_textBrowser.toPlainText()) + self.ui.plot.setLabel('left', 'Intensity', units='a.u.') self.ui.plot.setLabel('bottom', 'Time (ns)') return self.out - except: - pass + except Exception as err: + exc_type, exc_obj, exc_tb = sys.exc_info() + print(exc_type, exc_tb.tb_lineno) + + def fit_and_plot_with_irf(self): + """ Fit and plot with IRF """ + try: + x,y = self.acquire_settings() #get data + _, irf_counts = self.acquire_settings(mode="irf") #get irf counts + + #make sure Irf and data have the same length + if len(y) != len(irf_counts): + y = y[0:min(len(y), len(irf_counts))] + irf_counts = irf_counts[0:min(len(y), len(irf_counts))] + x = x[0:min(len(y), len(irf_counts))] + + y_norm = y/np.max(y) #normalized y + irf_norm = irf_counts/np.amax(irf_counts) #normalized irf + + t = x + time_fit = t + y = y_norm + irf_counts = irf_norm + + TRPL_interp = np.interp(time_fit, t, y) + + fit_func = self.ui.FittingFunc_comboBox.currentText() + self.ui.plot.plot(t, y, clear=self.ui.clear_plot_checkBox.isChecked(), pen=pg.mkPen(self.plot_color)) + if fit_func == "Stretched Exponential": #stretched exponential tab + tc_bounds = (self.ui.str_tc_min_spinBox.value(), self.ui.str_tc_max_spinBox.value()) #(0, 10000) + a_bounds = (self.ui.str_a_min_spinBox.value(), self.ui.str_a_max_spinBox.value())#(0.9, 1.1) + beta_bounds = (self.ui.str_beta_min_spinBox.value(), self.ui.str_beta_max_spinBox.value())#(0,1) + noise_bounds = (self.ui.str_noise_min_spinBox.value(), self.ui.str_noise_max_spinBox.value())#(0, 1e4) + stretch_exp_bounds = [tc_bounds, beta_bounds, a_bounds, noise_bounds] + stretch_exp_init_params = [self.ui.str_tc_init_spinBox.value(), self.ui.str_a_init_spinBox.value(), self.ui.str_beta_init_spinBox.value(), self.ui.str_noise_init_spinBox.value()] + + #tc, beta, a, avg_tau, PL_fit = stretch_exp_fit(TRPL_interp, t) +# resolution = float(self.ui.Res_comboBox.currentText()) + if self.ui.FittingMethod_comboBox.currentText() == "diff_ev": + bestfit_params, t_avg, bestfit_model, data_array, time_array, irf = fit_exp_stretch_diffev(t, self.resolution, TRPL_interp, irf_counts, stretch_exp_bounds) + else: #if fmin_tnc fitting method selected + bestfit_params, t_avg, bestfit_model, data_array, time_array, irf = fit_exp_stretch_fmin_tnc(t, self.resolution, TRPL_interp, irf_counts, stretch_exp_init_params, stretch_exp_bounds) + self.out = np.empty((len(t), 3)) + self.out[:,0] = t #time + self.out[:,1] = TRPL_interp #Raw PL + self.out[:,2] = bestfit_model # PL fit + self.ui.plot.plot(t, bestfit_model, clear=self.ui.clear_plot_checkBox.isChecked(), pen='k') + self.ui.Result_textBrowser.setText("Fit Results:\n\nFit Function: Stretched Exponential with IRF" + "\nFit Method: "+ self.ui.FittingMethod_comboBox.currentText() + + "\ntau_avg = %.5f ns" + "\nbeta = %.5f" + "\ntau_c = %.5f ns" + "\na = %.5f \nnoise = %.5f counts" %(t_avg, bestfit_params[1], bestfit_params[0], bestfit_params[2], bestfit_params[3])) + #self.effective_lifetime = t_avg + self.ui.average_lifetime_spinBox.setValue(t_avg) + + elif fit_func == "Double Exponential": #double exponential tab + a1_bounds = (self.ui.de_a1_min_spinBox.value(), self.ui.de_a1_max_spinBox.value()) + tau1_bounds = (self.ui.de_tau1_min_spinBox.value(), self.ui.de_tau1_max_spinBox.value()) + a2_bounds = (self.ui.de_a2_min_spinBox.value(), self.ui.de_a2_max_spinBox.value()) + tau2_bounds = (self.ui.de_tau2_min_spinBox.value(), self.ui.de_tau2_max_spinBox.value()) + noise_bounds = (self.ui.de_noise_min_spinBox.value(), self.ui.de_noise_max_spinBox.value()) + double_exp_bounds = [a1_bounds, tau1_bounds, a2_bounds, tau2_bounds, noise_bounds] + double_exp_init_params = [self.ui.de_a1_init_spinBox.value(), self.ui.de_tau1_init_spinBox.value(), self.ui.de_a2_init_spinBox.value(), + self.ui.de_tau2_init_spinBox.value(), self.ui.de_noise_init_spinBox.value()] + + if self.ui.FittingMethod_comboBox.currentText() == "diff_ev": + bestfit_params, bestfit_model, data_array, time_array, irf = fit_multi_exp_diffev(t, self.resolution, TRPL_interp, irf_counts, double_exp_bounds, 2) + #bestfit_params, bestfit_model, data_array, time_array, irf = fit_multi_exp_diffev(t, resolution, TRPL_interp, irf_counts, double_exp_init_bounds, 2) + else: + bestfit_params, bestfit_model, data_array, time_array, irf = fit_multi_exp_fmin_tnc(t, self.resolution, TRPL_interp, irf_counts, double_exp_init_params, double_exp_bounds, 2) + self.out = np.empty((len(t), 3)) + self.out[:,0] = t #time + self.out[:,1] = TRPL_interp #Raw PL + self.out[:,2] = bestfit_model # PL fit + self.ui.plot.plot(t, bestfit_model, clear=self.ui.clear_plot_checkBox.isChecked(), pen='k') + self.ui.Result_textBrowser.setText("Fit Results:\n\nFit Function: Double Exponential with IRF" + "\nFit Method: "+ self.ui.FittingMethod_comboBox.currentText() + + "\na1 = %.5f" + "\ntau1 = %.5f ns" + "\na2 = %.5f" + "\ntau2 = %.5f ns" + "\nnoise = %.5f counts" %(bestfit_params[0], bestfit_params[1], bestfit_params[2], bestfit_params[3], bestfit_params[4])) + #TODO - once tau_avg implemented, set average lifetime spinbox to tau_avg value + + elif fit_func == "Single Exponential": #single exponential tab + a_bounds = (self.ui.se_a_min_spinBox.value(), self.ui.se_a_max_spinBox.value()) + tau_bounds = (self.ui.se_tau_min_spinBox.value(), self.ui.se_tau_max_spinBox.value()) + noise_bounds = (self.ui.se_noise_min_spinBox.value(), self.ui.se_noise_max_spinBox.value()) + single_exp_bounds = [a_bounds, tau_bounds, noise_bounds] + single_exp_init_params = [self.ui.se_a_init_spinBox.value(), self.ui.se_tau_init_spinBox.value(), self.ui.se_noise_init_spinBox.value()] + + if self.ui.FittingMethod_comboBox.currentText() == "diff_ev": + bestfit_params, bestfit_model, data_array, time_array, irf = fit_multi_exp_diffev(t, self.resolution, TRPL_interp, irf_counts, single_exp_bounds, 1) + else: + bestfit_params, bestfit_model, data_array, time_array, irf = fit_multi_exp_fmin_tnc(t, self.resolution, TRPL_interp, irf_counts, single_exp_init_params, single_exp_bounds, 1) + self.out = np.empty((len(t), 3)) + self.out[:,0] = t #time + self.out[:,1] = TRPL_interp #Raw PL + self.out[:,2] = bestfit_model # PL fit + self.ui.plot.plot(t, bestfit_model, clear=self.ui.clear_plot_checkBox.isChecked(), pen='k') + self.ui.Result_textBrowser.setText("Fit Results:\n\nFit Function: Single Exponential with IRF" + "\nFit Method: "+ self.ui.FittingMethod_comboBox.currentText() + + "\na = %.5f" + "\ntau = %.5f ns" + "\nnoise = %.5f counts" %(bestfit_params[0], bestfit_params[1], bestfit_params[2])) + self.ui.average_lifetime_spinBox.setValue(bestfit_params[1]) #set spinbox to tau value + + #add fit params to data_list + self.data_list.append("Data Channel: " + str(self.ui.Data_channel_spinBox.value()) + "\n" + self.ui.Result_textBrowser.toPlainText()) + + except Exception as err: + exc_type, exc_obj, exc_tb = sys.exc_info() + print(exc_type, exc_tb.tb_lineno) + + def call_fit_and_plot(self): + if self.ui.fit_with_irf_checkBox.isChecked(): + self.fit_and_plot_with_irf() + else: + self.fit_and_plot() + if self.ui.calculate_srv_groupBox.isChecked(): + self.calculate_srv() #calculate srv on plot + self.data_list.append(self.get_srv_string()) #add srv params to data_list + def calculate_surface_lifetime(self): + effective_lifetime = self.ui.average_lifetime_spinBox.value() + self.bulk_lifetime = self.ui.bulk_lifetime_spinBox.value() # in ns + self.surface_lifetime = (effective_lifetime * self.bulk_lifetime)/(self.bulk_lifetime - effective_lifetime) + self.ui.surface_lifetime_label.setText(str(self.surface_lifetime)) + + def calculate_srv (self): + self.calculate_surface_lifetime() + self.thickness = self.ui.thickness_spinBox.value()*1e-7 # convert to cm + self.diffusion_coeffecient = self.ui.diffusion_coefficient_spinBox.value() # in cm2/s + + if self.ui.srv1_srv2_checkBox.isChecked(): + self.srv = self.thickness / (2*((1e-9*self.surface_lifetime) - ((1/self.diffusion_coeffecient)*((self.thickness/np.pi)**2)) )) + else: + self.srv = self.thickness / ((1e-9*self.surface_lifetime) - ((4/self.diffusion_coeffecient)*((self.thickness/np.pi)**2)) ) + + self.ui.srv_label.setText(str(self.srv)) + + def get_srv_string(self): + """ Get info from SRV Calculation groupbox as string """ + srv_string = "SRV Calculation:"\ + + "\nAverage Lifetime (ns): " + str(self.ui.average_lifetime_spinBox.value()) \ + + "\nBulk Lifetime (ns): " + str(self.ui.bulk_lifetime_spinBox.value()) \ + + "\nThickness (nm): " + str(self.ui.thickness_spinBox.value()) \ + + "\nDiffusion Coefficient (cm2/s): " + str(self.ui.diffusion_coefficient_spinBox.value()) + if self.ui.srv1_srv2_checkBox.isChecked(): + srv_string += "\nSRV1 = SRV2" + else: + srv_string += "\nSRV1 = 0" + srv_string += "\nSurface Lifetime (ns): " + self.ui.surface_lifetime_label.text() \ + + "\nSRV (cm/s): " + self.ui.srv_label.text() + return srv_string + + def export_data(self): + """ Save fit params and srv calculations stored in data_list as .txt """ + folder = os.path.dirname(self.filename[0]) + filename_ext = os.path.basename(self.filename[0]) + filename = os.path.splitext(filename_ext)[0] #get filename without extension + + path = folder + "/" + filename + "_fit_results.txt" + if not os.path.exists(path): + file = open(path, "w+") + else: + file = open(path, "a+") + + for i in range(len(self.data_list)): + file.write(self.data_list[i] + "\n\n") + + self.data_list = [] + file.close() + + def pub_ready_plot_export(self): try: filename = QtWidgets.QFileDialog.getSaveFileName(self,caption="Filename with EXTENSION") @@ -215,7 +488,6 @@ def pub_ready_plot_export(self): except: pass - def close_application(self): choice = QtGui.QMessageBox.question(self, 'EXIT!', "Do you want to exit the app?", @@ -224,7 +496,6 @@ def close_application(self): sys.exit() else: pass - def run(): win = MainWindow() diff --git a/PythonGUI_apps/Lifetime_analysis/__pycache__/Fit_functions.cpython-36.pyc b/PythonGUI_apps/Lifetime_analysis/__pycache__/Fit_functions.cpython-36.pyc deleted file mode 100644 index ece4395..0000000 Binary files a/PythonGUI_apps/Lifetime_analysis/__pycache__/Fit_functions.cpython-36.pyc and /dev/null differ diff --git a/PythonGUI_apps/PLQE_analysis/plqe_analysis.py b/PythonGUI_apps/PLQE_analysis/plqe_analysis.py new file mode 100644 index 0000000..a560746 --- /dev/null +++ b/PythonGUI_apps/PLQE_analysis/plqe_analysis.py @@ -0,0 +1,167 @@ +# system imports +from pathlib import Path +import os.path +import pyqtgraph as pg +from pyqtgraph import exporters +from pyqtgraph.Qt import QtCore, QtGui, QtWidgets +import matplotlib.pyplot as plt + +import numpy as np +import time + +# local modules + +pg.mkQApp() +pg.setConfigOption('background', 'w') + +base_path = Path(__file__).parent +file_path = (base_path / "plqe_analysis_gui.ui").resolve() + +uiFile = file_path + +WindowTemplate, TemplateBaseClass = pg.Qt.loadUiType(uiFile) + +"""params for plotting""" +plt.rc('xtick', labelsize = 20) +plt.rc('xtick.major', pad = 3) +plt.rc('ytick', labelsize = 20) +plt.rc('lines', lw = 1.5, markersize = 7.5) +plt.rc('legend', fontsize = 20) +plt.rc('axes', linewidth = 3.5) + +class MainWindow(TemplateBaseClass): + + def __init__(self): + super(TemplateBaseClass, self).__init__() + + # Create the main window + self.ui = WindowTemplate() + self.ui.setupUi(self) + + #setup uv vis plot + self.plot = self.ui.plotWidget.getPlotItem() + self.plot.setTitle(title="Wavelength vs. Intensity") + self.plot.setLabel('bottom', 'Wavelength', unit='nm') + self.plot.setLabel('left', 'Intensity', unit='a.u.') + self.plot.setLogMode(x=None, y=1) + + #setup line rois for laser and emission + self.laser_region = pg.LinearRegionItem(brush=QtGui.QBrush(QtGui.QColor(255, 0, 0, 50))) + self.laser_region.sigRegionChanged.connect(self.update_laser_spinBoxes) + self.emission_region = pg.LinearRegionItem() + self.emission_region.sigRegionChanged.connect(self.update_emission_spinBoxes) + self.laser_region.setRegion((200, 400)) + self.emission_region.setRegion((700, 800)) + + #setup ui signals + self.ui.load_data_pushButton.clicked.connect(self.open_data_file) + self.ui.plot_pushButton.clicked.connect(self.plot_intensity) + self.ui.clear_pushButton.clicked.connect(self.clear) + self.ui.calculate_plqe_pushButton.clicked.connect(self.calculate_plqe) + self.ui.laser_start_spinBox.valueChanged.connect(self.update_laser_region) + self.ui.laser_stop_spinBox.valueChanged.connect(self.update_laser_region) + self.ui.emission_start_spinBox.valueChanged.connect(self.update_emission_region) + self.ui.emission_stop_spinBox.valueChanged.connect(self.update_emission_region) + + self.show() + + def open_data_file(self): + """ Open data file """ + try: + self.filename = QtWidgets.QFileDialog.getOpenFileName(self) + self.data = np.loadtxt(self.filename[0], delimiter = '\t', skiprows = 1) + self.nm = np.copy(self.data[:,0]) + self.ref_data = np.copy(self.data[:,1]) + self.inpath_data = np.copy(self.data[:,2]) + self.outpath_data = np.copy(self.data[:,3]) + except Exception as err: + print(format(err)) + + def update_laser_spinBoxes(self): + """ Update laser spinboxes based on line rois """ + self.laser_start, self.laser_stop = self.laser_region.getRegion() + self.ui.laser_start_spinBox.setValue(self.laser_start) + self.ui.laser_stop_spinBox.setValue(self.laser_stop) + + + def update_emission_spinBoxes(self): + """ Update emission spinboxes based on line rois """ + self.emission_start, self.emission_stop = self.emission_region.getRegion() + self.ui.emission_start_spinBox.setValue(self.emission_start) + self.ui.emission_stop_spinBox.setValue(self.emission_stop) + + def update_laser_region(self): + """ Update laser line rois based on spinboxes """ + laser_start = self.ui.laser_start_spinBox.value() + laser_stop = self.ui.laser_stop_spinBox.value() + self.laser_region.setRegion((laser_start, laser_stop)) + + def update_emission_region(self): + """ Update emission line rois based on spinboxes """ + emission_start = self.ui.emission_start_spinBox.value() + emission_stop = self.ui.emission_stop_spinBox.value() + self.emission_region.setRegion((emission_start, emission_stop)) + + def plot_intensity(self): + try: + self.plot.plot(self.nm, self.inpath_data) + self.plot.addItem(self.laser_region, ignoreBounds=True) + self.plot.addItem(self.emission_region, ignoreBounds=True) + except Exception as err: + print(format(err)) + + def find_nearest(self,array,value): + idx = (np.abs(array-value)).argmin() + return idx + + def calculate_plqe(self): + + nm_interp_step = 1 + nm_interp_start = np.ceil(self.nm[0] / nm_interp_step) * nm_interp_step + nm_interp_stop = np.floor(self.nm[len(self.nm) - 1] / nm_interp_step) * nm_interp_step + nm_interp = np.arange(nm_interp_start, nm_interp_stop + nm_interp_step, nm_interp_step) + + ref_interp = np.interp(nm_interp, self.nm, self.ref_data) + + + inpath_interp = np.interp(nm_interp, self.nm, self.inpath_data) + outpath_interp = np.interp(nm_interp, self.nm, self.outpath_data) + + + """L_x is area under laser profile for experiment x""" + """P_x_ is area under emission profile for experiment x""" + + + #plt.semilogy(nm, a1_outpath_data[:,1]) + + emission_start_idx = self.find_nearest(nm_interp, self.emission_start) + emission_stop_idx = self.find_nearest(nm_interp, self.emission_stop) + + laser_start_idx = self.find_nearest(nm_interp, self.laser_start) + laser_stop_idx = self.find_nearest(nm_interp, self.laser_stop) + + la = np.trapz(ref_interp[laser_start_idx: laser_stop_idx], x = nm_interp[laser_start_idx:laser_stop_idx]) + lb = np.trapz(outpath_interp[laser_start_idx: laser_stop_idx], x = nm_interp[laser_start_idx:laser_stop_idx]) + lc = np.trapz(inpath_interp[laser_start_idx: laser_stop_idx], x = nm_interp[laser_start_idx:laser_stop_idx]) + + pa = np.trapz(ref_interp[emission_start_idx:emission_stop_idx], x = nm_interp[emission_start_idx:emission_stop_idx]) + pb = np.trapz(outpath_interp[emission_start_idx:emission_stop_idx], x = nm_interp[emission_start_idx:emission_stop_idx]) + pc = np.trapz(inpath_interp[emission_start_idx:emission_stop_idx], x = nm_interp[emission_start_idx:emission_stop_idx]) + + absorb = 1.0 - (lc / lb) + + plqe = 100 * (pc - ((1.0 - absorb) * pb)) / (la * absorb) + #print('PLQE Percent = %.3f' %(plqe)) + #return plqe + self.ui.plqe_label.setText("%.3f" %(plqe)) + + def clear(self): + self.plot.clear() + +"""Run the Main Window""" +def run(): + win = MainWindow() + QtGui.QApplication.instance().exec_() + return win + +#run() diff --git a/PythonGUI_apps/PLQE_analysis/plqe_analysis_gui.ui b/PythonGUI_apps/PLQE_analysis/plqe_analysis_gui.ui new file mode 100644 index 0000000..1caeccf --- /dev/null +++ b/PythonGUI_apps/PLQE_analysis/plqe_analysis_gui.ui @@ -0,0 +1,150 @@ + + + Form + + + + 0 + 0 + 575 + 524 + + + + Form + + + + + + PLQE + + + + + + Laser start + + + + + + + 9999.000000000000000 + + + 200.000000000000000 + + + + + + + Laser stop + + + + + + + 9999.000000000000000 + + + 400.000000000000000 + + + + + + + Emission start + + + + + + + 9999.000000000000000 + + + 700.000000000000000 + + + + + + + Emission stop + + + + + + + 9999.000000000000000 + + + 800.000000000000000 + + + + + + + PLQE Percent + + + + + + + 0 + + + + + + + Calculate PLQE + + + + + + + + + + Clear + + + + + + + Plot + + + + + + + Load data + + + + + + + + + + + PlotWidget + QGraphicsView +
pyqtgraph
+
+
+ + +
diff --git a/PythonGUI_apps/Spectrum_analysis/Spectra_fit_funcs.py b/PythonGUI_apps/Spectrum_analysis/Spectra_fit_funcs.py index 6323804..fba0a86 100644 --- a/PythonGUI_apps/Spectrum_analysis/Spectra_fit_funcs.py +++ b/PythonGUI_apps/Spectrum_analysis/Spectra_fit_funcs.py @@ -56,13 +56,22 @@ def gaussian_model(self): result = gmodel.fit(y, pars, x=x, nan_policy='propagate') return result - def gaussian_model_w_lims(self, center_min=None, center_max=None): + # def gaussian_model_w_lims(self, center_initial_guess=None, sigma_initial_guess=None, center_min=None, center_max=None): + # x,y = self.background_correction() + # gmodel = GaussianModel(prefix = 'g1_') # calling gaussian model + # pars = gmodel.guess(y, x=x) # parameters - center, width, height + # pars['g1_center'].set(center_initial_guess, min=center_min, max=center_max) + # pars['g1_sigma'].set(sigma_initial_guess) + # result = gmodel.fit(y, pars, x=x, nan_policy='propagate') + # return result #770 760 780 sigma 15 + def gaussian_model_w_lims(self, peak_pos, sigma, min_max_range): x,y = self.background_correction() gmodel = GaussianModel(prefix = 'g1_') # calling gaussian model pars = gmodel.guess(y, x=x) # parameters - center, width, height - pars['g1_center'].set(min=center_min, max=center_max) + pars['g1_center'].set(peak_pos, min=min_max_range[0], max=min_max_range[1]) + pars['g1_sigma'].set(sigma) result = gmodel.fit(y, pars, x=x, nan_policy='propagate') - return result + return result #770 760 780 sigma 15 class Single_Lorentzian(Spectra_Fit): """Fit a single Lorentzian to the spectrum @@ -79,11 +88,20 @@ def lorentzian_model(self): result = lmodel.fit(y, pars, x=x, nan_policy='propagate') return result - def lorentzian_model_w_lims(self, center_min = None, center_max = None): + # def lorentzian_model_w_lims(self, center_min = None, center_max = None): + # x,y = self.background_correction() + # lmodel = LorentzianModel(prefix = 'l1_') # calling lorentzian model + # pars = lmodel.guess(y, x=x) # parameters - center, width, height + # pars['l1_center'].set(min = center_min, max = center_max) + # result = lmodel.fit(y, pars, x=x, nan_policy='propagate') + # return result + + def lorentzian_model_w_lims(self, peak_pos, sigma, min_max_range): x,y = self.background_correction() lmodel = LorentzianModel(prefix = 'l1_') # calling lorentzian model pars = lmodel.guess(y, x=x) # parameters - center, width, height - pars['l1_center'].set(min = center_min, max = center_max) + pars['l1_center'].set(peak_pos, min = min_max_range[0], max = min_max_range[1]) + pars['l1_sigma'].set(sigma) result = lmodel.fit(y, pars, x=x, nan_policy='propagate') return result @@ -96,18 +114,35 @@ class Double_Gaussian(Spectra_Fit): """ def gaussian_model(self): + + x,y = self.background_correction() + gmodel_1 = GaussianModel(prefix='g1_') # calling gaussian model + pars = gmodel_1.guess(y, x=x) # parameters - center, width, height + + gmodel_2 = GaussianModel(prefix='g2_') + pars.update(gmodel_2.make_params()) # update parameters - center, width, height + + gmodel = gmodel_1 + gmodel_2 + result = gmodel.fit(y, pars, x=x, nan_policy='propagate') + return result + + def gaussian_model_w_lims(self, peak_pos, sigma, min_max_range): + #center_initial_guesses - list containing initial guesses for peak centers. [center_guess1, center_guess2] + #sigma_initial_guesses - list containing initial guesses for sigma. [sigma1, sigma2] + #min_max_range - list containing lists of min and max for peak center. [ [min1, max1], [min2, max2] ] + x,y = self.background_correction() gmodel_1 = GaussianModel(prefix='g1_') # calling gaussian model pars = gmodel_1.guess(y, x=x) # parameters - center, width, height - pars['g1_center'].set(800, min = 795, max = 820) - pars['g1_sigma'].set(15) + pars['g1_center'].set(peak_pos[0], min = min_max_range[0][0], max = min_max_range[0][1]) + pars['g1_sigma'].set(sigma[0]) pars['g1_amplitude'].set(min=0) gmodel_2 = GaussianModel(prefix='g2_') pars.update(gmodel_2.make_params()) # update parameters - center, width, height - pars['g2_center'].set(767, min = 760, max = 775) + pars['g2_center'].set(peak_pos[1], min = min_max_range[1][0], max = min_max_range[1][1]) + pars['g2_sigma'].set(sigma[1], min = composite_pars['g1_sigma'].value) pars['g2_amplitude'].set(min = 0) - pars['g2_sigma'].set(min = pars['g1_sigma'].value) gmodel = gmodel_1 + gmodel_2 result = gmodel.fit(y, pars, x=x, nan_policy='propagate') @@ -115,13 +150,45 @@ def gaussian_model(self): class Multi_Gaussian(Spectra_Fit): - def __init__(self, data, ref, num_of_gaussians, peak_pos, min_max_range): - Spectra_Fit.__init__(self, data, ref) + # def __init__(self, data, ref, num_of_gaussians, peak_pos, sigma, min_max_range): + # Spectra_Fit.__init__(self, data, ref) + # self.num_of_gaussians = num_of_gaussians + # self.peak_pos = peak_pos + # self.min_max_range = min_max_range + def __init__(self, data, ref, num_of_gaussians, wlref=None): + Spectra_Fit.__init__(self, data, ref, wlref) self.num_of_gaussians = num_of_gaussians + + def gaussian_model(self): + composite_model = None + composite_pars = None + + x,y = self.background_correction() + + for i in range(self.num_of_gaussians): + + model = GaussianModel(prefix='g'+str(i+1)+'_') + + if composite_pars is None: + composite_pars = model.guess(y, x=x) +# composite_pars = model.make_params() + + else: + composite_pars.update(model.make_params()) + + if composite_model is None: + composite_model = model + else: + composite_model += model + + result = composite_model.fit(y, composite_pars, x=x, nan_policy='propagate') + return result + + def gaussian_model_w_lims(self, peak_pos, sigma, min_max_range): self.peak_pos = peak_pos + self.sigma = sigma self.min_max_range = min_max_range - def multi_gaussian(self): composite_model = None composite_pars = None @@ -140,14 +207,14 @@ def multi_gaussian(self): # composite_pars = model.make_params() composite_pars['g'+str(i+1)+'_center'].set(self.peak_pos[i], min = self.min_max_range[0][0], max = self.min_max_range[0][1]) - composite_pars['g'+str(i+1)+'_sigma'].set(15) + composite_pars['g'+str(i+1)+'_sigma'].set(self.sigma[i]) composite_pars['g'+str(i+1)+'_amplitude'].set(min = 0) else: composite_pars.update(model.make_params()) composite_pars['g'+str(i+1)+'_center'].set(self.peak_pos[i], - min = self.min_max_range[1][0], max = self.min_max_range[1][1]) - composite_pars['g'+str(i+1)+'_sigma'].set(min = composite_pars['g1_sigma'].value) + min = self.min_max_range[i][0], max = self.min_max_range[i][1]) + composite_pars['g'+str(i+1)+'_sigma'].set(self.sigma[i], min = composite_pars['g1_sigma'].value) composite_pars['g'+str(i+1)+'_amplitude'].set(min = 0) diff --git a/PythonGUI_apps/Spectrum_analysis/Spectra_plot_fit.py b/PythonGUI_apps/Spectrum_analysis/Spectra_plot_fit.py index 24dc421..005746a 100644 --- a/PythonGUI_apps/Spectrum_analysis/Spectra_plot_fit.py +++ b/PythonGUI_apps/Spectrum_analysis/Spectra_plot_fit.py @@ -7,18 +7,22 @@ # system imports import sys +import h5py from pathlib import Path - +import os.path import pyqtgraph as pg from pyqtgraph.Qt import QtCore, QtGui, QtWidgets#, QColorDialog import numpy as np import matplotlib.pyplot as plt - +import pickle +import time +from lmfit.models import GaussianModel +import customplotting.mscope as cpm # local modules try: - from Spectra_fit_funcs import Spectra_Fit, Single_Gaussian, Single_Lorentzian + from Spectra_fit_funcs import Spectra_Fit, Single_Gaussian, Single_Lorentzian, Double_Gaussian, Multi_Gaussian except: - from Spectrum_analysis.Spectra_fit_funcs import Spectra_Fit, Single_Gaussian, Single_Lorentzian + from Spectrum_analysis.Spectra_fit_funcs import Spectra_Fit, Single_Gaussian, Single_Lorentzian, Double_Gaussian, Multi_Gaussian """Recylce params for plotting""" @@ -31,6 +35,7 @@ pg.mkQApp() pg.setConfigOption('background', 'w') +pg.setConfigOption('imageAxisOrder', 'row-major') base_path = Path(__file__).parent file_path = (base_path / "Spectra_plot_fit_gui.ui").resolve() @@ -40,245 +45,651 @@ WindowTemplate, TemplateBaseClass = pg.Qt.loadUiType(uiFile) class MainWindow(TemplateBaseClass): - - def __init__(self): - super(TemplateBaseClass, self).__init__() - - # Create the main window - self.ui = WindowTemplate() - self.ui.setupUi(self) - - self.ui.fitFunc_comboBox.addItems(["Single Gaussian","Single Lorentzian", "Double Gaussian", "Multiple Gaussians"]) - -# self.ui.actionSave.triggered.connect(self.save_file) + + def __init__(self): + super(TemplateBaseClass, self).__init__() + + # Create the main window + self.ui = WindowTemplate() + self.ui.setupUi(self) + + # self.ui.fitFunc_comboBox.addItems(["Single Gaussian","Single Lorentzian", "Double Gaussian", "Multiple Gaussians"]) # self.ui.actionExit.triggered.connect(self.close_application) - - self.ui.importSpec_pushButton.clicked.connect(self.open_file) - self.ui.importBck_pushButton.clicked.connect(self.open_bck_file) - self.ui.importWLRef_pushButton.clicked.connect(self.open_wlref_file) - self.ui.plot_pushButton.clicked.connect(self.plot) - self.ui.fit_pushButton.clicked.connect(self.fit_and_plot) - self.ui.config_fit_params_pushButton.clicked.connect(self.configure_fit_params) - self.ui.clear_pushButton.clicked.connect(self.clear_plot) - self.ui.export_fig_pushButton.clicked.connect(self.pub_ready_plot_export) - - self.file = None - self.bck_file = None - self.wlref_file = None - self.x = None - self.y = None - self.out = None # output file after fitting - - # Peak parameters if adjust params is selected - self.center_min = None - self.center_max = None - - self.show() - - def open_file(self): - try: - filename = QtWidgets.QFileDialog.getOpenFileName(self) - try: - self.file = np.loadtxt(filename[0], skiprows = 16, delimiter='\t') - except: - self.file = np.genfromtxt(filename[0], skip_header=1, skip_footer=3, delimiter='\t') - except: - pass - - def open_bck_file(self): - try: - filename = QtWidgets.QFileDialog.getOpenFileName(self) - try: - self.bck_file = np.loadtxt(filename[0], skiprows = 16, delimiter='\t') - except: - self.bck_file = np.genfromtxt(filename[0], skip_header=1, skip_footer=3, delimiter='\t') - except: - pass - - def open_wlref_file(self): - try: - filename = QtWidgets.QFileDialog.getOpenFileName(self) - try: - self.wlref_file = np.loadtxt(filename[0], skiprows = 16, delimiter='\t') - except: - self.wlref_file = np.genfromtxt(filename[0], skip_header=1, skip_footer=3, delimiter='\t') - except: - pass - - def save_file(self): - try: - filename = QtWidgets.QFileDialog.getSaveFileName(self) - np.savetxt(filename[0], self.out, fmt = '%.5f', header = 'Time, Raw_PL, Sim_PL', delimiter = ' ') - except: - pass - - def plot(self): - try: - self.x = self.file[:,0] - self.y = self.file[:,1] - - if self.ui.subtract_bck_checkBox.isChecked() == True and self.ui.WLRef_checkBox.isChecked() == False: - bck_y = self.bck_file[:,1] - self.y = self.y - bck_y - - elif self.ui.subtract_bck_checkBox.isChecked() == False and self.ui.WLRef_checkBox.isChecked() == True: - wlref_y = self.wlref_file[:,1] - self.y = (self.y)/wlref_y - - elif self.ui.subtract_bck_checkBox.isChecked() == True and self.ui.WLRef_checkBox.isChecked() == True: - bck_y = self.bck_file[:,1] - wlref_y = self.wlref_file[:,1] - self.y = (self.y-bck_y)/wlref_y - - - if self.ui.norm_checkBox.isChecked(): - self.normalize() - - self.ui.plot.plot(self.x, self.y, clear=self.clear_check(), pen='r') - - except: - pass - self.ui.plot.setLabel('left', 'Intensity', units='a.u.') - self.ui.plot.setLabel('bottom', 'Wavelength (nm)') - - - def normalize(self): - self.y = (self.y) / np.amax(self.y) - - def clear_plot(self): - self.ui.plot.clear() - self.ui.result_textBrowser.clear() - - def clear_check(self): - if self.ui.clear_checkBox.isChecked() == True: - return True - elif self.ui.clear_checkBox.isChecked() == False: - return False - - """Open param window and get peak center range values and assign it to variables to use later""" - def configure_fit_params(self): - self.param_window = ParamWindow() - self.param_window.peak_range.connect(self.peak_range) - - def peak_range(self, peaks): - self.center_min = peaks[0] - self.center_max = peaks[1] - - - def fit_and_plot(self): - fit_func = self.ui.fitFunc_comboBox.currentText() - - try: - - if self.ui.subtract_bck_checkBox.isChecked() == False: - self.ui.result_textBrowser.setText("You need to check the subtract background option!") - - elif self.wlref_file is not None and self.ui.WLRef_checkBox.isChecked() == False: - self.ui.result_textBrowser.setText("You need to check the White Light Correction option!") - - else: - if fit_func == "Single Gaussian" and self.ui.subtract_bck_checkBox.isChecked() == True: - - single_gauss = Single_Gaussian(self.file, self.bck_file, wlref=self.wlref_file) - - if self.ui.adjust_param_checkBox.isChecked(): - self.result = single_gauss.gaussian_model_w_lims( - center_min=self.center_min, center_max=self.center_max) - else: - self.result = single_gauss.gaussian_model() - self.ui.plot.plot(self.x, self.y, clear=self.clear_check(), pen='r') - self.ui.plot.plot(self.x, self.result.best_fit, clear=False, pen='k') - self.ui.result_textBrowser.setText(self.result.fit_report()) - - elif fit_func == "Single Lorentzian" and self.ui.subtract_bck_checkBox.isChecked() == True: - - single_lorentzian = Single_Lorentzian(self.file, self.bck_file, wlref=self.wlref_file) - - if self.ui.adjust_param_checkBox.isChecked(): - self.result = single_lorentzian.lorentzian_model_w_lims( - center_min = self.center_min, center_max = self.center_max) - else: - self.result = single_lorentzian.lorentzian_model() - self.ui.plot.plot(self.x, self.y, clear=self.clear_check(), pen='r') - self.ui.plot.plot(self.x, self.result.best_fit, clear=False, pen='k') - self.ui.result_textBrowser.setText(self.result.fit_report()) - - elif fit_func == "Double Gaussian" and self.ui.subtract_bck_checkBox.isChecked() == True: - self.ui.result_textBrowser.setText("Not Implemented Yet!") - - elif fit_func == "Multiple Gaussians" and self.ui.subtract_bck_checkBox.isChecked() == True: - self.ui.result_textBrowser.setText("Not Implemented Yet!") - - except Exception as e: - self.ui.result_textBrowser.setText(str(e)) - - - def pub_ready_plot_export(self): - filename = QtWidgets.QFileDialog.getSaveFileName(self,caption="Filename with EXTENSION") - try: - plt.figure(figsize=(8,6)) - plt.tick_params(direction='out', length=8, width=3.5) - plt.plot(self.x, self.y) - plt.plot(self.x, self.result.best_fit,'k') - plt.xlabel("Wavelength (nm)", fontsize=20, fontweight='bold') - plt.ylabel("Intensity (a.u.)", fontsize=20, fontweight='bold') - plt.tight_layout() - - plt.savefig(filename[0],bbox_inches='tight', dpi=300) - plt.close() - - except AttributeError: - self.ui.result_textBrowser.setText("Need to fit the data first!") - - - def close_application(self): - choice = QtGui.QMessageBox.question(self, 'EXIT!', - "Do you want to exit the app?", - QtGui.QMessageBox.Yes | QtGui.QMessageBox.No) - if choice == QtGui.QMessageBox.Yes: - sys.exit() - else: - pass - - + + ##setup ui signals + self.ui.importSpec_pushButton.clicked.connect(self.open_file) + self.ui.importBck_pushButton.clicked.connect(self.open_bck_file) + self.ui.importWLRef_pushButton.clicked.connect(self.open_wlref_file) + self.ui.load_spectra_scan_pushButton.clicked.connect(self.open_spectra_scan_file) + self.ui.load_bck_file_pushButton.clicked.connect(self.open_spectra_bck_file) + self.ui.load_fitted_scan_pushButton.clicked.connect(self.open_fit_scan_file) + + self.ui.plot_pushButton.clicked.connect(self.plot) + self.ui.plot_fit_scan_pushButton.clicked.connect(self.plot_fit_scan) + self.ui.plot_raw_scan_pushButton.clicked.connect(self.plot_raw_scan) + self.ui.plot_intensity_sums_pushButton.clicked.connect(self.plot_intensity_sums) + + self.ui.fit_pushButton.clicked.connect(self.fit_and_plot) + self.ui.fit_scan_pushButton.clicked.connect(self.fit_and_plot_scan) + # self.ui.config_fit_params_pushButton.clicked.connect(self.configure_fit_params) + self.ui.clear_pushButton.clicked.connect(self.clear_plot) + self.ui.export_fig_pushButton.clicked.connect(self.pub_ready_plot_export) + + self.ui.import_pkl_pushButton.clicked.connect(self.open_pkl_file) + self.ui.data_txt_pushButton.clicked.connect(self.pkl_data_to_txt) + self.ui.scan_params_txt_pushButton.clicked.connect(self.pkl_params_to_txt) + + self.ui.pkl_to_h5_pushButton.clicked.connect(self.pkl_to_h5) + + self.ui.tabWidget.currentChanged.connect(self.switch_overall_tab) + self.ui.fitFunc_comboBox.currentTextChanged.connect(self.switch_bounds_and_guess_tab) + self.ui.adjust_param_checkBox.stateChanged.connect(self.switch_adjust_param) + + # for i in reversed(range(self.ui.bounds_groupBox.layout().count())): + # self.ui.bounds_groupBox.layout().itemAt(i).widget().deleteLater() + #self.ui.single_bounds_page.layout().addWidget(QtWidgets.QPushButton("test")) + + self.file = None + self.bck_file = None + self.wlref_file = None + self.x = None + self.y = None + self.out = None # output file after fitting + + # Peak parameters if adjust params is selected + self.center_min = None + self.center_max = None + + self.show() + + """ Open Single Spectrum files """ + def open_file(self): + try: + filename = QtWidgets.QFileDialog.getOpenFileName(self) + try: + self.file = np.loadtxt(filename[0], skiprows = 16, delimiter='\t') + except: + self.file = np.genfromtxt(filename[0], skip_header=1, skip_footer=3, delimiter='\t') + except: + pass + + def open_bck_file(self): + try: + filename = QtWidgets.QFileDialog.getOpenFileName(self) + try: + self.bck_file = np.loadtxt(filename[0], skiprows = 16, delimiter='\t') + except: + self.bck_file = np.genfromtxt(filename[0], skip_header=1, skip_footer=3, delimiter='\t') + except Exception as e: + self.ui.result_textBrowser.append(str(e)) + pass + + def open_wlref_file(self): + try: + filename = QtWidgets.QFileDialog.getOpenFileName(self) + try: + self.wlref_file = np.loadtxt(filename[0], skiprows = 16, delimiter='\t') + except: + self.wlref_file = np.genfromtxt(filename[0], skip_header=1, skip_footer=3, delimiter='\t') + except: + pass + + """Open Scan Files""" + def open_spectra_scan_file(self): + try: + filename = QtWidgets.QFileDialog.getOpenFileName(self) + self.spec_scan_file = pickle.load(open(filename[0], 'rb')) + self.ui.result_textBrowser2.append("Done Loading - Spectra Scan File") + except Exception as e: + self.ui.result_textBrowser2.append(str(e)) + pass + + def open_spectra_bck_file(self): + try: + filename = QtWidgets.QFileDialog.getOpenFileName(self) + self.bck_file = np.loadtxt(filename[0])#, skiprows=1, delimiter=None) + self.ui.result_textBrowser2.append("Done Loading - Background File") + except Exception as e: + self.ui.result_textBrowser2.append(str(e)) + pass + + def open_fit_scan_file(self): + try: + filename = QtWidgets.QFileDialog.getOpenFileName(self) + self.fit_scan_file = pickle.load(open(filename[0], 'rb')) + self.ui.result_textBrowser2.append("Done Loading - Scan Fit File") + except Exception as e: + self.ui.result_textBrowser2.append(str(e)) + pass + + def open_pkl_file(self): + """ Open pkl file to convert to txt """ + try: + self.pkl_to_convert = QtWidgets.QFileDialog.getOpenFileName(self) + except: + pass + + def switch_overall_tab(self): + """ Enable/disable fit settings on right depending on current tab """ + if self.ui.tabWidget.currentIndex() == 0: + self.ui.fitting_settings_groupBox.setEnabled(True) + self.ui.fit_pushButton.setEnabled(True) + self.ui.fit_scan_pushButton.setEnabled(True) + self.ui.scan_fit_settings_groupBox.setEnabled(False) + elif self.ui.tabWidget.currentIndex() == 1: + self.ui.fitting_settings_groupBox.setEnabled(False) + self.ui.fit_pushButton.setEnabled(False) + self.ui.fit_scan_pushButton.setEnabled(True) + self.ui.scan_fit_settings_groupBox.setEnabled(True) + elif self.ui.tabWidget.currentIndex() == 2: + self.ui.fitting_settings_groupBox.setEnabled(False) + self.ui.fit_pushButton.setEnabled(False) + self.ui.fit_scan_pushButton.setEnabled(False) + self.ui.scan_fit_settings_groupBox.setEnabled(False) + + """ Single spectrum functions """ + def switch_bounds_and_guess_tab(self): + """ Show the appropriate bounds and initial guess params based on fit function """ + fit_func = self.ui.fitFunc_comboBox.currentText() + if fit_func == "Single Gaussian" or fit_func == "Single Lorentzian": + self.ui.n_label.setEnabled(False) + self.ui.n_spinBox.setEnabled(False) + self.ui.bounds_stackedWidget.setCurrentIndex(0) + self.ui.guess_stackedWidget.setCurrentIndex(0) + self.ui.plot_components_checkBox.setEnabled(False) + self.ui.n_spinBox.setValue(1) + elif fit_func == "Double Gaussian": + self.ui.n_label.setEnabled(False) + self.ui.n_spinBox.setEnabled(False) + self.ui.bounds_stackedWidget.setCurrentIndex(1) + self.ui.guess_stackedWidget.setCurrentIndex(1) + self.ui.plot_components_checkBox.setEnabled(True) + self.ui.n_spinBox.setValue(2) + elif fit_func == "Triple Gaussian": + self.ui.n_label.setEnabled(False) + self.ui.n_spinBox.setEnabled(False) + self.ui.bounds_stackedWidget.setCurrentIndex(2) + self.ui.guess_stackedWidget.setCurrentIndex(2) + self.ui.plot_components_checkBox.setEnabled(True) + self.ui.n_spinBox.setValue(3) + + def switch_adjust_param(self): + """ Enable bounds and initial guess only when adjust parameters is checked """ + checked = self.ui.adjust_param_checkBox.isChecked() + self.ui.bounds_groupBox.setEnabled(checked) + self.ui.guess_groupBox.setEnabled(checked) + + def plot(self): + try: + self.x = self.file[:,0] + self.y = self.file[:,1] + + if self.ui.subtract_bck_checkBox.isChecked() == True and self.ui.WLRef_checkBox.isChecked() == False: + bck_y = self.bck_file[:,1] + self.y = self.y - bck_y + + elif self.ui.subtract_bck_checkBox.isChecked() == False and self.ui.WLRef_checkBox.isChecked() == True: + wlref_y = self.wlref_file[:,1] + self.y = (self.y)/wlref_y + + elif self.ui.subtract_bck_checkBox.isChecked() == True and self.ui.WLRef_checkBox.isChecked() == True: + bck_y = self.bck_file[:,1] + wlref_y = self.wlref_file[:,1] + self.y = (self.y-bck_y)/wlref_y + + + if self.ui.norm_checkBox.isChecked(): + self.normalize() + + self.ui.plot.plot(self.x, self.y, clear=self.clear_check(), pen='r') + + except Exception as err: + pass + self.ui.plot.setLabel('left', 'Intensity', units='a.u.') + self.ui.plot.setLabel('bottom', 'Wavelength (nm)') + + def normalize(self): + self.y = (self.y) / np.amax(self.y) + + def clear_plot(self): + self.ui.plot.clear() + self.ui.result_textBrowser.clear() + + def clear_check(self): + if self.ui.clear_checkBox.isChecked() == True: + return True + elif self.ui.clear_checkBox.isChecked() == False: + return False + + """Open param window and get peak center range values and assign it to variables to use later""" + # def configure_fit_params(self): + # self.param_window = ParamWindow() + # self.param_window.peak_range.connect(self.peak_range) + + def peak_range(self, peaks): + self.center_min = peaks[0] + self.center_max = peaks[1] + + + def fit_and_plot(self): + fit_func = self.ui.fitFunc_comboBox.currentText() + + try: + + if self.ui.subtract_bck_checkBox.isChecked() == False: + self.ui.result_textBrowser.setText("You need to check the subtract background option!") + + elif self.wlref_file is not None and self.ui.WLRef_checkBox.isChecked() == False: + self.ui.result_textBrowser.setText("You need to check the White Light Correction option!") + + else: + if fit_func == "Single Gaussian" and self.ui.subtract_bck_checkBox.isChecked() == True: + single_gauss = Single_Gaussian(self.file, self.bck_file, wlref=self.wlref_file) + if self.ui.adjust_param_checkBox.isChecked(): + center1_min = self.ui.single_peakcenter1_min_spinBox.value() + center1_max = self.ui.single_peakcenter1_max_spinBox.value() + center1_guess = self.ui.single_peakcenter1_guess_spinBox.value() + sigma1_guess = self.ui.single_sigma1_guess_spinBox.value() + self.result = single_gauss.gaussian_model_w_lims(center1_guess, sigma1_guess, + [center1_min, center1_max]) + else: + self.result = single_gauss.gaussian_model() + self.ui.plot.plot(self.x, self.y, clear=self.clear_check(), pen='r') + self.ui.plot.plot(self.x, self.result.best_fit, clear=False, pen='k') + self.ui.result_textBrowser.setText(self.result.fit_report()) + + elif fit_func == "Single Lorentzian" and self.ui.subtract_bck_checkBox.isChecked() == True: + single_lorentzian = Single_Lorentzian(self.file, self.bck_file, wlref=self.wlref_file) + + if self.ui.adjust_param_checkBox.isChecked(): + center1_min = self.ui.single_peakcenter1_min_spinBox.value() + center1_max = self.ui.single_peakcenter1_max_spinBox.value() + center1_guess = self.ui.single_peakcenter1_guess_spinBox.value() + sigma1_guess = self.ui.single_sigma1_guess_spinBox.value() + self.result = single_lorentzian.lorentzian_model_w_lims(center1_guess, sigma1_guess, + [center1_min, center1_max]) + else: + self.result = single_lorentzian.lorentzian_model() + self.ui.plot.plot(self.x, self.y, clear=self.clear_check(), pen='r') + self.ui.plot.plot(self.x, self.result.best_fit, clear=False, pen='k') + self.ui.result_textBrowser.setText(self.result.fit_report()) + + elif fit_func == "Double Gaussian" and self.ui.subtract_bck_checkBox.isChecked() == True: + double_gauss = Double_Gaussian(self.file, self.bck_file, wlref=self.wlref_file) + if self.ui.adjust_param_checkBox.isChecked(): + center1_min = self.ui.double_peakcenter1_min_spinBox.value() + center1_max = self.ui.double_peakcenter1_max_spinBox.value() + center2_min = self.ui.double_peakcenter2_min_spinBox.value() + center2_max = self.ui.double_peakcenter2_max_spinBox.value() + center1_guess = self.ui.double_peakcenter1_guess_spinBox.value() + sigma1_guess = self.ui.double_sigma1_guess_spinBox.value() + center2_guess = self.ui.double_peakcenter2_guess_spinBox.value() + sigma2_guess = self.ui.double_sigma2_guess_spinBox.value() + + peak_pos = [center1_guess, center2_guess] + sigma = [sigma1_guess, sigma2_guess] + min_max_range = [ [center1_min, center1_max], [center2_min, center2_max] ] + self.result = double_gauss.gaussian_model_w_lims(peak_pos, sigma, min_max_range) + + else: + self.result = double_gauss.gaussian_model() + + self.ui.plot.plot(self.x, self.y, clear=self.clear_check(), pen='r') + self.ui.plot.plot(self.x, self.result.best_fit, clear=False, pen='k') + if self.ui.plot_components_checkBox.isChecked(): + comps = self.result.eval_components(x=self.x) + self.ui.plot.plot(self.x, comps['g1_'], pen='b', clear=False) + self.ui.plot.plot(self.x, comps['g2_'], pen='g', clear=False) + + self.ui.result_textBrowser.setText(self.result.fit_report()) + + elif fit_func == "Triple Gaussians" and self.ui.subtract_bck_checkBox.isChecked() == True: + #currently only works for triple gaussian (n=3) + multiple_gauss = Multi_Gaussian(self.file, self.bck_file, 3, wlref=self.wlref_file) + if self.ui.adjust_param_checkBox.isChecked(): + center1_min = self.ui.multi_peakcenter1_min_spinBox.value() + center1_max = self.ui.multi_peakcenter1_max_spinBox.value() + center2_min = self.ui.multi_peakcenter2_min_spinBox.value() + center2_max = self.ui.multi_peakcenter2_max_spinBox.value() + center3_min = self.ui.multi_peakcenter3_min_spinBox.value() + center3_max = self.ui.multi_peakcenter3_max_spinBox.value() + center1_guess = self.ui.multi_peakcenter1_guess_spinBox.value() + sigma1_guess = self.ui.multi_sigma1_guess_spinBox.value() + center2_guess = self.ui.multi_peakcenter2_guess_spinBox.value() + sigma2_guess = self.ui.multi_sigma2_guess_spinBox.value() + center3_guess = self.ui.multi_peakcenter3_guess_spinBox.value() + sigma3_guess = self.ui.multi_sigma3_guess_spinBox.value() + num_gaussians = 3 + peak_pos = [center1_guess, center2_guess, center3_guess] + sigma = [sigma1_guess, sigma2_guess, sigma3_guess] + min_max_range = [ [center1_min, center1_max], [center2_min, center2_max], [center3_min, center3_max] ] + + self.result = multiple_gauss.gaussian_model_w_lims(peak_pos, sigma, min_max_range) + else: + self.result = multiple_gauss.gaussian_model() + + self.ui.plot.plot(self.x, self.y, clear=self.clear_check(), pen='r') + self.ui.plot.plot(self.x, self.result.best_fit, clear=False, pen='k') + if self.ui.plot_components_checkBox.isChecked(): + comps = self.result.eval_components(x=self.x) + self.ui.plot.plot(self.x, comps['g1_'], pen='b', clear=False) + self.ui.plot.plot(self.x, comps['g2_'], pen='g', clear=False) + self.ui.plot.plot(self.x, comps['g3_'], pen='c', clear=False) + self.ui.result_textBrowser.setText(self.result.fit_report()) + + + except Exception as e: + self.ui.result_textBrowser.setText(str(e)) + + def pub_ready_plot_export(self): + filename = QtWidgets.QFileDialog.getSaveFileName(self,caption="Filename with EXTENSION") + try: + try: + data = self.spec_scan_file + param_selection = str(self.ui.comboBox.currentText()) + if param_selection == 'pk_pos': label = 'PL Peak Position (n.m.)' + elif param_selection == 'fwhm': label = 'PL FWHM (n.m.)' + cpm.plot_confocal(self.img, figsize=(10,10), stepsize = data['Scan Parameters']['X step size (um)'], cmap="seismic", cbar_label=label) + plt.savefig(filename[0],bbox_inches='tight', dpi=300) + plt.close() + except: + plt.figure(figsize=(8,6)) + plt.tick_params(direction='out', length=8, width=3.5) + plt.plot(self.x, self.y) + plt.plot(self.x, self.result.best_fit,'k') + plt.xlabel("Wavelength (nm)", fontsize=20, fontweight='bold') + plt.ylabel("Intensity (a.u.)", fontsize=20, fontweight='bold') + plt.tight_layout() + + plt.savefig(filename[0],bbox_inches='tight', dpi=300) + plt.close() + + except AttributeError: + self.ui.result_textBrowser.setText("Need to fit the data first!") + + + """ Scan spectra functions """ + def plot_fit_scan(self): + try: + if self.ui.use_raw_scan_settings.isChecked(): + data = self.spec_scan_file + num_x = int((data['Scan Parameters']['X scan size (um)'])/(data['Scan Parameters']['X step size (um)'])) + num_y = int((data['Scan Parameters']['Y scan size (um)'])/(data['Scan Parameters']['Y step size (um)'])) + else: + num_x = self.ui.num_x_spinBox.value() + num_y = self.ui.num_y_spinBox.value() + + numb_of_points = num_x * num_y #75*75 + + fwhm = np.zeros(shape=(numb_of_points,1)) + pk_pos = np.zeros(shape=(numb_of_points,1)) +# pk_pos_plus = np.zeros(shape=(numb_of_points,1)) +# pk_pos_minus = np.zeros(shape=(numb_of_points,1)) + sigma = np.zeros(shape=(numb_of_points,1)) + height = np.zeros(shape=(numb_of_points,1)) + + for i in range(numb_of_points): + fwhm[i, 0] = self.fit_scan_file['result_'+str(i)].values['g1_fwhm'] + pk_pos[i, 0] = self.fit_scan_file['result_'+str(i)].values['g1_center'] + sigma[i, 0] = self.fit_scan_file['result_'+str(i)].values['g1_sigma'] + height[i, 0] = self.fit_scan_file['result_'+str(i)].values['g1_height'] + + newshape = (num_x, num_y) + + param_selection = str(self.ui.comboBox.currentText()) + self.img = np.reshape(eval(param_selection), newshape) + + if self.ui.use_raw_scan_settings.isChecked(): + self.ui.fit_scan_viewbox.setImage(self.img, scale= + (data['Scan Parameters']['X step size (um)'], + data['Scan Parameters']['Y step size (um)'])) + scale = pg.ScaleBar(size=2,suffix='um') + scale.setParentItem(self.ui.fit_scan_viewbox.view) + scale.anchor((1, 1), (1, 1), offset=(-30, -30)) + else: + self.ui.fit_scan_viewbox.setImage(self.img) + + self.ui.fit_scan_viewbox.view.invertY(False) + + except Exception as e: + self.ui.result_textBrowser2.append(str(e)) + pass + + def plot_raw_scan(self): + try: + data = self.spec_scan_file + numb_pixels_X = int((data['Scan Parameters']['X scan size (um)'])/(data['Scan Parameters']['X step size (um)'])) + numb_pixels_Y = int((data['Scan Parameters']['Y scan size (um)'])/(data['Scan Parameters']['Y step size (um)'])) + # TODO test line scan plots + + intensities = data['Intensities'].T #this is only there because of how we are saving the data in the app + + intensities = np.reshape(intensities, newshape=(2048,numb_pixels_X,numb_pixels_Y)) + + wavelengths = data['Wavelengths'] + + self.ui.raw_scan_viewbox.view.invertY(False) + self.ui.raw_scan_viewbox.setImage(intensities, scale= + (data['Scan Parameters']['X step size (um)'], + data['Scan Parameters']['Y step size (um)']), xvals=wavelengths) + + + #roi_plot = self.ui.raw_scan_viewBox.getRoiPlot() + #roi_plot.plot(data['Wavelengths'], intensities) + scale = pg.ScaleBar(size=2,suffix='um') + scale.setParentItem(self.ui.raw_scan_viewbox.view) + scale.anchor((1, 1), (1, 1), offset=(-30, -30)) + + except Exception as e: + self.ui.result_textBrowser2.append(str(e)) + + def plot_intensity_sums(self): + try: + data = self.spec_scan_file + numb_pixels_X = int((data['Scan Parameters']['X scan size (um)'])/(data['Scan Parameters']['X step size (um)'])) + numb_pixels_Y = int((data['Scan Parameters']['Y scan size (um)'])/(data['Scan Parameters']['Y step size (um)'])) + # TODO test line scan plots + + intensities = data['Intensities'] + + #intensities = np.reshape(intensities, newshape=(2048, numb_pixels_X*numb_pixels_Y)) + + sums = np.sum(intensities, axis=-1) + sums = np.reshape(sums, newshape=(numb_pixels_X, numb_pixels_Y)) + + self.ui.intensity_sums_viewBox.setImage(sums, scale= + (data['Scan Parameters']['X step size (um)'], + data['Scan Parameters']['Y step size (um)'])) + self.ui.intensity_sums_viewBox.view.invertY(False) + + scale = pg.ScaleBar(size=2,suffix='um') + scale.setParentItem(self.ui.intensity_sums_viewBox.view) + scale.anchor((1, 1), (1, 1), offset=(-30, -30)) + + except Exception as e: + self.ui.result_textBrowser2.append(str(e)) + + + def fit_and_plot_scan(self): +# self.ui.result_textBrowser.append("Starting Scan Fitting") + print("Starting Scan Fitting") + + try: + """Define starting and stopping wavelength values here""" + start_nm = int(self.ui.start_nm_spinBox.value()) + stop_nm = int(self.ui.stop_nm_spinBox.value()) + + ref = self.bck_file + index = (ref[:,0]>start_nm) & (ref[:,0]start_nm) & (x 0 0 - 1816 - 1562 + 1728 + 1052 + + + 0 + 0 + + MainWindow - + - + + + + + + 12 + + + + Fit Settings + + + + + + false + + + + 10 + + + + n: + + + + + + + false + + + + 10 + + + + 1 + + + 1 + + + + + + + false + + + + 10 + + + + Plot components (>1 Gaussian) + + + true + + + + + + + false + + + + 10 + + + + Bounds + + + + + + 0 + + + + + + + + + + + + + + max + + + + + + + min + + + + + + + Peak Center 1 (nm) + + + + + + + 9999.000000000000000 + + + 760.000000000000000 + + + + + + + 9999.000000000000000 + + + 780.000000000000000 + + + + + + + + + + + + + + + + + + min + + + + + + + 9999.000000000000000 + + + 760.000000000000000 + + + + + + + 9999.000000000000000 + + + 820.000000000000000 + + + + + + + 9999.000000000000000 + + + 795.000000000000000 + + + + + + + Peak Center 2 (nm) + + + + + + + 9999.000000000000000 + + + 775.000000000000000 + + + + + + + max + + + + + + + Peak Center 1 (nm) + + + + + + + + + + + 9999.000000000000000 + + + 760.000000000000000 + + + + + + + 9999.000000000000000 + + + 820.000000000000000 + + + + + + + 9999.000000000000000 + + + 775.000000000000000 + + + + + + + 9999.000000000000000 + + + 755.000000000000000 + + + + + + + 9999.000000000000000 + + + 740.000000000000000 + + + + + + + Peak Center 1 (nm) + + + + + + + max + + + + + + + Peak Center 2 (nm) + + + + + + + Peak Center 3 (nm) + + + + + + + 9999.000000000000000 + + + 795.000000000000000 + + + + + + + min + + + + + + + + + + + + + + + + + + + + + + 10 + + + + Adjust Parameters + + + + + + + + 10 + + + + + Single Gaussian + + + + + Single Lorentzian + + + + + Double Gaussian + + + + + Triple Gaussian + + + + + + + + false + + + + 10 + + + + Initial Guess + + + + + + 0 + + + + + + + 9999.000000000000000 + + + 770.000000000000000 + + + + + + + Peak Center 1 (nm) + + + + + + + Sigma 1 (nm) + + + + + + + 15.000000000000000 + + + + + + + + + + + 9999.000000000000000 + + + 767.000000000000000 + + + + + + + 15.000000000000000 + + + + + + + Peak Center 1 (nm) + + + + + + + 9999.000000000000000 + + + 800.000000000000000 + + + + + + + Sigma 1 (nm) + + + + + + + Peak Center 2 (nm) + + + + + + + Sigma 2 (nm) + + + + + + + 15.000000000000000 + + + + + + + + + + + Peak Center 2 (nm) + + + + + + + Sigma 2 (nm) + + + + + + + Peak Center 3 (nm) + + + + + + + Sigma 3 (nm) + + + + + + + Peak Center 1 (nm) + + + + + + + Sigma 1 (nm) + + + + + + + 9999.000000000000000 + + + 800.000000000000000 + + + + + + + 15.000000000000000 + + + + + + + 9999.000000000000000 + + + 767.000000000000000 + + + + + + + 15.000000000000000 + + + + + + + 9999.000000000000000 + + + 750.000000000000000 + + + + + + + 15.000000000000000 + + + + + + + + + + + + + + + + + + 10 + + + + Fit Single Spectrum + + + + + + + + 10 + + + + Fit Entire Scan + + + + + + + false + + + + 12 + + + + Scan Fit Settings + + + + + + + 10 + + + + Stop at (nm): + + + + + + + + 10 + + + + 300 + + + 1100 + + + 600 + + + + + + + + 10 + + + + Start at (nm): + + + + + + + + 10 + + + + Qt::ImhDigitsOnly + + + 300 + + + 1100 + + + 900 + + + + + + + - + 15 - - Settings + + 0 - - - - - - 12 - - - - Spectrum + + false + + + + Single Spectrum + + + + + + + 0 + 0 + + + + + 350 + 16777215 + + + + + 12 + + + + Load Settings + + + + + + + 10 + + + + For Single Spectrum + + + + + + + + 10 + + + + Spectrum File - - - - - - - - 12 - - - - Background + + + + + + + + 10 + + + + Background File - - - - - - - - 12 - - - - White Light + + + + + + + + 10 + + + + White Light Ref File - - - - - - - - 12 - - - - Subtract Background - - - - - - - - 12 - - - - Correct for White Light - - - - - - - - 12 - - - - Normalize - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 12 - 50 - false - - - - Plot - - - - - - - - 12 - - - - Clear Plots Everytime - - - true - - - - - - - - 12 - false - - - - Clear Plot - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 12 - - - - Fitting Settings - - + + + + + + + + 10 + + + + Subtract Background + + + + + + + + 10 + + + + Correct for White Light + + + + + + + + 10 + + + + Normalize + + + + + + + + 10 + 50 + false + + + + Plot + + + + + + + + 10 + + + + Clear Plots Everytime + + + true + + + + + + + + 10 + false + + + + Clear Plot + + + + + + + + 10 + + + + Export Publication +Ready Figure + + + + + + + + 10 + + + + + + + + + + + + + + + Scan Spectra Data + + + + + + + + + + + 15 + + + + For Raw Scan Data + + + + + + + + 12 + + + + Plot + + + + + - + + + + 0 + 0 + + + + + 0 + 300 + + + + + + + + + + - 12 + 15 + + + pk_pos + + + + + fwhm + + + + + sigma + + + + + height + + - - + + + + + 0 + 0 + + + + + 0 + 300 + + + + + + + + 2000 + + + 100 + + + + + - Configure Fit Params + # X points - - + + + + # Y points + + + + + 12 - Adjust Parameters + Plot - - + + + + 2000 + + + 100 + + + + + + + Use Raw Scan Settings + + + true + + + + + - 12 + 15 - Fit + After Fitting Scan Data - -
- - - - Qt::Horizontal - - - - 40 - 20 - + + + + + + + + + + 15 + + + + Intensity Sums + + + + + + + + 12 + + + + Plot + + + + + + + + + + 0 + 0 + + + + + 0 + 300 + + + + + + + + + + Qt::Horizontal + + + + + + + + 210 + 16777215 + + + + + 15 + + + + Load Settings + + + + + + + 12 + + + + For Scan Data + + + + + + + + 12 + + + + Spectra Scan +File + + + + + + + + 12 + + + + Background +File + + + + + + + + 12 + + + + Load Only + Fit File + + + + + + + + + +
+
+ + + .pkl conversion + + + + + 10 + 70 + 351 + 251 + + + + txt + + + + + + + 12 + + + + Data to .txt + + + + + + + + 12 + + + + Scan params to .txt + + + + + + + + + 390 + 70 + 321 + 251 + + + + h5 + + + + + 50 + 80 + 221 + 41 + - -
- - 12 - Export Publication -Ready Figure + .pkl to .h5 - -
-
-
- - - - - 10 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 15 - - - - Fit Results - + + + + + 20 + 20 + 323 + 40 + + + + + 12 + + + + Import .pkl file + + +
@@ -296,8 +1321,8 @@ Ready Figure 0 0 - 1816 - 21 + 1728 + 31 @@ -309,6 +1334,11 @@ Ready Figure QGraphicsView
pyqtgraph
+ + ImageView + QGraphicsView +
pyqtgraph
+
diff --git a/PythonGUI_apps/Spectrum_analysis/Spectra_plot_fit_gui_old.ui b/PythonGUI_apps/Spectrum_analysis/Spectra_plot_fit_gui_old.ui new file mode 100644 index 0000000..2ec3fe0 --- /dev/null +++ b/PythonGUI_apps/Spectrum_analysis/Spectra_plot_fit_gui_old.ui @@ -0,0 +1,807 @@ + + + MainWindow + + + + 0 + 0 + 1430 + 885 + + + + MainWindow + + + + + + 1135 + 30 + 281 + 311 + + + + + 15 + + + + Fitting Settings + + + + + + + 12 + + + + + + + + + 12 + + + + Configure Fit Params + + + + + + + + 12 + + + + Start at (nm): + + + + + + + + 12 + + + + 300 + + + 1100 + + + 600 + + + + + + + + 12 + + + + Stop at (nm): + + + + + + + + 12 + + + + 300 + + + 1100 + + + 900 + + + + + + + + 12 + + + + Adjust Parameters + + + + + + + + 12 + + + + Fit Single Spectrum + + + + + + + + 12 + + + + Fit Entire Scan + + + + + + + + 12 + + + + Export Publication +Ready Figure + + + + + + + + + 1140 + 350 + 281 + 481 + + + + + 10 + + + + + + + 10 + 10 + 1153 + 605 + + + + + 15 + + + + 1 + + + false + + + + Single Spectrum + + + + + 20 + 20 + 204 + 431 + + + + + 15 + + + + Load Settings + + + + + + + 12 + + + + For Single Spectrum + + + + + + + + 12 + + + + Spectrum +File + + + + + + + + 12 + + + + Background +File + + + + + + + + 12 + + + + White Light +Ref File + + + + + + + + 12 + + + + Subtract Background + + + + + + + + 12 + + + + Correct for White Light + + + + + + + + 12 + + + + Normalize + + + + + + + + 12 + 50 + false + + + + Plot + + + + + + + + 12 + + + + Clear Plots Everytime + + + true + + + + + + + + 12 + false + + + + Clear Plot + + + + + + + + + 250 + 50 + 851 + 401 + + + + + + + 250 + 20 + 178 + 24 + + + + + 15 + + + + For Single Spectrum + + + + + + Scan Spectra Data + + + + + 200 + 60 + 521 + 331 + + + + + + + 220 + 20 + 181 + 24 + + + + + 15 + + + + For Raw Scan Data + + + + + + 400 + 20 + 101 + 31 + + + + + 12 + + + + Plot + + + + + + 200 + 460 + 521 + 331 + + + + + + + 560 + 410 + 81 + 31 + + + + + 12 + + + + Plot + + + + + + 210 + 410 + 211 + 31 + + + + + 15 + + + + After Fitting Scan Data + + + + + + 420 + 410 + 131 + 31 + + + + + 15 + + + + + pk_pos + + + + + fwhm + + + + + sigma + + + + + height + + + + + + + 0 + 20 + 181 + 251 + + + + + 15 + + + + Load Settings + + + + + + + 12 + + + + Background +File + + + + + + + + 12 + + + + Spectra Scan +File + + + + + + + + 12 + + + + For Scan Data + + + + + + + + 12 + + + + Load Only + Fit File + + + + + + + + + 800 + 450 + 141 + 41 + + + + # X points + + + + + + 800 + 500 + 141 + 41 + + + + # Y points + + + + + + 950 + 460 + 81 + 31 + + + + 2000 + + + 100 + + + + + + 950 + 510 + 81 + 31 + + + + 2000 + + + 100 + + + + + + 800 + 570 + 231 + 41 + + + + Use Raw Scan Settings + + + true + + + + + + 760 + 20 + 211 + 31 + + + + + 15 + + + + Intensity Sums + + + + + + 970 + 20 + 101 + 31 + + + + + 12 + + + + Plot + + + + + + 580 + 60 + 521 + 331 + + + + + + + pkl to txt + + + + + 10 + 20 + 281 + 201 + + + + Convert + + + + + 10 + 40 + 241 + 31 + + + + + 12 + + + + Import .pkl file + + + + + + 10 + 90 + 241 + 31 + + + + + 12 + + + + Data to .txt + + + + + + 10 + 140 + 241 + 34 + + + + + 12 + + + + Scan params to .txt + + + + + + + + + + 0 + 0 + 1430 + 31 + + + + + + + + PlotWidget + QGraphicsView +
pyqtgraph
+
+ + ImageView + QGraphicsView +
pyqtgraph
+
+
+ + +
diff --git a/PythonGUI_apps/UV_Vis_analysis/uv_vis_analysis.py b/PythonGUI_apps/UV_Vis_analysis/uv_vis_analysis.py new file mode 100644 index 0000000..44e2ffb --- /dev/null +++ b/PythonGUI_apps/UV_Vis_analysis/uv_vis_analysis.py @@ -0,0 +1,172 @@ +# system imports +from pathlib import Path +import os.path +import pyqtgraph as pg +from pyqtgraph import exporters +from pyqtgraph.Qt import QtCore, QtGui, QtWidgets +import matplotlib.pyplot as plt + +import numpy as np +import time + +# local modules + +pg.mkQApp() +pg.setConfigOption('background', 'w') + +base_path = Path(__file__).parent +file_path = (base_path / "uv_vis_analysis_gui.ui").resolve() + +uiFile = file_path + +WindowTemplate, TemplateBaseClass = pg.Qt.loadUiType(uiFile) + +"""params for plotting""" +plt.rc('xtick', labelsize = 20) +plt.rc('xtick.major', pad = 3) +plt.rc('ytick', labelsize = 20) +plt.rc('lines', lw = 1.5, markersize = 7.5) +plt.rc('legend', fontsize = 20) +plt.rc('axes', linewidth = 3.5) + +class MainWindow(TemplateBaseClass): + + def __init__(self): + super(TemplateBaseClass, self).__init__() + + # Create the main window + self.ui = WindowTemplate() + self.ui.setupUi(self) + + #setup uv vis plot + self.absorbance_plot_layout = pg.GraphicsLayoutWidget() + self.ui.absorbance_plot_container.layout().addWidget(self.absorbance_plot_layout) + self.absorbance_plot = self.absorbance_plot_layout.addPlot(title="Wavelengths vs. Absorbance") + self.absorbance_plot.setLabel('bottom', 'Wavelength', unit='nm') + self.absorbance_plot.setLabel('left', 'Absorbance', unit='a.u.') + + #setup correction region for uv vis + self.correction_region = pg.LinearRegionItem() + self.correction_region_min = 600 + self.correction_region_max = 900 + self.correction_region.setRegion((self.correction_region_min, self.correction_region_max)) + + #setup uv vis ui signals + self.ui.actionLoad_data.triggered.connect(self.open_data_file) + self.ui.plot_absorbance_pushButton.clicked.connect(self.plot_absorbance) + self.ui.clear_uvvis_pushButton.clicked.connect(self.clear_uvvis) + self.ui.export_uv_vis_pushButton.clicked.connect(self.export_uv_vis) + self.correction_region.sigRegionChanged.connect(self.update_correction_region) + + #setup tauc plot + self.tauc_plot_layout = pg.GraphicsLayoutWidget() + self.ui.tauc_plot_container.layout().addWidget(self.tauc_plot_layout) + self.tauc_plot = self.tauc_plot_layout.addPlot(title="Tauc plot fit") + self.tauc_plot.setLabel('bottom', 'hv', unit='ev') + y_label = '(ahv)' + chr(0x00B2) #char is superscripted 2 + self.tauc_plot.setLabel('left', y_label) + + #setup tauc ui signals + self.ui.plot_tauc_pushButton.clicked.connect(self.plot_tauc) + self.ui.clear_tauc_pushButton.clicked.connect(self.clear_tauc) + self.ui.export_tauc_pushButton.clicked.connect(self.export_tauc) + + self.show() + + def open_data_file(self): + try: + self.filename = QtWidgets.QFileDialog.getOpenFileName(self) + self.data = np.loadtxt(self.filename[0], delimiter = ',', skiprows = 1) + self.Wavelength = self.data[:,0] # in nm + self.Absorbance = self.data[:,1] + except Exception as err: + print(format(err)) + + def update_correction_region(self): + """ Update correction region variables from region """ + self.correction_region_min, self.correction_region_max = self.correction_region.getRegion() + + def plot_absorbance(self): + try: + self.scatter_corrected = False + self.plotted_absorbance = self.Absorbance #by default set to original absorbance data + if self.ui.correct_for_scattering_checkBox.isChecked(): #if checked, correct absorbance data + self.scatter_corrected = True + self.plotted_absorbance = self.Absorbance - np.mean(self.Absorbance[(self.Wavelength>self.correction_region_min) & (self.Wavelength self.hv_min) & (self.hv < self.hv_max) + model = np.polyfit(self.hv[self.index], self.Alpha_hv[self.index], 1) + self.Alpha_hv_fit = self.hv * model[0] + model[1] #This is the linear fit + self.tauc_plot.plot(self.hv, self.Alpha_hv, pen='r') + self.tauc_plot.plot(self.hv, self.Alpha_hv_fit, pen='k') + self.tauc_plot.setXRange(1,2) + self.tauc_plot.setYRange(0, np.max(self.Alpha_hv[self.index]) + 1) + + self.Eg = - model[1]/model[0] + self.ui.bandgap_label.setText(str(self.Eg)) + except: + pass + + def clear_tauc(self): + self.tauc_plot.clear() + + def export_uv_vis(self): + """ Export publication ready uv vis figure """ + try: + filename = QtWidgets.QFileDialog.getSaveFileName(self,caption="Filename with EXTENSION") + plt.figure(figsize=(8,6)) + plt.tick_params(direction='out', length=8, width=3.5) + plt.plot(self.Wavelength, self.plotted_absorbance, linewidth = 3, color = 'r') + if self.scatter_corrected: + plt.xlim(self.correction_region_min, self.correction_region_max) + plt.ylim(0, np.max(self.plotted_absorbance[(self.Wavelength>self.correction_region_min)]) +0.5) + else: + plt.xlim(self.correction_region_min, self.correction_region_max) + plt.ylim(0, np.max(self.plotted_absorbance[(self.Wavelength>self.correction_region_min)] +0.5)) + plt.xlabel('Wavelength (nm)', fontsize = 20) + plt.ylabel('Absorbance (a.u.)', fontsize = 20) + plt.savefig(filename[0],bbox_inches='tight', dpi=300) + plt.close() + except: + pass + + def export_tauc(self): + """ Export publication ready tauc figure""" + try: + filename = QtWidgets.QFileDialog.getSaveFileName(self,caption="Filename with EXTENSION") + plt.figure(figsize=(8,6)) + plt.tick_params(direction='out', length=8, width=3.5) + plt.plot(self.hv, self.Alpha_hv, linewidth = 3, color = 'r') + plt.plot(self.hv, self.Alpha_hv_fit, linewidth = 2, color = 'black') + plt.xlim(1,2) + plt.ylim(0, np.max(self.Alpha_hv[self.index]) + 1) + plt.xlabel('h$\\nu$ (eV)', fontsize = 20) + plt.ylabel('($\\alpha$h$\\nu$)$^2$', fontsize = 20) + #plt.title(Plot_title, fontsize = 20) + + plt.text(1.2, 1.2, r'E$_{g}$ = %.2f eV'%self.Eg, fontsize = 15) + plt.tight_layout() + plt.savefig(filename[0],bbox_inches='tight', dpi=300) + plt.close() + except: + pass + +"""Run the Main Window""" +def run(): + win = MainWindow() + QtGui.QApplication.instance().exec_() + return win \ No newline at end of file diff --git a/PythonGUI_apps/UV_Vis_analysis/uv_vis_analysis_gui.ui b/PythonGUI_apps/UV_Vis_analysis/uv_vis_analysis_gui.ui new file mode 100644 index 0000000..6cb2989 --- /dev/null +++ b/PythonGUI_apps/UV_Vis_analysis/uv_vis_analysis_gui.ui @@ -0,0 +1,238 @@ + + + MainWindow + + + + 0 + 0 + 742 + 924 + + + + MainWindow + + + + + + + UV-Vis plot + + + + + + + + Plot + + + + + + + Correct for scattering + + + + + + + Clear + + + + + + + + + + 0 + 0 + + + + + + + + + Export UV-Vis plot + + + + + + + + + + Tauc plot + + + true + + + false + + + + + + + + hv min (ev) + + + + + + + + 180 + 0 + + + + -9999999.000000000000000 + + + 9999999.000000000000000 + + + 0.010000000000000 + + + 1.500000000000000 + + + + + + + + 180 + 0 + + + + -9999999.000000000000000 + + + 9999999.000000000000000 + + + 0.010000000000000 + + + 1.800000000000000 + + + + + + + hv max (ev) + + + + + + + Plot + + + + + + + Clear + + + + + + + + + + 0 + 0 + + + + + + + + + + + Bandgap (ev): + + + + + + + 0 + + + + + + + + 16777215 + 16777215 + + + + Export tauc plot + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 742 + 31 + + + + + File + + + + + + + + + Load data + + + + + + diff --git a/README.md b/README.md index 064b128..aeac6f0 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,28 @@ # Python_GUI_apps -GUI Python apps +GUI Python apps + +## Dependencies +- scopefoundry +- numpy +- pyqt +- qtpy +- h5py +- pyqtgraph +- matplotlib +- scipy +- lmfit +- customplotting + +## Installing dependencies from command-line +``` +conda install numpy pyqt qtpy h5py pyqtgraph +pip install git+git://github.com/ScopeFoundry/ScopeFoundry.git +pip install matplotlib scipy lmfit customplotting==0.1.4.dev0 +``` + +## Run instructions +After setup, you can run the application by double-clicking DataBrowser.py. +You can also run it from command-line while in the PythonGUI_apps folder: +``` +python DataBrowser.py +```