From 70185c04ba065aacfcf29b241f6ffbdfec49f7a9 Mon Sep 17 00:00:00 2001 From: SarthakJariwala Date: Tue, 28 Apr 2020 11:30:13 -0700 Subject: [PATCH 01/10] change app style to fusion, modify add to mem for plot without irf to include the pl rise time in exported graph --- PythonGUI_apps/DataBrowser.py | 4 ++- .../Lifetime_analysis/Lifetime_plot_fit.py | 26 ++++++++++++++----- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/PythonGUI_apps/DataBrowser.py b/PythonGUI_apps/DataBrowser.py index 3dea4ec..0279473 100644 --- a/PythonGUI_apps/DataBrowser.py +++ b/PythonGUI_apps/DataBrowser.py @@ -85,8 +85,10 @@ def load_app(self): def run(): + app = QtGui.QApplication(sys.argv)#.instance() + app.setStyle("Fusion") win = MainWindow() - QtGui.QApplication.instance().exec_() + sys.exit(app.exec_()) return run() \ No newline at end of file diff --git a/PythonGUI_apps/Lifetime_analysis/Lifetime_plot_fit.py b/PythonGUI_apps/Lifetime_analysis/Lifetime_plot_fit.py index 9437f9f..afe106a 100644 --- a/PythonGUI_apps/Lifetime_analysis/Lifetime_plot_fit.py +++ b/PythonGUI_apps/Lifetime_analysis/Lifetime_plot_fit.py @@ -95,10 +95,12 @@ def __init__(self): self.file = None self.out = None # output file after fitting self.data_list = [] - self.fit_lifetime_called = False + self.fit_lifetime_called_w_irf = False + self.fit_lifetime_called_wo_irf = False self.x_mem = [] # containers for adding x data to memory self.y_mem = [] # containers for adding y data to memory self.best_fit_mem = [] # containers for adding best fit data to memory + self.best_fit_mem_x = [] # containers for adding best fit data to memory self.legend = [] # containers for adding legend to memory #variables accounting for data received from FLIM analysis @@ -258,7 +260,8 @@ def plot(self): x,y = self.acquire_settings() #get data self.ui.plot.plot(x, y, clear=self.ui.clear_plot_checkBox.isChecked(), pen=pg.mkPen(self.plot_color)) - self.fit_lifetime_called = False + self.fit_lifetime_called_w_irf = False + self.fit_lifetime_called_wo_irf = False try: self.ui.Result_textBrowser.setText("Integral Counts :\n" "{:.2E}".format( @@ -347,7 +350,8 @@ def fit_and_plot(self): #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.fit_lifetime_called = True + self.fit_lifetime_called_wo_irf = True + self.fit_lifetime_called_w_irf = False self.ui.plot.setLabel('left', 'Intensity', units='a.u.') self.ui.plot.setLabel('bottom', 'Time (ns)') @@ -475,7 +479,8 @@ def fit_and_plot_with_irf(self): #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.fit_lifetime_called = True + self.fit_lifetime_called_w_irf = True + self.fit_lifetime_called_wo_irf = False except Exception as e: self.ui.Result_textBrowser.append(format(e)) @@ -548,12 +553,19 @@ def clean_up_after_fig_export(self): self.y_mem = [] self.legend = [] self.best_fit_mem = [] + self.best_fit_mem_x = [] def add_trace_to_mem(self): try: - if self.fit_lifetime_called == True: + if self.fit_lifetime_called_w_irf == True: self.x_mem.append(self.out[:,0]) self.y_mem.append(self.out[:,1]) + self.best_fit_mem_x.append(self.out[:,0]) + self.best_fit_mem.append(self.out[:,2]) + elif self.fit_lifetime_called_wo_irf == True: + self.x_mem.append(self.acquire_settings()[0]) + self.y_mem.append(self.acquire_settings()[1]) + self.best_fit_mem_x.append(self.out[:,0]) self.best_fit_mem.append(self.out[:,2]) else: self.x_mem.append(self.acquire_settings()[0]) @@ -578,8 +590,8 @@ def pub_ready_plot_export(self): plt.tick_params(direction='out', length=8, width=3.5) for i in range(len(self.x_mem)): plt.plot(self.x_mem[i], self.y_mem[i], label=str(self.legend[i])) - if self.fit_lifetime_called == True: - plt.plot(self.x_mem[i], self.best_fit_mem[i],'k--') + if self.fit_lifetime_called_w_irf == True or self.fit_lifetime_called_wo_irf == True: + plt.plot(self.best_fit_mem_x[i], self.best_fit_mem[i],'k--') plt.yscale('log') plt.xlabel("Time (ns)", fontsize=20, fontweight='bold') From 57845ef915d6bf1381434b57eec3d036cd1b061c Mon Sep 17 00:00:00 2001 From: SarthakJariwala Date: Fri, 1 May 2020 16:57:50 -0700 Subject: [PATCH 02/10] fix residual func in fit_wo_irf, change export for fit_wo_irf to include raw PL with rise --- .../Lifetime_analysis/Fit_functions.py | 56 ++++++++++--------- .../Lifetime_analysis/Lifetime_plot_fit.py | 15 +++-- 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/PythonGUI_apps/Lifetime_analysis/Fit_functions.py b/PythonGUI_apps/Lifetime_analysis/Fit_functions.py index 2afed57..7ad5c3e 100644 --- a/PythonGUI_apps/Lifetime_analysis/Fit_functions.py +++ b/PythonGUI_apps/Lifetime_analysis/Fit_functions.py @@ -9,10 +9,10 @@ from scipy.optimize import differential_evolution from scipy.special import gamma -def stretch_exp_fit(TRPL, t, Tc = (0,1e5), Beta = (0,1), A = (0,1e6)): +def stretch_exp_fit(TRPL, t, Tc = (0,1e5), Beta = (0,1), A = (0,1e6), noise=(0,1e6)): - def exp_stretch(t, tc, beta, a): - return (a * np.exp(-((1.0 / tc) * t) ** beta)) + def exp_stretch(t, tc, beta, a, noise): + return ((a * np.exp(-((1.0 / tc) * t) ** beta)) + noise) def avg_tau_from_exp_stretch(tc, beta): return (tc / beta) * gamma(1.0 / beta) @@ -25,13 +25,14 @@ def residuals(params):#params are the parameters to be adjusted by differential tc = params[0] beta = params[1] a = params[2] + noise = params[3] - PL_sim = exp_stretch(t,tc,beta,a) + PL_sim = exp_stretch(t,tc,beta,a, noise) - Resid= np.sqrt(np.sum(((PL_sim-TRPL)**2)/(np.sqrt(PL_sim)**2))) + Resid= np.sum(((PL_sim-TRPL)**2)/(np.sqrt(TRPL)**2)) return Resid #returns the difference between the PL data and simulated data - bounds = [Tc, Beta, A] + bounds = [Tc, Beta, A, noise] result = differential_evolution(residuals, bounds) return result.x @@ -41,20 +42,21 @@ def residuals(params):#params are the parameters to be adjusted by differential tc = p[0] beta = p[1] a = p[2] + noise = p[3] - PL_fit = exp_stretch(t,tc,beta,a) + PL_fit = exp_stretch(t,tc,beta,a, noise) avg_tau = avg_tau_from_exp_stretch(tc,beta) - return tc, beta, a, avg_tau, PL_fit + return tc, beta, a, avg_tau, PL_fit, noise -def double_exp_fit(TRPL, t, tau1_bounds=(0,100), a1_bounds=(0,1e5), tau2_bounds=(0,100), a2_bounds=(0,1e5)): +def double_exp_fit(TRPL, t, tau1_bounds=(0,1000), a1_bounds=(0,1e6), tau2_bounds=(0,10000), a2_bounds=(0,1e5), noise=(0,1e6)): def single_exp(t, tau, a): - return (a * np.exp(-((1.0 / tau)*t) )) + return (a * np.exp(-((1.0 / tau)*t))) - def double_exp(t, tau1, a1, tau2, a2): - return ((single_exp(t, tau1, a1)) + (single_exp(t, tau2, a2))) + def double_exp(t, tau1, a1, tau2, a2, noise): + return ((single_exp(t, tau1, a1)) + (single_exp(t, tau2, a2)) + noise) def avg_tau_from_double_exp(tau1, a1, tau2, a2): return (((tau1*a1) + (tau2*a2))/(a1+a2)) @@ -68,13 +70,14 @@ def residuals(params):#params are the parameters to be adjusted by differential a1 = params[1] tau2 = params[2] a2 = params[3] + noise = params[4] - PL_sim = double_exp(t,tau1, a1, tau2, a2) + PL_sim = double_exp(t,tau1, a1, tau2, a2, noise) - Resid= np.sqrt(np.sum(((PL_sim-TRPL)**2)/(np.sqrt(PL_sim)**2))) + Resid= np.sum(((PL_sim-TRPL)**2)/(np.sqrt(TRPL)**2)) return Resid #returns the difference between the PL data and simulated data - bounds = [tau1_bounds, a1_bounds, tau2_bounds, a2_bounds] + bounds = [tau1_bounds, a1_bounds, tau2_bounds, a2_bounds, noise] result = differential_evolution(residuals, bounds) return result.x @@ -85,17 +88,18 @@ def residuals(params):#params are the parameters to be adjusted by differential a1 = p[1] tau2 = p[2] a2 = p[3] + noise = p[4] - PL_fit = double_exp(t, tau1, a1, tau2, a2) + PL_fit = double_exp(t, tau1, a1, tau2, a2, noise) avg_tau = avg_tau_from_double_exp(tau1, a1, tau2, a2) - return tau1, a1, tau2, a2, avg_tau, PL_fit + return tau1, a1, tau2, a2, avg_tau, PL_fit, noise -def single_exp_fit(TRPL, t, tau_bounds=(0,1000), a_bounds=(0,1e5)): +def single_exp_fit(TRPL, t, tau_bounds=(0,10000), a_bounds=(0,1e6), noise=(0,1e6)): - def single_exp(t, tau, a): - return (a * np.exp(-((1.0 / tau)*t) )) + def single_exp(t, tau, a, noise): + return (a * np.exp(-((1.0 / tau)*t) ) + noise) def Diff_Ev_Fit_singleExp(TRPL): TRPL = TRPL @@ -104,13 +108,14 @@ def residuals(params):#params are the parameters to be adjusted by differential #Variable Rates tau = params[0] a = params[1] + noise = params[2] - PL_sim = single_exp(t, tau, a) + PL_sim = single_exp(t, tau, a, noise) - Resid= np.sqrt(np.sum(((PL_sim-TRPL)**2)/(np.sqrt(PL_sim)**2))) + Resid= np.sum(((PL_sim-TRPL)**2)/(np.sqrt(TRPL)**2)) return Resid #returns the difference between the PL data and simulated data - bounds = [tau_bounds, a_bounds] + bounds = [tau_bounds, a_bounds, noise] result = differential_evolution(residuals, bounds) return result.x @@ -119,8 +124,9 @@ def residuals(params):#params are the parameters to be adjusted by differential tau = p[0] a = p[1] + noise = p[2] - PL_fit = single_exp(t, tau, a) + PL_fit = single_exp(t, tau, a, noise) - return tau, a, PL_fit + return tau, a, PL_fit, noise \ No newline at end of file diff --git a/PythonGUI_apps/Lifetime_analysis/Lifetime_plot_fit.py b/PythonGUI_apps/Lifetime_analysis/Lifetime_plot_fit.py index afe106a..7967a1e 100644 --- a/PythonGUI_apps/Lifetime_analysis/Lifetime_plot_fit.py +++ b/PythonGUI_apps/Lifetime_analysis/Lifetime_plot_fit.py @@ -306,7 +306,7 @@ def fit_and_plot(self): self.ui.plot.plot(t, y, clear=self.ui.clear_plot_checkBox.isChecked(), pen=pg.mkPen(self.plot_color)) if fit_func == "Stretched Exponential": #stretch exponential tab - tc, beta, a, avg_tau, PL_fit = stretch_exp_fit(TRPL_interp, t) + tc, beta, a, avg_tau, PL_fit, noise = 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 @@ -316,11 +316,12 @@ def fit_and_plot(self): "\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)) + "\nBeta = "+str(beta)+ + "\nNoise = "+ str(noise)) self.ui.average_lifetime_spinBox.setValue(avg_tau) elif fit_func == "Double Exponential": #double exponential tab - tau1, a1, tau2, a2, avg_tau, PL_fit = double_exp_fit(TRPL_interp, t) + tau1, a1, tau2, a2, avg_tau, PL_fit, noise = 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 @@ -332,11 +333,12 @@ def fit_and_plot(self): "\nTau 1 = " + str(tau1)+" ns" "\nA 1 = " + str(a1)+ "\nTau 2 = " + str(tau2)+" ns" - "\nA 2 = " + str(a2)) + "\nA 2 = " + str(a2)+ + "\nNoise = "+ str(noise)) #TODO - once tau_avg implemented, set average lifetime spinbox to tau_avg value elif fit_func == "Single Exponential": #single exponential tab - tau, a, PL_fit = single_exp_fit(TRPL_interp, t) + tau, a, PL_fit, noise = 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 @@ -345,7 +347,8 @@ def fit_and_plot(self): self.ui.Result_textBrowser.setText("Fit Results:\n\nFit Function: Single Exponential" "\nFit Method: " + "diff_ev" + "\nLifetime = " + str(tau)+ " ns" - "\nA = " + str(a)) + "\nA = " + str(a)+ + "\nNoise = "+ str(noise)) self.ui.average_lifetime_spinBox.setValue(tau) #add fit params to data_list From 69931ef5c52dbd57f1ed02faff50ebcbfd41a3c1 Mon Sep 17 00:00:00 2001 From: Sarthak Jariwala Date: Wed, 1 Jul 2020 17:18:01 -0700 Subject: [PATCH 03/10] Starting port to fbs structure --- src/build/settings/base.json | 6 + src/build/settings/linux.json | 6 + src/build/settings/mac.json | 3 + src/main/icons/Icon.ico | Bin 0 -> 168229 bytes src/main/icons/README.md | 11 + src/main/icons/base/16.png | Bin 0 -> 544 bytes src/main/icons/base/24.png | Bin 0 -> 783 bytes src/main/icons/base/32.png | Bin 0 -> 912 bytes src/main/icons/base/48.png | Bin 0 -> 1497 bytes src/main/icons/base/64.png | Bin 0 -> 1657 bytes src/main/icons/linux/1024.png | Bin 0 -> 26841 bytes src/main/icons/linux/128.png | Bin 0 -> 3177 bytes src/main/icons/linux/256.png | Bin 0 -> 5641 bytes src/main/icons/linux/512.png | Bin 0 -> 11653 bytes src/main/icons/mac/1024.png | Bin 0 -> 47311 bytes src/main/icons/mac/128.png | Bin 0 -> 4978 bytes src/main/icons/mac/256.png | Bin 0 -> 10278 bytes src/main/icons/mac/512.png | Bin 0 -> 21699 bytes src/main/python/DataBrowser_GUI.ui | 97 ++ .../python/Export_Windows/Export_window.py | 77 + .../Export_Windows/Multi_Trace_Exporter.py | 147 ++ .../Export_Windows/Multi_Trace_Exporter.ui | 85 + src/main/python/Export_Windows/__init__.py | 1 + .../python/Export_Windows/export_fig_gui.ui | 171 ++ src/main/python/Export_Windows/export_plot.ui | 186 +++ src/main/python/FLIM_analysis/FLIM_plot.py | 374 +++++ .../python/FLIM_analysis/flim_plot_gui.ui | 185 +++ .../FLIM_analysis/step_size_labview_files.ui | 61 + src/main/python/H5_Pkl/h5_pkl_view.py | 124 ++ src/main/python/H5_Pkl/h5_pkl_view_gui.ui | 89 + src/main/python/H5_Pkl/h5_tree.py | 100 ++ src/main/python/H5_Pkl/h5_view_and_plot.py | 141 ++ .../python/H5_Pkl/h5_view_and_plot_gui.ui | 280 ++++ src/main/python/H5_Pkl/pkl_tree.py | 103 ++ .../python/Image_analysis/Image_analysis.py | 222 +++ .../Image_analysis/image_analysis_gui.ui | 208 +++ .../python/Lifetime_analysis/Fit_functions.py | 132 ++ .../Fit_functions_with_irf.py | 329 ++++ .../Lifetime_analysis_gui_layout.ui | 1392 ++++++++++++++++ .../Lifetime_analysis/Lifetime_plot_fit.py | 653 ++++++++ src/main/python/Lifetime_analysis/__init__.py | 1 + .../python/Lifetime_analysis/picoharp_phd.py | 408 +++++ .../python/Lifetime_analysis/read_ph_phd.py | 54 + .../python/Lifetime_analysis/skip_rows.ui | 42 + .../OO_PZstageScan_acquire_gui.ui | 750 +++++++++ .../OceanOptics_acquire/OO_acquire_gui.ui | 240 +++ .../OceanOptics_acquire_plot.py | 297 ++++ .../PLQE_analysis/column_selection_gui.ui | 107 ++ .../python/PLQE_analysis/plqe_analysis.py | 212 +++ .../python/PLQE_analysis/plqe_analysis_gui.ui | 150 ++ .../Spectrum_analysis/Spectra_fit_funcs.py | 241 +++ .../Spectrum_analysis/Spectra_plot_fit.py | 1020 ++++++++++++ .../Spectrum_analysis/Spectra_plot_fit_gui.ui | 1464 +++++++++++++++++ src/main/python/Spectrum_analysis/__init__.py | 1 + .../Spectrum_analysis/analyze_fit_results.ui | 91 + .../pyqtgraph_MATPLOTLIBWIDGET.py | 56 + .../Spectrum_analysis/scan_params_input.ui | 68 + src/main/python/Table/Table_widget.py | 145 ++ src/main/python/Table/Table_widget_gui.ui | 70 + src/main/python/Table/__init__.py | 7 + .../python/UV_Vis_analysis/uv_vis_analysis.py | 188 +++ .../UV_Vis_analysis/uv_vis_analysis_gui.ui | 268 +++ src/main/python/__init__.py | 1 + src/main/python/main.py | 111 ++ src/main/python/main_0.py | 15 + src/main/resources/base/DataBrowser_GUI.ui | 97 ++ 66 files changed, 11287 insertions(+) create mode 100644 src/build/settings/base.json create mode 100644 src/build/settings/linux.json create mode 100644 src/build/settings/mac.json create mode 100644 src/main/icons/Icon.ico create mode 100644 src/main/icons/README.md create mode 100644 src/main/icons/base/16.png create mode 100644 src/main/icons/base/24.png create mode 100644 src/main/icons/base/32.png create mode 100644 src/main/icons/base/48.png create mode 100644 src/main/icons/base/64.png create mode 100644 src/main/icons/linux/1024.png create mode 100644 src/main/icons/linux/128.png create mode 100644 src/main/icons/linux/256.png create mode 100644 src/main/icons/linux/512.png create mode 100644 src/main/icons/mac/1024.png create mode 100644 src/main/icons/mac/128.png create mode 100644 src/main/icons/mac/256.png create mode 100644 src/main/icons/mac/512.png create mode 100644 src/main/python/DataBrowser_GUI.ui create mode 100644 src/main/python/Export_Windows/Export_window.py create mode 100644 src/main/python/Export_Windows/Multi_Trace_Exporter.py create mode 100644 src/main/python/Export_Windows/Multi_Trace_Exporter.ui create mode 100644 src/main/python/Export_Windows/__init__.py create mode 100644 src/main/python/Export_Windows/export_fig_gui.ui create mode 100644 src/main/python/Export_Windows/export_plot.ui create mode 100644 src/main/python/FLIM_analysis/FLIM_plot.py create mode 100644 src/main/python/FLIM_analysis/flim_plot_gui.ui create mode 100644 src/main/python/FLIM_analysis/step_size_labview_files.ui create mode 100644 src/main/python/H5_Pkl/h5_pkl_view.py create mode 100644 src/main/python/H5_Pkl/h5_pkl_view_gui.ui create mode 100644 src/main/python/H5_Pkl/h5_tree.py create mode 100644 src/main/python/H5_Pkl/h5_view_and_plot.py create mode 100644 src/main/python/H5_Pkl/h5_view_and_plot_gui.ui create mode 100644 src/main/python/H5_Pkl/pkl_tree.py create mode 100644 src/main/python/Image_analysis/Image_analysis.py create mode 100644 src/main/python/Image_analysis/image_analysis_gui.ui create mode 100644 src/main/python/Lifetime_analysis/Fit_functions.py create mode 100644 src/main/python/Lifetime_analysis/Fit_functions_with_irf.py create mode 100644 src/main/python/Lifetime_analysis/Lifetime_analysis_gui_layout.ui create mode 100644 src/main/python/Lifetime_analysis/Lifetime_plot_fit.py create mode 100644 src/main/python/Lifetime_analysis/__init__.py create mode 100644 src/main/python/Lifetime_analysis/picoharp_phd.py create mode 100644 src/main/python/Lifetime_analysis/read_ph_phd.py create mode 100644 src/main/python/Lifetime_analysis/skip_rows.ui create mode 100644 src/main/python/OceanOptics_acquire/OO_PZstageScan_acquire_gui.ui create mode 100644 src/main/python/OceanOptics_acquire/OO_acquire_gui.ui create mode 100644 src/main/python/OceanOptics_acquire/OceanOptics_acquire_plot.py create mode 100644 src/main/python/PLQE_analysis/column_selection_gui.ui create mode 100644 src/main/python/PLQE_analysis/plqe_analysis.py create mode 100644 src/main/python/PLQE_analysis/plqe_analysis_gui.ui create mode 100644 src/main/python/Spectrum_analysis/Spectra_fit_funcs.py create mode 100644 src/main/python/Spectrum_analysis/Spectra_plot_fit.py create mode 100644 src/main/python/Spectrum_analysis/Spectra_plot_fit_gui.ui create mode 100644 src/main/python/Spectrum_analysis/__init__.py create mode 100644 src/main/python/Spectrum_analysis/analyze_fit_results.ui create mode 100644 src/main/python/Spectrum_analysis/pyqtgraph_MATPLOTLIBWIDGET.py create mode 100644 src/main/python/Spectrum_analysis/scan_params_input.ui create mode 100644 src/main/python/Table/Table_widget.py create mode 100644 src/main/python/Table/Table_widget_gui.ui create mode 100644 src/main/python/Table/__init__.py create mode 100644 src/main/python/UV_Vis_analysis/uv_vis_analysis.py create mode 100644 src/main/python/UV_Vis_analysis/uv_vis_analysis_gui.ui create mode 100644 src/main/python/__init__.py create mode 100644 src/main/python/main.py create mode 100644 src/main/python/main_0.py create mode 100644 src/main/resources/base/DataBrowser_GUI.ui diff --git a/src/build/settings/base.json b/src/build/settings/base.json new file mode 100644 index 0000000..7d4aafc --- /dev/null +++ b/src/build/settings/base.json @@ -0,0 +1,6 @@ +{ + "app_name": "GLabViz", + "author": "Sarthak Jariwala", + "main_module": "src/main/python/main.py", + "version": "0.0.0" +} \ No newline at end of file diff --git a/src/build/settings/linux.json b/src/build/settings/linux.json new file mode 100644 index 0000000..7a64c95 --- /dev/null +++ b/src/build/settings/linux.json @@ -0,0 +1,6 @@ +{ + "categories": "Utility;", + "description": "", + "author_email": "", + "url": "" +} \ No newline at end of file diff --git a/src/build/settings/mac.json b/src/build/settings/mac.json new file mode 100644 index 0000000..f7bd610 --- /dev/null +++ b/src/build/settings/mac.json @@ -0,0 +1,3 @@ +{ + "mac_bundle_identifier": "" +} \ No newline at end of file diff --git a/src/main/icons/Icon.ico b/src/main/icons/Icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..3312d8606e5ccf262e3e8f3397189f9105049a19 GIT binary patch literal 168229 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 WW*AAnCk45!Wo2xK?K9y1_WuD=+-6k( literal 0 HcmV?d00001 diff --git a/src/main/icons/README.md b/src/main/icons/README.md new file mode 100644 index 0000000..c6c4194 --- /dev/null +++ b/src/main/icons/README.md @@ -0,0 +1,11 @@ +![Sample app icon](linux/128.png) + +This directory contains the icons that are displayed for your app. Feel free to +change them. + +The difference between the icons on Mac and the other platforms is that on Mac, +they contain a ~5% transparent margin. This is because otherwise they look too +big (eg. in the Dock or in the app switcher). + +You can create Icon.ico from the .png files with +[an online tool](http://icoconvert.com/Multi_Image_to_one_icon/). \ No newline at end of file diff --git a/src/main/icons/base/16.png b/src/main/icons/base/16.png new file mode 100644 index 0000000000000000000000000000000000000000..f7d02dc268f79c183376b30dddf5ea0cf5827dfe GIT binary patch literal 544 zcmV+*0^j|KP)TmGsBvSN0G7{E%tE_-gl|fEv;0aRau+G)01lU7P2{XIYtU(DY*`z5wZh-t$ z17;xV-$KBe25cyY`gJ9TKA2b zQ`OPi4q_$n+Q`u1Rq4iCqev!2*O%K1W{B_hXa`+h=PJlsp!i@kPT6X zFgMtf4&AdjLi@^LEPz?gPFhert=$)fabl?(6T3=?Mx5-d`oLZ)@tUkX_s2+$#>F(4wKl3YIpWCbP|H9s!5%<RuTU6aQntuM7@! zJkGa2f3Hqhe%Cykf0SrKM}494z#-;LdXvexZ{ zdcbzc3-LACtO$GJb`m+Ec0Hhy4FPV@8I-klM7`(%Jex>U7OJ7wQzqfr0= N002ovPDHLkV1g}~Xzc(1 literal 0 HcmV?d00001 diff --git a/src/main/icons/base/32.png b/src/main/icons/base/32.png new file mode 100644 index 0000000000000000000000000000000000000000..36b25e8602defb2a39c5163a8826612e8623c71a GIT binary patch literal 912 zcmV;B18@9^P)nVc} zqV&-)A}~WJ5emdiD0(Q3;#A@fR`PP;{{vTJ1{iUxCP-K{Un_#u9&?SH$Yd5ZC`0I*8@X_GI=oM zz9F&f(Al|ODu7)(S+1?l){woPsgFZ>gcIZ6<)+ik{N?9^qM!?Kv&Omu>U|%YET71* zAztmlAb~8t=EF+T*8XL^PN=11b08sU|oDRuTRV9=W z!Be2<7Ze=9KL8L2Y!_v<8w6JTBMu}^TA~VeW=%(3BQD66LBg#bRP7JHTT1|fEuwZq zfe;lZKH6Zw7y=*+*q#OlQx=miQ?1^s0T9IvQRx~BlH6t_{eiylEBD|<07wYUijrXD zfa_c&{O#0GZ3sZijU_7pL<9ygCVAmaiTGH@P)z}#?S^jXsHO(cbAwiOoB%p*P^eA_ zAi@pMNvnDV0K#tAGd$F)R}4UjSRW!o0Asb;k6c#v@c#ht_{ighnspvsSH3sb)(*q9 z{|Mkm->6V^;D)C=)HFv5ooN?KSKX#{gBCzh-O%N&?&zO)E6)*KOX~&&KuEQEQuDQ; zal;6}a@D#3G;ZhsXsR|0V8o4R0I^h40qD7*0idfoE`Vrmr~!Tr m+-P@Y&Y*^FZxxMdb^HaNZoLAL0b_Om0000aA!)0& zq7kJSE%ku$NC7DC9il+u-WDTFy-ei`!#%zv4U(y`HT`KwbS@|kx( zOVLW-O)6QuzuQ;yXsf3q=!YkyC#4)pk{JKDU*G2K)yYbNpK(C4WKcN|E} zx&~n9RsIj?rgxO0SOxLTw?E14_w>ZhR|Md(R*~!L@^@mhzKk3N za3bn#xut+1k7IN1>OCwCzJ0>HxlI9laxk!FV~fC8-=IBM216@Y0%LBPW>6k7Mg=(y6pxij`_7J%*d zdHCL^e9we}WG~+5EA%o3AbGBe_|{#bTsZ9x00|HN;3&OXH3PtM3DwxscT{?9;CM~>s|3`Q3IkvjKc4%ri~m zWMWS<<*L)F_qrHxRSIXeNR4qc@qG=RgM}UtDG}?FRH&DL3#g?5NaI07E1ZA=3)k7+ zwHX0;%mF|WRPr~+nw@9>v}&+)adlipH8?^FkJrRl7}SVZ;0gsUqyS%+2CiBLE~o(U zVi7!373L<3j6X@u=KwK$K~itQ>?ipVLX*kZXXH(zW)}%ZG#@bAApj$~VrtN+KoX~` zDOKhbuxwCtLSsZ%&8msD*}qDq)I&{KM*{S^bO3osi>RSo6hS(R&8*nlMj-%O_fcjZ z=KWyqg?!eHk*7d0HFPCn0wm7`LA@kedOh4UYduDNbZ&Hd!IJeX#jF5mFW>=;YAh_q z-n-x->EHcSxy;!lZ{^Iav zRy+@;O>CF-k}Xu|WqgfHt-c$jJk!eSj#wz)Q`X-N%dnU*&RZxKy~THG9;XHw;|ZuZP2vrwU2S3|^&U1sNK^$*Qb zTPWXClvXuFpxgvb=6qqpc4N z>kU+6pd2;IRDiLb<@JgHu*mcVE@*R60b8t>1t1evld1v$c@xGJ07f;~q_Vo#j)lx7 zjB5ZgVMh%M1tyVo(+T4`02aBE$}RwmlDzI3fJ~T8ssa|WJz*9V0E(o;l^1Q8j&1j-5CD?n7x3Ru@rhH00000NkvXXu0mjf7k9Up literal 0 HcmV?d00001 diff --git a/src/main/icons/base/64.png b/src/main/icons/base/64.png new file mode 100644 index 0000000000000000000000000000000000000000..4b0a42323f9d02912c17717cc21d9d6bb6f5c613 GIT binary patch literal 1657 zcmV-<28Q{GP)OaPm)zxYuRG&^ZnM{1a=Sabx3e2*pEB)SyP2K; z?|Ei+?zm#)fd-F;xA$z7b}9WDjoj)Z!)y=z`{k* zbHkzr{`F*}r?XW$0Np*%Ivox(3hnfjDRQiPPvW=#1%OLuqrL0G_%L+)TpXW5JC{t+ zms;Z%z z@gF@0Fmx>3+_P2w7P{MfDqhzge_c{{-QK@2?HPcX!RUhl1;2s3Tk}?$hUM@yEQgPT z0)U-+1;QB^z{cY&pUvkeZMmR|QE?rX|nzHI|KZ7(Jgf#r?7{JwUqL0Wjehs<}tL`}& zK+5`@-=%9xK#PfK=?f85)ssM2LH>$B5#th~ z?ZoV=&qv3}leg_pT(JZIu0IB@zY5(tvk_C@N0C&QM^!g3`dUApgc4F1(N1D@=^Uit zIY`5MWdRI-5N_}4kl%&wy*xK&07zUx!CR6j(3bJ1`Z?H#CZL3B!~;OrnXyUo#I1W1 zmx};m=cA8H5`G1`>v%`e00a=*f`i)@k+Lc6-BW?>y)h_$3L5;P)zl6Erg=!i^N@z4 zIQC`z8L5WcV|Naz9{|97p!izekb&ip6#ryUQNeYbkgoBt4?t5EoyFrXcB*twS1NW&^d6XA^T?y%8iB)zu;B?K&m2zlpydR)RfdVwY=7h zTs9oBfgsvq0d06GVY zG*ECY{R=?2>I{NY0JH|GYtvFxRwl070-#YaPXkLrj%|u;IZ%5MwhMrniW=c}w&`Jm z3oKl>4?yQ&kp_~G3;TjP%hJFHf)_xc@LKq@nG{ZbWyR^bV*nZj>%BpZ%P2oq4euD? zoRBv+!3qGCV0uv~zm6IgZqxP30O%Yzo)m0pkeGs0*dJ6lHPQvS@HxWrbd&;sN|<-- zBUZM-!8%7Z$UICATz3V4MuGp_P%JSIXZH-WoK!g7$yKn6GKBK7#x;<0*WCl4bFg`e ztXLJg?m7UCg8ekumP@XS0ibgbl?E4G7Yab5;9?pCxGo-m&Y{vY2y)#s02&1cX%OtX zw*YhwylL>jbzcBz6im|Kq3iwu&^fG3gU7D>4nU)zr6CZ`BClOv8vvG6$p<1g0NeoJ z4gmILf|lzPz!d)U(T&Y_ZVs{U_wB#V0Gu2i%?}ix#Un3tJ`f5>N07&9@BzS7CPxo7 z9gKa5PJ=)I zhOXNJz$7vaf&iGhZX19u5ovH40GI2I0Wi6p1{VQvyIv6hU2dhp1pu~PcLe~G8)>Kz zfFsvk1E8ytG&lrMk?XDlU~-xU2LLK|T?hbOcGF-3fNQP`1;At{4V(a6bzL|BT|8;9 z03gJ5&j2u4rNIP%FxNc=pvx!?WdI0w-4_5%OKD&b_89;;1+E_w?YeIObU_;SK^oYy zXjuZlTps|}PkB8T-~QW^mWJvh;f{H9;U8~ZU#|ZE!$D!oHJQ}700000NkvXXu0mjf DAnWoV literal 0 HcmV?d00001 diff --git a/src/main/icons/linux/1024.png b/src/main/icons/linux/1024.png new file mode 100644 index 0000000000000000000000000000000000000000..2248377200ccb2f2bfb3e721089213cc7b47451e GIT binary patch literal 26841 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 fqxwknp~-~COe_WRi9&kd-X~+QpfOe)k z9)i}?{~JgIGi+D{iXkp4s&3ef^c0GV9zP;DnB?ZRHyt~v`0nx=J#8R= zsbMMc{n*&tZ1(uK(cvW?6!yOQowW3ijRNy2FCo12&El)?ubNJSi~s)ODSrzQTk*HB z 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 literal 0 HcmV?d00001 diff --git a/src/main/icons/linux/256.png b/src/main/icons/linux/256.png new file mode 100644 index 0000000000000000000000000000000000000000..578fdc787602fd2e6e5608e8dcba51f1eb638e68 GIT binary patch literal 5641 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 WbgP9LPnVs<-})a=`f`{6 literal 0 HcmV?d00001 diff --git a/src/main/icons/linux/512.png b/src/main/icons/linux/512.png new file mode 100644 index 0000000000000000000000000000000000000000..0fbac4f9c0612b5a799a1544a857315ab8ef20c5 GIT binary patch literal 11653 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 SOW=iR2z%V(SpHGxoBszVs1fA= literal 0 HcmV?d00001 diff --git a/src/main/icons/mac/1024.png b/src/main/icons/mac/1024.png new file mode 100644 index 0000000000000000000000000000000000000000..c1c8691dd52d9a3cfa4ed7d8bcfe92be80f4e835 GIT binary patch literal 47311 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 BTt)x@ literal 0 HcmV?d00001 diff --git a/src/main/icons/mac/128.png b/src/main/icons/mac/128.png new file mode 100644 index 0000000000000000000000000000000000000000..de9bee605f235a04db3a1848323f0780ddd0c27a GIT binary patch literal 4978 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

XL;iL7 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 + + MainWindow + + + + 0 + 0 + 435 + 221 + + + + GLabViz - DataBrowser + + + + + + + + 30 + 75 + true + + + + GLabViz + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 15 + + + + Analysis Tool : + + + + + + + + 15 + + + + + + + + + 15 + + + + Load + + + + + + + + + 0 + 0 + 435 + 21 + + + + + + + + diff --git a/src/main/python/Export_Windows/Export_window.py b/src/main/python/Export_Windows/Export_window.py new file mode 100644 index 0000000..0e5cc63 --- /dev/null +++ b/src/main/python/Export_Windows/Export_window.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +""" +Created on Sun Nov 3 20:43:12 2019 + +@author: sarth +""" +from pathlib import Path +import pyqtgraph as pg +from pyqtgraph.Qt import QtCore, QtGui + +"""Export Images GUI""" +base_path = Path(__file__).parent +ui_file_path = (base_path / "export_fig_gui.ui").resolve() +exportFig_WindowTemplate, exportFig_TemplateBaseClass = pg.Qt.loadUiType(ui_file_path) + +class ExportFigureWindow(exportFig_TemplateBaseClass): + + export_fig_signal = QtCore.pyqtSignal() + + def __init__(self): + exportFig_TemplateBaseClass.__init__(self) + + self.ui = exportFig_WindowTemplate() + self.ui.setupUi(self) + self.ui.cmap_comboBox.addItems(['viridis', 'plasma', 'inferno', 'magma', + 'cividis','Greys', 'Purples', 'Blues', + 'Greens', 'Oranges', 'Reds', 'YlOrBr', + 'YlOrRd', 'OrRd', 'PuRd', 'RdPu', 'BuPu', + 'GnBu', 'PuBu', 'YlGnBu', 'PuBuGn', 'BuGn', + 'YlGn', 'binary', 'gist_yarg', 'gist_gray', + 'gray', 'bone', 'pink', 'spring', 'summer', + 'autumn', 'winter', 'cool', 'Wistia', 'hot', + 'afmhot', 'gist_heat', 'copper', 'rainbow', 'jet']) + self.ui.cbar_checkBox.stateChanged.connect(self.cbar_title_state) + self.ui.exportFig_pushButton.clicked.connect(self.export) + self.show() + + def cbar_title_state(self): + if self.ui.cbar_checkBox.isChecked(): + self.ui.cbar_label.setEnabled(True) + else: + self.ui.cbar_label.setEnabled(False) + + def export(self): + self.export_fig_signal.emit() + self.close() + +"""Export plot GUI""" +ui_file_path = (base_path / "export_plot.ui").resolve() +export_WindowTemplate, export_TemplateBaseClass = pg.Qt.loadUiType(ui_file_path) + +class ExportPlotWindow(export_TemplateBaseClass): + + export_fig_signal = QtCore.pyqtSignal() + + def __init__(self): + export_TemplateBaseClass.__init__(self) + + self.ui = export_WindowTemplate() + self.ui.setupUi(self) + #self.ui.traceColor_comboBox.addItems(["C0","C1","C2","C3","C4","C5","C6","C7", "r", "g", "b", "y", "k"]) + #self.ui.fitColor_comboBox.addItems(["k", "r", "b", "y", "g","C0","C1","C2","C3","C4","C5","C6","C7"]) + self.ui.export_pushButton.clicked.connect(self.export) + #self.ui.legend_checkBox.stateChanged.connect(self.legend_title) + self.show() + + #def legend_title(self): + # if self.ui.legend_checkBox.isChecked(): + # self.ui.legend1_lineEdit.setEnabled(True) + # self.ui.legend2_lineEdit.setEnabled(True) + # else: + # self.ui.legend1_lineEdit.setEnabled(False) + # self.ui.legend2_lineEdit.setEnabled(False) + + def export(self): + self.export_fig_signal.emit() + self.close() \ No newline at end of file diff --git a/src/main/python/Export_Windows/Multi_Trace_Exporter.py b/src/main/python/Export_Windows/Multi_Trace_Exporter.py new file mode 100644 index 0000000..5d5555d --- /dev/null +++ b/src/main/python/Export_Windows/Multi_Trace_Exporter.py @@ -0,0 +1,147 @@ +import pyqtgraph as pg +from pathlib import Path +from pyqtgraph.Qt import QtCore, QtGui, QtWidgets +try: + from Lifetime_analysis.read_ph_phd import read_picoharp_phd, get_x_y +except Exception as e: + print(e) +import matplotlib.pyplot as plt + +"""Recylce params for plotting""" +plt.rc('xtick', labelsize = 20) +plt.rc('xtick.major', pad = 3) +plt.rc('ytick', labelsize = 20) +plt.rc('lines', lw = 2.5, markersize = 7.5) +plt.rc('legend', fontsize = 20) +plt.rc('axes', linewidth=3.5) + +pg.mkQApp() + +base_path = Path(__file__).parent +file_path = (base_path / "Multi_Trace_Exporter.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) + + self.temp_layout = pg.GraphicsLayoutWidget() + + # file system tree + self.fs_model = QtWidgets.QFileSystemModel() + self.fs_model.setRootPath(QtCore.QDir.currentPath()) + self.ui.treeView.setModel(self.fs_model) + self.ui.treeView.setIconSize(QtCore.QSize(25,25)) + self.ui.treeView.setSortingEnabled(True) + + self.tree_selectionModel = self.ui.treeView.selectionModel() + self.tree_selectionModel.selectionChanged.connect(self.on_treeview_selection_change) + + self.ui.comboBox.currentIndexChanged.connect(self.add_trace_to_temp_plot) + self.ui.add_pushButton.clicked.connect(self.add_trace_to_mem) + self.ui.export_pushButton.clicked.connect(self.pub_ready_plot_export) + + self.x_i = [] + self.y_i = [] + self.x_mem = [] + self.y_mem = [] + self.legend = [] + + self.show() + + def on_treeview_selection_change(self): + try: + fname = self.fs_model.filePath(self.tree_selectionModel.currentIndex()) + _ , ext = fname.rsplit('.',1) + + self.ui.comboBox.clear() + self.ui.textBrowser.clear() + self.x_i = [] + self.y_i = [] + + if ext in ['phd']: + self.parser = read_picoharp_phd(fname) + curve_list = [] + + for i in range(self.parser.no_of_curves()): + curve_list.append("Curve "+str(i)) + x, y = get_x_y(i, self.parser, smooth_trace=self.ui.smooth_checkBox.isChecked(), boxwidth=self.ui.smooth_spinBox.value()) + self.x_i.append(x) + self.y_i.append(y) + + self.ui.comboBox.addItems(curve_list) + self.ui.textBrowser.setText(str(self.parser.info())) + + else: + self.ui.textBrowser.setText(str("Select a PicoHarp File")) + except Exception as e: + print(e) + + def add_trace_to_temp_plot(self): + try: + #self.temp_layout = pg.GraphicsLayoutWidget() + self.temp_layout.clear() + self.temp_plot = self.temp_layout.addPlot(title = "Current Selection") + self.temp_plot.plot(self.x_i[self.ui.comboBox.currentIndex()], self.y_i[self.ui.comboBox.currentIndex()], pen='r') + self.temp_plot.setLogMode(False, True) + self.temp_layout.show() + except Exception as e: + print(e) + + def add_trace_to_mem(self): + try: + self.x_mem.append(self.x_i[self.ui.comboBox.currentIndex()]) + self.y_mem.append(self.y_i[self.ui.comboBox.currentIndex()]) + self.legend.append(self.ui.lineEdit.text()) + except Exception as e: + print(e) + + def pub_ready_plot_export(self): + 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) + for i in range(len(self.x_mem)): + if self.ui.Normalize_checkBox.isChecked(): + plt.plot(self.x_mem[i], self.y_mem[i]/max(self.y_mem[i]), label=str(self.legend[i])) + else: + plt.plot(self.x_mem[i], self.y_mem[i], label=str(self.legend[i])) + + plt.yscale('log') + plt.xlabel("Time (ns)", fontsize=20, fontweight='bold') + plt.ylabel("Intensity (norm.)", fontsize=20, fontweight='bold') + plt.legend() + plt.tight_layout() + + plt.savefig(filename[0],bbox_inches='tight', dpi=300) + plt.close() + + self.clear_memory() + + except Exception as e: + print(e) + pass + + def clear_memory(self): + self.x_mem = [] + self.y_mem = [] + self.legend = [] + + + + +def run(): + win = MainWindow() + QtGui.QApplication.instance().exec_() + return win + +#run() \ No newline at end of file diff --git a/src/main/python/Export_Windows/Multi_Trace_Exporter.ui b/src/main/python/Export_Windows/Multi_Trace_Exporter.ui new file mode 100644 index 0000000..7b59ef1 --- /dev/null +++ b/src/main/python/Export_Windows/Multi_Trace_Exporter.ui @@ -0,0 +1,85 @@ + + + MainWindow + + + + 0 + 0 + 1108 + 1063 + + + + MainWindow + + + + + + + + + + Smoothen Trace + + + + + + + Enter Trace Legend Here + + + + + + + Add + + + + + + + Normalize (for export) + + + + + + + 1 + + + + + + + Export + + + + + + + + + + + + + + + 0 + 0 + 1108 + 38 + + + + + + + + diff --git a/src/main/python/Export_Windows/__init__.py b/src/main/python/Export_Windows/__init__.py new file mode 100644 index 0000000..7c68785 --- /dev/null +++ b/src/main/python/Export_Windows/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- \ No newline at end of file diff --git a/src/main/python/Export_Windows/export_fig_gui.ui b/src/main/python/Export_Windows/export_fig_gui.ui new file mode 100644 index 0000000..e80e065 --- /dev/null +++ b/src/main/python/Export_Windows/export_fig_gui.ui @@ -0,0 +1,171 @@ + + + ExportFigure + + + + 0 + 0 + 420 + 369 + + + + Form + + + + + + + 15 + + + + 1000000000 + + + + + + + + 15 + + + + Color Bar Label + + + + + + + + 15 + + + + Data Channel to Save + + + + + + + + 15 + + + + + + + + + 15 + + + + + + + + + 15 + + + + ColorMap + + + + + + + + 15 + + + + 1000000000 + + + + + + + + 15 + + + + ColorBar Min + + + + + + + + 15 + + + + ColorBar Max + + + + + + + false + + + + 15 + + + + + + + + + 15 + + + + Export Figure + + + + + + + + 15 + + + + Reversed + + + + + + + + 15 + + + + + + + + + + + + diff --git a/src/main/python/Export_Windows/export_plot.ui b/src/main/python/Export_Windows/export_plot.ui new file mode 100644 index 0000000..840264f --- /dev/null +++ b/src/main/python/Export_Windows/export_plot.ui @@ -0,0 +1,186 @@ + + + Form + + + + 0 + 0 + 930 + 435 + + + + Form + + + + + + + 10 + + + + 4 + + + -100.000000000000000 + + + 10000000000000000000000.000000000000000 + + + 1.500000000000000 + + + + + + + + 10 + + + + Lower + + + + + + + + 10 + + + + Lower + + + + + + + + 10 + + + + Upper + + + + + + + + 15 + + + + Export Graph + + + + + + + + 12 + + + + Y limits + + + + + + + + 10 + + + + Upper + + + + + + + + 12 + + + + X limits + + + + + + + + 10 + + + + 4 + + + -10000.000000000000000 + + + 1000000000000000000.000000000000000 + + + 0.010000000000000 + + + + + + + + 10 + + + + 4 + + + -10000000.000000000000000 + + + 100000000000.000000000000000 + + + + + + + + 10 + + + + 4 + + + -1000000000.000000000000000 + + + 10000000000000.000000000000000 + + + 10000.000000000000000 + + + + + + + + diff --git a/src/main/python/FLIM_analysis/FLIM_plot.py b/src/main/python/FLIM_analysis/FLIM_plot.py new file mode 100644 index 0000000..5f5744f --- /dev/null +++ b/src/main/python/FLIM_analysis/FLIM_plot.py @@ -0,0 +1,374 @@ +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 + +sys.path.append(os.path.abspath('../Lifetime_analysis')) +sys.path.append(os.path.abspath('../Spectrum_analysis')) +sys.path.append(os.path.abspath('../H5_Pkl')) +sys.path.append(os.path.abspath('../Export_Windows')) +from Lifetime_analysis import Lifetime_plot_fit +from Spectrum_analysis import Spectra_plot_fit +from H5_Pkl import h5_pkl_view +try: + from Export_window import ExportFigureWindow +except: + from Export_Windows.Export_window import ExportFigureWindow +# local modules + +pg.mkQApp() +pg.setConfigOption('background', 'w') + + +base_path = Path(__file__).parent +file_path = (base_path / "flim_plot_gui.ui").resolve() + +uiFile = file_path + +WindowTemplate, TemplateBaseClass = pg.Qt.loadUiType(uiFile) + +def updateDelay(scale, time): + """ Hack fix for scalebar inaccuracy """ + QtCore.QTimer.singleShot(time, scale.updateBar) + +class MainWindow(TemplateBaseClass): + + hist_data_signal = QtCore.pyqtSignal() + + def __init__(self): + pg.setConfigOption('imageAxisOrder', 'row-major') + 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_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.export_window) + 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.ui.analyze_lifetime_pushButton.clicked.connect(self.on_analyze_lifetime) + self.ui.analyze_psf_pushButton.clicked.connect(self.on_analyze_psf) + + self.show() + + def open_file(self): + """ Open FLIM scan file """ + try: + self.filename = QtWidgets.QFileDialog.getOpenFileName(self, filter="Scan files (*.pkl *.h5 *.txt)") + if ".pkl" in self.filename[0]: + self.flim_scan_file = pickle.load(open(self.filename[0], 'rb')) + self.scan_file_type = "pkl" + self.launch_h5_pkl_viewer() + self.get_data_params() + elif ".h5" in self.filename[0]: + self.flim_scan_file = h5py.File(self.filename[0], 'r') + self.scan_file_type = "h5" + self.launch_h5_pkl_viewer() + self.get_data_params() + elif ".txt" in self.filename[0]: + self.intensity_sums = np.loadtxt(self.filename[0]).T + self.stepsize_window = StepSizeWindow() + self.stepsize_window.stepsize_signal.connect(self.get_stepsize) + self.scan_file_type = "txt" + # self.pkl_file = pickle.load(open(self.filename[0], 'rb')) + except Exception as err: + print(format(err)) + + def launch_h5_pkl_viewer(self): + """ Launches H5/PKL viewer to give an insight into the data and its structure""" + viewer_window = h5_pkl_view.H5PklView(sys.argv) + viewer_window.settings['data_filename'] = self.filename[0] + + 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 get_stepsize(self): + """ Get step size from user input -- specfically written for loading + txt files from legacy labview code, but can also be run on txt file + saved using the new FLIM acquistion code """ + self.stepsize = self.stepsize_window.ui.stepsize_doubleSpinBox.value() + self.x_step_size = self.stepsize + self.y_step_size = self.stepsize + + def get_data_params(self): + + data = self.flim_scan_file + if self.scan_file_type == "pkl": + self.x_scan_size = data['Scan Parameters']['X scan size (um)'] + self.y_scan_size = data['Scan Parameters']['Y scan size (um)'] + self.x_step_size = data['Scan Parameters']['X step size (um)'] + self.y_step_size = data['Scan Parameters']['Y step size (um)'] + self.hist_data = data['Histogram data'] + self.time_data = data['Time data'] + else: #run this if scan file is h5 + self.x_scan_size = data['Scan Parameters'].attrs['X scan size (um)'] + self.y_scan_size = data['Scan Parameters'].attrs['Y scan size (um)'] + self.x_step_size = data['Scan Parameters'].attrs['X step size (um)'] + self.y_step_size = data['Scan Parameters'].attrs['Y step size (um)'] + self.hist_data = data['Histogram data'][()] #get dataset values + self.time_data = data['Time data'][()] + + self.numb_x_pixels = int(self.x_scan_size/self.x_step_size) + self.numb_y_pixels = int(self.y_scan_size/self.y_step_size) + + + def plot_intensity_sums(self): + try: + if self.scan_file_type is "pkl" or self.scan_file_type is "h5": + pg.setConfigOption('imageAxisOrder', 'row-major') + self.hist_data = np.reshape(self.hist_data, newshape=(self.hist_data.shape[0], self.numb_x_pixels*self.numb_y_pixels)) + self.intensity_sums = np.sum(self.hist_data, axis=0) #sum intensities for each pixel + self.intensity_sums = np.reshape(self.intensity_sums, newshape=(self.numb_x_pixels, self.numb_y_pixels)) + else: + pg.setConfigOption('imageAxisOrder', 'col-major') + self.ui.intensity_sums_viewBox.view.invertY(False) # stop y axis invert + self.ui.intensity_sums_viewBox.setImage(self.intensity_sums, scale= + (self.x_step_size, + self.y_step_size)) + if self.scan_file_type is "pkl" or self.scan_file_type is "h5": + 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)) + self.ui.intensity_sums_viewBox.view.sigRangeChanged.connect(lambda: updateDelay(scale, 10)) + 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.mean(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 on_analyze_psf(self): + self.spectrum_window = Spectra_plot_fit.MainWindow() + self.spectrum_window.show() + self.spectrum_window.opened_from_flim = True + sum_data = self.ui.intensity_sums_viewBox.getRoiPlot().getPlotItem().curves[0].getData() + self.spectrum_window.sum_data_from_flim = np.asarray(sum_data) + self.spectrum_window.ui.plot_without_bck_radioButton.setChecked(True) + self.spectrum_window.ui.result_textBrowser.setText("Data successfully loaded from FLIM analysis.") + + def plot_raw_scan(self): + try: + self.hist_image = np.reshape(self.hist_data, newshape=(self.hist_data.shape[0],self.numb_x_pixels,self.numb_y_pixels)) + self.times = self.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= + (self.x_step_size, + self.y_step_size), 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)) + self.ui.raw_hist_data_viewBox.view.sigRangeChanged.connect(lambda: updateDelay(scale, 10)) + + 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"): #make sure second plot is properly cleared everytime + self.roi2_plot.clear() + c = self.ui.raw_hist_data_viewBox.getRoiPlot().getPlotItem().curves.pop() + c.scene().removeItem(c) + self.roi2_plot = self.ui.raw_hist_data_viewBox.getRoiPlot().plot(xvals, data, pen='r') + + def get_raw_hist_curve(self, curve_index): + #curve_index = 0 for original roi + #curve_index = 1 for second comparison roi + curves = self.ui.raw_hist_data_viewBox.getRoiPlot().getPlotItem().curves + return curves[curve_index].getData() + + def on_analyze_lifetime(self): + self.lifetime_window = Lifetime_plot_fit.MainWindow() + self.lifetime_window.show() + self.lifetime_window.opened_from_flim = True + self.lifetime_window.hist_data_from_flim = np.asarray(self.get_raw_hist_curve(0)) + self.lifetime_window.ui.Result_textBrowser.setText("Data successfully loaded from FLIM analysis.") + + def export_window(self): + self.export_window = ExportFigureWindow() + self.export_window.ui.vmin_spinBox.setValue(np.min(self.intensity_sums)) + self.export_window.ui.vmax_spinBox.setValue(np.max(self.intensity_sums)) + self.export_window.export_fig_signal.connect(self.save_intensities_image) + + def save_intensities_image(self): + try: + 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 + save_to = folder + "\\" + filename + "_intensity_sums.png" + if self.export_window.ui.reverse_checkBox.isChecked(): + colormap = str(self.export_window.ui.cmap_comboBox.currentText())+"_r" + else: + colormap = str(self.export_window.ui.cmap_comboBox.currentText()) + if self.export_window.ui.cbar_checkBox.isChecked(): + label = str(self.export_window.ui.cbar_label.text()) + else: + label = "PL Intensity (a.u.)" + cpm.plot_confocal(self.intensity_sums, FLIM_adjust=False, + stepsize=np.abs(self.x_step_size),cmap=colormap, + cbar_label=label, vmin=self.export_window.ui.vmin_spinBox.value(), + vmax=self.export_window.ui.vmax_spinBox.value()) + plt.savefig(save_to, bbox_inches='tight', dpi=300) + except Exception as e: + print(format(e)) + + def save_intensities_array(self): + try: + 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 + save_to = folder + "\\" + 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 + +"""Skip rows GUI""" +ui_file_path = (base_path / "step_size_labview_files.ui").resolve() +stepsize_WindowTemplate, stepsize_TemplateBaseClass = pg.Qt.loadUiType(ui_file_path) + +class StepSizeWindow(stepsize_TemplateBaseClass): + + stepsize_signal = QtCore.pyqtSignal() #signal to help with pass info back to MainWindow + + def __init__(self): + stepsize_TemplateBaseClass.__init__(self) + + # Create the param window + self.ui = stepsize_WindowTemplate() + self.ui.setupUi(self) + self.ui.done_pushButton.clicked.connect(self.done) + self.setWindowFlag(QtCore.Qt.WindowCloseButtonHint, False) + self.show() + + def done(self): + self.stepsize_signal.emit() + self.close() + +"""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/src/main/python/FLIM_analysis/flim_plot_gui.ui b/src/main/python/FLIM_analysis/flim_plot_gui.ui new file mode 100644 index 0000000..bbfef2a --- /dev/null +++ b/src/main/python/FLIM_analysis/flim_plot_gui.ui @@ -0,0 +1,185 @@ + + + Form + + + + 0 + 0 + 738 + 876 + + + + FLIM Analysis + + + + + + 0 + + + + Analysis + + + + + + + + Load Scan + + + + + + + + + Plot + + + + + + + Save intensities array + + + + + + + Save intensities image + + + + + + + Analyze PSF + + + + + + + + + + 500 + 300 + + + + + + + + + 12 + + + + Histogram Intensity Sums + + + + + + + + 12 + + + + Raw Histogram Data + + + + + + + + 500 + 300 + + + + + + + + + + Plot + + + + + + + Analyze lifetime + + + + + + + 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/src/main/python/FLIM_analysis/step_size_labview_files.ui b/src/main/python/FLIM_analysis/step_size_labview_files.ui new file mode 100644 index 0000000..2078fb7 --- /dev/null +++ b/src/main/python/FLIM_analysis/step_size_labview_files.ui @@ -0,0 +1,61 @@ + + + Form + + + + 0 + 0 + 255 + 75 + + + + Form + + + + + + + 12 + + + + Step Size (um) + + + + + + + Done! + + + + + + + + 12 + + + + 4 + + + 100.000000000000000 + + + 0.100000000000000 + + + 0.100000000000000 + + + + + + + + diff --git a/src/main/python/H5_Pkl/h5_pkl_view.py b/src/main/python/H5_Pkl/h5_pkl_view.py new file mode 100644 index 0000000..7be99a5 --- /dev/null +++ b/src/main/python/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/src/main/python/H5_Pkl/h5_pkl_view_gui.ui b/src/main/python/H5_Pkl/h5_pkl_view_gui.ui new file mode 100644 index 0000000..d147ddc --- /dev/null +++ b/src/main/python/H5_Pkl/h5_pkl_view_gui.ui @@ -0,0 +1,89 @@ + + + MainWindow + + + + 0 + 0 + 856 + 925 + + + + H5/pkl View + + + + + + + Qt::Horizontal + + + + File + + + + + + + + + File: + + + + + + + Browse... + + + + + + + true + + + 0 + + + + Data View + + + + + + Once a file is loaded, data will show up here. + + + + + + + + + + + + + + + + + 0 + 0 + 856 + 21 + + + + + + + + diff --git a/src/main/python/H5_Pkl/h5_tree.py b/src/main/python/H5_Pkl/h5_tree.py new file mode 100644 index 0000000..6d82566 --- /dev/null +++ b/src/main/python/H5_Pkl/h5_tree.py @@ -0,0 +1,100 @@ +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("") + return self.f + + 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/src/main/python/H5_Pkl/h5_view_and_plot.py b/src/main/python/H5_Pkl/h5_view_and_plot.py new file mode 100644 index 0000000..18f3635 --- /dev/null +++ b/src/main/python/H5_Pkl/h5_view_and_plot.py @@ -0,0 +1,141 @@ +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 + + + +class H5ViewPlot(BaseApp): + + name = "h5_view_plot" + + def __init__(self, argv): + pg.setConfigOption('imageAxisOrder', 'row-major') + 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.f = 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: + if self.f['Cube/Info/Cube'].attrs['AcqMode'] == b'Hyperspectral Acquisition': # This works for our PhotonEtc. Hyperspectral Camera output + x_start = int(self.f['Cube/Info/Cube'].attrs['LowerWavelength']) + x_end = int(self.f['Cube/Info/Cube'].attrs['UpperWavelength']) + else: + 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/src/main/python/H5_Pkl/h5_view_and_plot_gui.ui b/src/main/python/H5_Pkl/h5_view_and_plot_gui.ui new file mode 100644 index 0000000..a7bcc01 --- /dev/null +++ b/src/main/python/H5_Pkl/h5_view_and_plot_gui.ui @@ -0,0 +1,280 @@ + + + MainWindow + + + + 0 + 0 + 856 + 925 + + + + H5 View and Plot + + + + + + + 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 + 21 + + + + + + + + PlotWidget + QGraphicsView +
pyqtgraph
+
+ + ImageView + QGraphicsView +
pyqtgraph
+
+
+ + +
diff --git a/src/main/python/H5_Pkl/pkl_tree.py b/src/main/python/H5_Pkl/pkl_tree.py new file mode 100644 index 0000000..e60a85e --- /dev/null +++ b/src/main/python/H5_Pkl/pkl_tree.py @@ -0,0 +1,103 @@ +from ScopeFoundry.data_browser import DataBrowserView +from qtpy import QtWidgets +import h5py +import pickle +import numpy as np +import lmfit + +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) + elif type(value) == lmfit.model.ModelResult: + value = "lmfit.model.ModelResult" + # 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/src/main/python/Image_analysis/Image_analysis.py b/src/main/python/Image_analysis/Image_analysis.py new file mode 100644 index 0000000..50b2d1c --- /dev/null +++ b/src/main/python/Image_analysis/Image_analysis.py @@ -0,0 +1,222 @@ +import sys +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 +from PIL import Image + +# local modules + +pg.mkQApp() + + +base_path = Path(__file__).parent +file_path = (base_path / "image_analysis_gui.ui").resolve() + +uiFile = file_path + +WindowTemplate, TemplateBaseClass = pg.Qt.loadUiType(uiFile) + +def updateDelay(scale, time): + """ Hack fix for scalebar inaccuracy""" + QtCore.QTimer.singleShot(time, scale.updateBar) + +class MainWindow(TemplateBaseClass): + + def __init__(self): + pg.setConfigOption('imageAxisOrder', 'col-major') + super(TemplateBaseClass, self).__init__() + + # Create the main window + self.ui = WindowTemplate() + self.ui.setupUi(self) + + #setup image plot + self.image_plot_layout=pg.GraphicsLayoutWidget() + self.ui.image_groupBox.layout().addWidget(self.image_plot_layout) + self.image_plot = self.image_plot_layout.addPlot() + self.img_item = pg.ImageItem() + self.image_plot.addItem(self.img_item) + self.image_plot_view = self.image_plot.getViewBox() + + #setup lookup table + self.hist_lut = pg.HistogramLUTItem() + self.image_plot_layout.addItem(self.hist_lut) + + #region of interest - allows user to select scan area + self.roi = pg.ROI([0,0],[10, 10], movable=True) + self.roi.addScaleHandle([1, 1], [0, 0]) + self.roi.addRotateHandle([0, 0], [1, 1]) + self.roi.translateSnap = True + self.roi.scaleSnap = True + self.roi.sigRegionChanged.connect(self.line_profile_update_plot) + self.image_plot.addItem(self.roi) + + #setup rgb plot + self.rgb_plot_layout=pg.GraphicsLayoutWidget() + self.ui.rgb_plot_groupBox.layout().addWidget(self.rgb_plot_layout) + self.rgb_plot = self.rgb_plot_layout.addPlot() + + #set up ui signals + self.ui.load_image_pushButton.clicked.connect(self.load_image) + self.ui.custom_pixel_size_checkBox.stateChanged.connect(self.switch_custom_pixel_size) + self.ui.update_settings_pushButton.clicked.connect(self.reload_image) + self.ui.spot_radioButton.toggled.connect(self.update_camera) + + self.update_camera() #initialize camera pixel size + self.update_scaling_factor() #initialize scaling_factor + self.show() + + #row major. invert y false, rotate false + def load_image(self): + """ + Prompts the user to select a text file containing image data. + """ + try: + file = QtWidgets.QFileDialog.getOpenFileName(self, 'Open file', os.getcwd()) + self.original_image = Image.open(file[0]) + self.original_image = self.original_image.rotate(-90, expand=True) #correct image orientation + self.resize_to_scaling_factor(self.original_image) + except Exception as err: + print(format(err)) + + def resize_to_scaling_factor(self, image): + """ + Handles loading of image according to scaling_factor + """ + self.update_scaling_factor() + + if self.ui.spot_radioButton.isChecked() and self.ui.resize_image_checkBox.isChecked(): + image = self.original_image.resize((round(image.size[0]*self.scaling_factor), round(image.size[1]*self.scaling_factor))) + self.image_plot.getAxis("bottom").setScale(scale = 1) + self.image_plot.getAxis("left").setScale(scale = 1) + else: + image = self.original_image + self.image_plot.getAxis("bottom").setScale(scale = self.scaling_factor) + self.image_plot.getAxis("left").setScale(scale = self.scaling_factor) + + if self.ui.greyscale_checkBox.isChecked(): + image = image.convert("L") #convert to greyscale + + self.image_array = np.array(image) + width = self.image_array.shape[0] + height = self.image_array.shape[1] + + try: + #set image bounds with qrect + self.img_item_rect = QtCore.QRectF(0, 0, width, height) + self.img_item.setImage(image=self.image_array) + self.img_item.setRect(self.img_item_rect) + + # if self.ui.greyscale_checkBox.isChecked(): + # self.hist_lut.setImageItem(self.img_item) + + if self.ui.vertical_radioButton.isChecked(): + roi_height = self.scaling_factor * height + self.roi.setSize([width, roi_height]) + elif self.ui.horizontal_radioButton.isChecked(): + roi_height = self.scaling_factor * width + self.roi.setSize([roi_height, height]) + self.roi.setAngle(0) + self.roi.setPos(0, 0) + self.line_profile_update_plot() + except: + pass + + def line_profile_update_plot(self): + """ Handle line profile for intensity sum viewbox """ + self.rgb_plot.clear() + + # Extract image data from ROI + data, coords = self.roi.getArrayRegion(self.image_array, self.img_item, returnMappedCoords=True) + if data is None: + return + + if self.ui.vertical_radioButton.isChecked(): + x_values = coords[0,:,0] + elif self.ui.horizontal_radioButton.isChecked(): + x_values = coords[1,0,:] + + if self.ui.pixera_radioButton.isChecked() or (self.ui.spot_radioButton.isChecked() and not self.ui.resize_image_checkBox.isChecked()): + x_values = x_values * self.scaling_factor + + #calculate average along columns in region + if len(data.shape) <= 2: #if grayscale, average intensities + if self.ui.vertical_radioButton.isChecked(): + avg_to_plot = np.mean(data, axis=-1) + elif self.ui.horizontal_radioButton.isChecked(): + avg_to_plot = np.mean(data, axis=0) + try: + self.rgb_plot.plot(x_values, avg_to_plot) + except: + pass + elif len(data.shape) > 2: #if rgb arrays, plot individual components + r_values = data[:,:,0] + g_values = data[:,:,1] + b_values = data[:,:,2] + if self.ui.vertical_radioButton.isChecked(): + r_avg = np.mean(r_values, axis=-1) #average red values across columns + g_avg = np.mean(g_values, axis=-1) #average green values + b_avg = np.mean(b_values, axis=-1) #average blue values + elif self.ui.horizontal_radioButton.isChecked(): + r_avg = np.mean(r_values, axis=0) + g_avg = np.mean(g_values, axis=0) + b_avg = np.mean(b_values, axis=0) + try: + self.rgb_plot.plot(x_values, r_avg, pen='r') + self.rgb_plot.plot(x_values, g_avg, pen='g') + self.rgb_plot.plot(x_values, b_avg, pen='b') + except Exception as e: + pass + + def update_scaling_factor(self): + """ + Calculate scaling factor + """ + if self.ui.custom_pixel_size_checkBox.isChecked(): + self.camera_pixel_size = self.ui.custom_pixel_size_spinBox.value() + self.scaling_factor = self.camera_pixel_size + else: + self.scaling_factor = self.camera_pixel_size/int(self.ui.magnification_comboBox.currentText()) + self.roi.snapSize = self.scaling_factor #roi snaps to multiples of scaling_factor + + def reload_image(self): + if hasattr(self, "original_image"): + self.resize_to_scaling_factor(self.original_image) #resize image, sets up roi + + def switch_custom_pixel_size(self): + checked = self.ui.custom_pixel_size_checkBox.isChecked() + self.ui.custom_pixel_size_spinBox.setEnabled(checked) + self.ui.magnification_comboBox.setEnabled(not checked) + + def update_camera(self): + if self.ui.spot_radioButton.isChecked(): + self.camera_pixel_size = 7.4 + self.ui.greyscale_checkBox.setChecked(False) + self.ui.resize_image_checkBox.setEnabled(True) + self.update_scaling_factor() + elif self.ui.pixera_radioButton.isChecked(): + self.camera_pixel_size = 3 + self.ui.greyscale_checkBox.setChecked(True) + self.ui.resize_image_checkBox.setEnabled(False) + self.update_scaling_factor() + + 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/src/main/python/Image_analysis/image_analysis_gui.ui b/src/main/python/Image_analysis/image_analysis_gui.ui new file mode 100644 index 0000000..9fcffd9 --- /dev/null +++ b/src/main/python/Image_analysis/image_analysis_gui.ui @@ -0,0 +1,208 @@ + + + MainWindow + + + + 0 + 0 + 1029 + 743 + + + + Image Analysis + + + QTabWidget::Triangular + + + + + + + Settings + + + + + + + + + + + + + Greyscale image + + + + + + + Load image + + + + + + + + 50 + + + + + 75 + + + + + 100 + + + + + 150 + + + + + + + + Magnification + + + + + + + Update settings + + + + + + + false + + + + + + + Custom pixel size (um) + + + + + + + Camera + + + + + + SPOT + + + true + + + + + + + Pixera + + + + + + + Resize image + + + + + + + + + + Direction to average pixels + + + + + + Vertical + + + true + + + + + + + Horizontal + + + + + + + + + + + + + + + + 0 + 0 + + + + + 600 + 0 + + + + Image + + + + + + + + RGB Plot + + + + + + + + + + 0 + 0 + 1029 + 31 + + + + + + + + diff --git a/src/main/python/Lifetime_analysis/Fit_functions.py b/src/main/python/Lifetime_analysis/Fit_functions.py new file mode 100644 index 0000000..7ad5c3e --- /dev/null +++ b/src/main/python/Lifetime_analysis/Fit_functions.py @@ -0,0 +1,132 @@ +# -*- coding: utf-8 -*- +""" +Created on Fri Mar 29 11:21:59 2019 + +@author: Sarthak +""" + +import numpy as np +from scipy.optimize import differential_evolution +from scipy.special import gamma + +def stretch_exp_fit(TRPL, t, Tc = (0,1e5), Beta = (0,1), A = (0,1e6), noise=(0,1e6)): + + def exp_stretch(t, tc, beta, a, noise): + return ((a * np.exp(-((1.0 / tc) * t) ** beta)) + noise) + + def avg_tau_from_exp_stretch(tc, beta): + return (tc / beta) * gamma(1.0 / beta) + + def Diff_Ev_Fit_SE(TRPL): + TRPL = TRPL + + def residuals(params):#params are the parameters to be adjusted by differential evolution or leastsq, interp is the data to compare to the model. + #Variable Rates + tc = params[0] + beta = params[1] + a = params[2] + noise = params[3] + + PL_sim = exp_stretch(t,tc,beta,a, noise) + + Resid= np.sum(((PL_sim-TRPL)**2)/(np.sqrt(TRPL)**2)) + return Resid #returns the difference between the PL data and simulated data + + bounds = [Tc, Beta, A, noise] + + result = differential_evolution(residuals, bounds) + return result.x + + p = Diff_Ev_Fit_SE(TRPL) + + tc = p[0] + beta = p[1] + a = p[2] + noise = p[3] + + PL_fit = exp_stretch(t,tc,beta,a, noise) + + avg_tau = avg_tau_from_exp_stretch(tc,beta) + + return tc, beta, a, avg_tau, PL_fit, noise + +def double_exp_fit(TRPL, t, tau1_bounds=(0,1000), a1_bounds=(0,1e6), tau2_bounds=(0,10000), a2_bounds=(0,1e5), noise=(0,1e6)): + + def single_exp(t, tau, a): + return (a * np.exp(-((1.0 / tau)*t))) + + def double_exp(t, tau1, a1, tau2, a2, noise): + return ((single_exp(t, tau1, a1)) + (single_exp(t, tau2, a2)) + noise) + + def avg_tau_from_double_exp(tau1, a1, tau2, a2): + return (((tau1*a1) + (tau2*a2))/(a1+a2)) + + def Diff_Ev_Fit_DE(TRPL): + TRPL = TRPL + + def residuals(params):#params are the parameters to be adjusted by differential evolution or leastsq, interp is the data to compare to the model. + #Variable Rates + tau1 = params[0] + a1 = params[1] + tau2 = params[2] + a2 = params[3] + noise = params[4] + + PL_sim = double_exp(t,tau1, a1, tau2, a2, noise) + + Resid= np.sum(((PL_sim-TRPL)**2)/(np.sqrt(TRPL)**2)) + return Resid #returns the difference between the PL data and simulated data + + bounds = [tau1_bounds, a1_bounds, tau2_bounds, a2_bounds, noise] + + result = differential_evolution(residuals, bounds) + return result.x + + p = Diff_Ev_Fit_DE(TRPL) + + tau1 = p[0] + a1 = p[1] + tau2 = p[2] + a2 = p[3] + noise = p[4] + + PL_fit = double_exp(t, tau1, a1, tau2, a2, noise) + + avg_tau = avg_tau_from_double_exp(tau1, a1, tau2, a2) + + return tau1, a1, tau2, a2, avg_tau, PL_fit, noise + +def single_exp_fit(TRPL, t, tau_bounds=(0,10000), a_bounds=(0,1e6), noise=(0,1e6)): + + def single_exp(t, tau, a, noise): + return (a * np.exp(-((1.0 / tau)*t) ) + noise) + + def Diff_Ev_Fit_singleExp(TRPL): + TRPL = TRPL + + def residuals(params):#params are the parameters to be adjusted by differential evolution or leastsq, interp is the data to compare to the model. + #Variable Rates + tau = params[0] + a = params[1] + noise = params[2] + + PL_sim = single_exp(t, tau, a, noise) + + Resid= np.sum(((PL_sim-TRPL)**2)/(np.sqrt(TRPL)**2)) + return Resid #returns the difference between the PL data and simulated data + + bounds = [tau_bounds, a_bounds, noise] + + result = differential_evolution(residuals, bounds) + return result.x + + p = Diff_Ev_Fit_singleExp(TRPL) + + tau = p[0] + a = p[1] + noise = p[2] + + PL_fit = single_exp(t, tau, a, noise) + + return tau, a, PL_fit, noise + \ No newline at end of file diff --git a/src/main/python/Lifetime_analysis/Fit_functions_with_irf.py b/src/main/python/Lifetime_analysis/Fit_functions_with_irf.py new file mode 100644 index 0000000..5b454af --- /dev/null +++ b/src/main/python/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 (i + + MainWindow + + + + 0 + 0 + 2620 + 1676 + + + + Lifetime Analysis + + + + + + + + + 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 + + + + + + + 4 + + + 9999999.000000000000000 + + + + + + + 0 + + + + + + + 0 + + + + + + + SRV (cm/s) + + + + + + + 100000.000000000000000 + + + 8000.000000000000000 + + + + + + + Diffusion Coefficient (cm2/s) + + + + + + + Surface Lifetime (ns) + + + + + + + Average Lifetime (ns) + + + + + + + Calculate + + + + + + + SRV (cm/s) + + + + + + + 0 + + + + + + + + 75 + true + + + + SRV1 = SRV2 + + + + + + + + 75 + true + + + + SRV1 = 0 + + + + + + + + + + + + + 2 + 0 + + + + + + + + + 15 + + + + Settings + + + + + + + 12 + + + + Export Settings + + + + + + For Figure: + + + + + + + Export Fit Data + + + + + + + Clear Export Memory + + + + + + + + 12 + 50 + false + + + + Export figure + + + + + + + For Data: + + + + + + + Enter Legend Here... + + + + + + + Add trace to memory + + + + + + + + + + + 12 + + + + Plot Controls + + + + + + + 12 + 50 + false + + + + Plot + + + + + + + Y Axis Log + + + + + + + Plot color + + + + + + + + 12 + false + + + + Clear Plot + + + + + + + + + + + + Normalize + + + + + + + Clear plot everytime + + + + + + + + + + + 12 + + + + + + + + + 12 + + + + Resolution (ns) + + + + + + + false + + + + 12 + + + + true + + + + + + + + 12 + + + + 512 + + + 1 + + + + + + + + 12 + + + + IRF Channel No + + + + + + + + 12 + + + + Data Channel No + + + + + + + + 12 + + + + + + + + + 12 + + + + Load separate IRF file + + + + + + + + 12 + + + + Smooth Data + + + + + + + false + + + + 12 + + + + 1 + + + 1000 + + + + + + + + + + + + 0 + 0 + 2620 + 38 + + + + + File + + + + + + + + + + + + Open + + + + + Exit + + + + + Save + + + + + false + + + Open IRF File + + + + + + PlotWidget + QGraphicsView +
pyqtgraph
+
+
+ + +
diff --git a/src/main/python/Lifetime_analysis/Lifetime_plot_fit.py b/src/main/python/Lifetime_analysis/Lifetime_plot_fit.py new file mode 100644 index 0000000..7967a1e --- /dev/null +++ b/src/main/python/Lifetime_analysis/Lifetime_plot_fit.py @@ -0,0 +1,653 @@ +# -*- coding: utf-8 -*- +""" +Created on Wed Mar 27 16:50:26 2019 + +@author: Sarthak +""" + +# system imports +import sys +import os +from pathlib import Path + +# module imports +import pyqtgraph as pg +from pyqtgraph.Qt import QtCore, QtGui, QtWidgets +import numpy as np +import matplotlib.pyplot as plt + +sys.path.append(os.path.abspath('../Export_Windows')) +try: + from Export_window import ExportPlotWindow +except: + from Export_Windows.Export_window import ExportPlotWindow + +# local module imports +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""" +plt.rc('xtick', labelsize = 20) +plt.rc('xtick.major', pad = 3) +plt.rc('ytick', labelsize = 20) +plt.rc('lines', lw = 2.5, markersize = 7.5) +plt.rc('legend', fontsize = 20) +plt.rc('axes', linewidth=3.5) + +pg.mkQApp() +pg.setConfigOption('background', 'w') +##pg.setConfigOption('crashWarning', True) + +base_path = Path(__file__).parent +file_path = (base_path / "Lifetime_analysis_gui_layout.ui").resolve() + +uiFile = file_path + +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.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.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.export_window)#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.add_to_mem_pushButton.clicked.connect(self.add_trace_to_mem) + self.ui.export_data_pushButton.clicked.connect(self.export_data) + self.ui.clear_export_data_pushButton.clicked.connect(self.clear_export_data) + self.ui.smoothData_checkBox.stateChanged.connect(self.smooth_trace_enabled) + + #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.fit_lifetime_called_w_irf = False + self.fit_lifetime_called_wo_irf = False + self.x_mem = [] # containers for adding x data to memory + self.y_mem = [] # containers for adding y data to memory + self.best_fit_mem = [] # containers for adding best fit data to memory + self.best_fit_mem_x = [] # containers for adding best fit data to memory + self.legend = [] # containers for adding legend to memory + + #variables accounting for data received from FLIM analysis + self.opened_from_flim = False #switched to True in FLIM_plot when "analyze lifetime" clicked + self.hist_data_from_flim = [] #container for flim roi data + + self.show() + + def open_file(self): + """ Open data file """ +# try: + self.filename = QtWidgets.QFileDialog.getOpenFileName(self) + try: + if ".csv" in self.filename[0] or ".txt" in self.filename[0]: #if txt or csv, prompt user to enter # of rows to skip + self.skip_rows_window = SkipRowsWindow() + self.skip_rows_window.skip_rows_signal.connect(self.open_with_skip_rows_window) + self.ui.Res_comboBox.setEnabled(True) + else: + self.file = read_picoharp_phd(self.filename[0]) + self.opened_from_flim = False + except: + pass + + def open_with_skip_rows_window(self): + """ Prompts user to enter how many rows to skip """ + skip_rows = self.skip_rows_window.ui.skip_rows_spinBox.value() + if ".txt" in self.filename[0]: + self.file = np.loadtxt(self.filename[0], skiprows=skip_rows) + elif ".csv" in self.filename[0]: + self.file = np.genfromtxt(self.filename[0], skip_header=skip_rows, delimiter=",") + + def open_irf_file(self): + """ Open file with irf - enabled if 'load separate irf' is checled """ + self.irf_filename = QtWidgets.QFileDialog.getOpenFileName(self) + try: + if ".txt" in self.irf_filename[0] or ".csv" in self.irf_filename[0]: + self.irf_skip_rows_window = SkipRowsWindow() + self.irf_skip_rows_window.skip_rows_signal.connect(self.open_irf_with_skip_rows_window) + self.ui.Res_comboBox.setEnabled(True) + else: + self.irf_file = read_picoharp_phd(self.irf_filename[0]) + except: + pass + + def open_irf_with_skip_rows_window(self): + irf_skip_rows = self.irf_skip_rows_window.ui.skip_rows_spinBox.value() + if ".txt" in self.irf_filename[0]: + self.irf_file = np.loadtxt(self.irf_filename[0], skiprows=irf_skip_rows) + elif ".csv" in self.irf_filename[0]: + self.irf_file = np.genfrontxt(self.irf_filename[0], skip_header=irf_skip_rows, delimiter=",") + + 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 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 smooth_trace_enabled(self): + """Enable smooth spin box when smooth data is checked""" + if self.ui.smoothData_checkBox.isChecked(): + self.ui.smoothData_spinBox.setEnabled(True) + else: + self.ui.smoothData_spinBox.setEnabled(False) + + 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") + """ + if mode == "data": + channel = int(self.ui.Data_channel_spinBox.value()) + elif mode == "irf": + channel = int(self.ui.irf_channel_spinBox.value()) + try: + 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] + + self.resolution = float(self.ui.Res_comboBox.currentText()) + except: + res, y = self.file.get_curve(channel) + time_window = int(np.floor(self.file.get_time_window_in_ns(channel))) + y = y[0:time_window] + self.resolution = res + + length = np.shape(y)[0] + x = np.arange(0, length*self.resolution, self.resolution, np.float) + + if self.ui.smoothData_checkBox.isChecked() and mode=="data": + y = np.convolve(y, np.ones(self.ui.smoothData_spinBox.value())/self.ui.smoothData_spinBox.value(), mode="same") + + if self.ui.normalize_checkBox.isChecked(): + y = y / np.amax(y) + + return x,y + + except Exception as e: + self.ui.Result_textBrowser.setText(str(e)) + + def plot(self): + try: + if self.opened_from_flim: + x, y = self.hist_data_from_flim + else: + x,y = self.acquire_settings() #get data + + self.ui.plot.plot(x, y, clear=self.ui.clear_plot_checkBox.isChecked(), pen=pg.mkPen(self.plot_color)) + self.fit_lifetime_called_w_irf = False + self.fit_lifetime_called_wo_irf = False + + try: + self.ui.Result_textBrowser.setText("Integral Counts :\n" "{:.2E}".format( + self.file.get_integral_counts(int(self.ui.Channel_spinBox.value())))) + except: + self.ui.Result_textBrowser.setText("Integral Counts :\n" "{:.2E}".format(np.sum(y))) + except: + pass + self.ui.plot.setLabel('left', 'Intensity', units='a.u.') + self.ui.plot.setLabel('bottom', 'Time (ns)') + + def make_semilog(self): + """ 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: + if not hasattr(self, "file"): + self.ui.Result_textBrowser.setText("You need to load a data file.") + else: + if self.opened_from_flim: + x, y = self.hist_data_from_flim + else: + 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]):] + x = x[np.asscalar(find_max_int[0]):] + + t = x + 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=self.ui.clear_plot_checkBox.isChecked(), pen=pg.mkPen(self.plot_color)) + + if fit_func == "Stretched Exponential": #stretch exponential tab + tc, beta, a, avg_tau, PL_fit, noise = 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=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)+ + "\nNoise = "+ str(noise)) + self.ui.average_lifetime_spinBox.setValue(avg_tau) + + elif fit_func == "Double Exponential": #double exponential tab + tau1, a1, tau2, a2, avg_tau, PL_fit, noise = 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=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)+ + "\nNoise = "+ str(noise)) + #TODO - once tau_avg implemented, set average lifetime spinbox to tau_avg value + + elif fit_func == "Single Exponential": #single exponential tab + tau, a, PL_fit, noise = 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=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)+ + "\nNoise = "+ str(noise)) + 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.fit_lifetime_called_wo_irf = True + self.fit_lifetime_called_w_irf = False + + self.ui.plot.setLabel('left', 'Intensity', units='a.u.') + self.ui.plot.setLabel('bottom', 'Time (ns)') + return self.out + + except Exception as e: + self.ui.Result_textBrowser.append(format(e)) + + def fit_and_plot_with_irf(self): + """ Fit and plot with IRF """ + try: + self.ui.Result_textBrowser.clear() + if not hasattr(self, "file"): + self.ui.Result_textBrowser.append("You need to load a data file.") + if not hasattr(self, "irf_file") and self.ui.separate_irf_checkBox.isChecked(): + self.ui.Result_textBrowser.append("You need to load an IRF file.") + else: + if self.opened_from_flim: + x,y = self.hist_data_from_flim + else: + 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 + if bestfit_params[3] > bestfit_params[1]: + self.ui.average_lifetime_spinBox.setValue(bestfit_params[3]) + elif bestfit_params[1] > bestfit_params[3]: + self.ui.average_lifetime_spinBox.setValue(bestfit_params[1]) + + 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()) + self.fit_lifetime_called_w_irf = True + self.fit_lifetime_called_wo_irf = False + except Exception as e: + self.ui.Result_textBrowser.append(format(e)) + + 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 + + self.srv1_srv2_equal = self.thickness / (2*((1e-9*self.surface_lifetime) - ((1/self.diffusion_coeffecient)*((self.thickness/np.pi)**2)) )) + self.srv1_zero = self.thickness / ((1e-9*self.surface_lifetime) - ((4/self.diffusion_coeffecient)*((self.thickness/np.pi)**2)) ) + + self.ui.srv1_srv2_equal_label.setText(str(self.srv1_srv2_equal)) + self.ui.srv1_zero_label.setText(str(self.srv1_zero)) + + 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()) + srv_string += "\nSurface Lifetime (ns): " + self.ui.surface_lifetime_label.text() + + srv_string += "\nSRV1 = SRV2"\ + + "\nSRV (cm/s): " + self.ui.srv1_srv2_equal_label.text() + + srv_string += "\nSRV1 = 0"\ + + "\nSRV (cm/s): " + self.ui.srv1_zero_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 clear_export_data(self): + self.data_list = [] + self.clean_up_after_fig_export() + + def clean_up_after_fig_export(self): + self.x_mem = [] + self.y_mem = [] + self.legend = [] + self.best_fit_mem = [] + self.best_fit_mem_x = [] + + def add_trace_to_mem(self): + try: + if self.fit_lifetime_called_w_irf == True: + self.x_mem.append(self.out[:,0]) + self.y_mem.append(self.out[:,1]) + self.best_fit_mem_x.append(self.out[:,0]) + self.best_fit_mem.append(self.out[:,2]) + elif self.fit_lifetime_called_wo_irf == True: + self.x_mem.append(self.acquire_settings()[0]) + self.y_mem.append(self.acquire_settings()[1]) + self.best_fit_mem_x.append(self.out[:,0]) + self.best_fit_mem.append(self.out[:,2]) + else: + self.x_mem.append(self.acquire_settings()[0]) + self.y_mem.append(self.acquire_settings()[1]) + self.legend.append(self.ui.lineEdit.text()) + except Exception as e: + print(e) + + def export_window(self): + self.exportplotwindow = ExportPlotWindow() + self.exportplotwindow.export_fig_signal.connect(self.pub_ready_plot_export) + + def pub_ready_plot_export(self): + try: + if self.x_mem == []: + self.ui.result_textBrowser.setText("Add traces to memory first!") + + else: + filename = QtWidgets.QFileDialog.getSaveFileName(self,caption="Filename with EXTENSION") + + plt.figure(figsize=(8,6)) + plt.tick_params(direction='out', length=8, width=3.5) + for i in range(len(self.x_mem)): + plt.plot(self.x_mem[i], self.y_mem[i], label=str(self.legend[i])) + if self.fit_lifetime_called_w_irf == True or self.fit_lifetime_called_wo_irf == True: + plt.plot(self.best_fit_mem_x[i], self.best_fit_mem[i],'k--') + + plt.yscale('log') + plt.xlabel("Time (ns)", fontsize=20, fontweight='bold') + plt.ylabel("Intensity (norm.)", fontsize=20, fontweight='bold') + plt.legend() + plt.tight_layout() + plt.xlim([self.exportplotwindow.ui.lowerX_spinBox.value(),self.exportplotwindow.ui.upperX_spinBox.value()]) + plt.ylim([self.exportplotwindow.ui.lowerY_spinBox.value(),self.exportplotwindow.ui.upperY_doubleSpinBox.value()]) + + plt.savefig(filename[0],bbox_inches='tight', dpi=300) + plt.close() + self.clean_up_after_fig_export() + + except Exception as e: + self.ui.Result_textBrowser.append(format(e)) + pass + + 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 + +"""Skip rows GUI""" +ui_file_path = (base_path / "skip_rows.ui").resolve() +skiprows_WindowTemplate, skiprows_TemplateBaseClass = pg.Qt.loadUiType(ui_file_path) + +class SkipRowsWindow(skiprows_TemplateBaseClass): + + skip_rows_signal = QtCore.pyqtSignal() #signal to help with pass info back to MainWindow + + def __init__(self): + skiprows_TemplateBaseClass.__init__(self) + + # Create the param window + self.ui = skiprows_WindowTemplate() + self.ui.setupUi(self) + self.ui.done_pushButton.clicked.connect(self.done) + self.setWindowFlag(QtCore.Qt.WindowCloseButtonHint, False) + self.show() + + def done(self): + self.skip_rows_signal.emit() + self.close() + + +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/src/main/python/Lifetime_analysis/__init__.py b/src/main/python/Lifetime_analysis/__init__.py new file mode 100644 index 0000000..b4011af --- /dev/null +++ b/src/main/python/Lifetime_analysis/__init__.py @@ -0,0 +1 @@ +from __future__ import absolute_import, division, print_function \ No newline at end of file diff --git a/src/main/python/Lifetime_analysis/picoharp_phd.py b/src/main/python/Lifetime_analysis/picoharp_phd.py new file mode 100644 index 0000000..455e1bc --- /dev/null +++ b/src/main/python/Lifetime_analysis/picoharp_phd.py @@ -0,0 +1,408 @@ +# -*- coding: utf-8 -*- +""" +Majority of this PicoHarp Parser was written by skyjur. +Check out the original code here --- +https://github.com/skyjur/picoharp300-curvefit-ui + +Modified by Sarthak --- +Created on Fri Apr 5 15:50:36 2019 + +@author: Sarthak +""" + +""" +PicoHarp 300 file parser +""" +import datetime +import numpy as np +from ctypes import c_uint, c_ulong, c_char, c_int, c_int64, c_float, \ + Structure, sizeof, memmove, addressof + +DISPCURVES = 8 +MAXCURVES = 512 +MAXCHANNELS = 65536 + + +class tParamStruct(Structure): + _pack_ = 4 + _fields_ = [ + ('Start', c_float), + ('Step', c_float), + ('End', c_float), + ] + + +class tCurveMapping(Structure): + _pack_ = 4 + _fields_ = [ + ('MapTo', c_int), + ('Show', c_int), + ] + + +class TxtHdr(Structure): + _pack_ = 4 + _fields_ = [ + ('Ident', c_char * 16), + ('FormatVersion', c_char * 6), + ('CreatorName', c_char * 18), + ('CreatorVersion', c_char * 12), + ('FileTime', c_char * 18), + ('CRLF', c_char * 2), + ('CommentField', c_char * 256), + ] + + +class BinHdr(Structure): + _pack_ = 4 + _fields_ = [ + ('Curves', c_int), + ('BitsPerHistoBin', c_int), + ('RoutingChannels', c_int), + ('NumberOfBoards', c_int), + ('ActiveCurve', c_int), + ('MeasMode', c_int), + ('SubMode', c_int), + ('RangeNo', c_int), + ('Offset', c_int), + ('Tacq', c_int), # in m + ('StopAt', c_int), + ('StopOnOvfl', c_int), + ('Restart', c_int), + ('DispLinLog', c_int), + ('DispTimeFrom', c_int), + ('DispTimeTo', c_int), + ('DispCountsFrom', c_int), + ('DispCountsTo', c_int), + ('DispCurves', tCurveMapping * DISPCURVES), + ('Params', tParamStruct * 3), + ('RepeatMode', c_int), + ('RepeatsPerCurve', c_int), + ('RepeatTime', c_int), + ('RepeatWaitTime', c_int), + ('ScriptName', c_char * 20), + ] + + +class BoardHdr(Structure): + _pack_ = 4 + _fields_ = [ + ('HardwareIdent', c_char * 16), + ('HardwareVersion', c_char * 8), + ('HardwareSerial', c_int), + ('SyncDivider', c_int), + ('CFDZeroCross0', c_int), + ('CFDLevel0', c_int), + ('CFDZeroCross1', c_int), + ('CFDLevel1', c_int), + ('Resolution', c_float), + ('RouterModelCode', c_int), + ('RouterEnabled', c_int), + ('RtChan1_InputType;', c_int), + ('RtChan1_InputLevel', c_int), + ('RtChan1_InputEdge', c_int), + ('RtChan1_CFDPresent', c_int), + ('RtChan1_CFDLevel', c_int), + ('RtChan1_CFDZeroCross', c_int), + ('RtChan2_InputType;', c_int), + ('RtChan2_InputLevel', c_int), + ('RtChan2_InputEdge', c_int), + ('RtChan2_CFDPresent', c_int), + ('RtChan2_CFDLevel', c_int), + ('RtChan2_CFDZeroCross', c_int), + ('RtChan3_InputType;', c_int), + ('RtChan3_InputLevel', c_int), + ('RtChan3_InputEdge', c_int), + ('RtChan3_CFDPresent', c_int), + ('RtChan3_CFDLevel', c_int), + ('RtChan3_CFDZeroCross', c_int), + ('RtChan4_InputType;', c_int), + ('RtChan4_InputLevel', c_int), + ('RtChan4_InputEdge', c_int), + ('RtChan4_CFDPresent', c_int), + ('RtChan4_CFDLevel', c_int), + ('RtChan4_CFDZeroCross', c_int), + ] + + +class CurveHdr(Structure): + _pack_ = 4 + _fields_ = [ + ('CurveIndex', c_int), + ('TimeOfRecording', c_ulong), + ('HardwareIdent', c_char * 16), + ('HardwareVersion', c_char * 8), + ('HardwareSerial', c_int), + ('SyncDivider', c_int), + ('CFDZeroCross0', c_int), + ('CFDLevel0', c_int), + ('CFDZeroCross1', c_int), + ('CFDLevel1', c_int), + ('Offset', c_int), + ('RoutingChannel', c_int), + ('ExtDevices', c_int), + ('MeasMode', c_int), + ('SubMode', c_int), + ('P1', c_float), + ('P2', c_float), + ('P3', c_float), + ('RangeNo', c_int), + ('Resolution', c_float), + ('Channels', c_int), + ('Tacq', c_int), + ('StopAfter', c_int), + ('StopReason', c_int), + ('InpRate0', c_int), + ('InpRate1', c_int), + ('HistCountRate', c_int), + ('IntegralCount', c_int64), + ('reserved', c_int), + ('DataOffset', c_int), + ('RouterModelCode', c_int), + ('RouterEnabled', c_int), + ('RtChan_InputType;', c_int), + ('RtChan_InputLevel', c_int), + ('RtChan_InputEdge', c_int), + ('RtChan_CFDPresent', c_int), + ('RtChan_CFDLevel', c_int), + ('RtChan_CFDZeroCross', c_int), + ] + + +class ParseError(Exception): pass + + +def _read(f, CType): + data = f.read(sizeof(CType)) + obj = CType() + memmove(addressof(obj), data, len(data)) + return obj + + +def _validate_header(header): + if not header.Ident == 'PicoHarp 300' or not header.FormatVersion == '2.0': + raise ParseError('Does not look like a PicoHarp 300 file.') + + +class Curve(object): + res = None + data = None + + def __repr__(self): + return 'Curve' % ( + self.res, + len(self.data) + ) + + +def timefmt(t): + d = datetime.datetime.fromtimestamp(t) + return d.strftime('%a %b %d %H:%M:%S %Y') + + +class PicoharpParser(object): + _ready = False + + def __init__(self, filename): + if isinstance(filename, (str)):#, unicode)): + filename = open(filename, mode='rb') + self.f = filename + self._prepare() + + def _prepare(self): + self.f.seek(0) + + header = self._header = _read(self.f, TxtHdr) + """SJ commented this --- it was giving ParseError""" +# _validate_header(header) + + bin_header = self._bin_header = _read(self.f, BinHdr) + + self._boards = [] + for i in range(bin_header.NumberOfBoards): + self._boards.append(_read(self.f, BoardHdr)) + + self._curves = [] + for i in range(bin_header.Curves): + self._curves.append(_read(self.f, CurveHdr)) + + def header(self): + return [(k, getattr(self._header, k)) for k, t in self._header._fields_] + + def no_of_curves(self): + return self._bin_header.Curves + + def get_curve(self, n): + header = self._curves[n] + res = header.Resolution + + self.f.seek(header.DataOffset) + array = np.fromfile(self.f, c_uint, header.Channels) + + return res, array + + def get_time_window_in_ns(self, curve_no): + curve = self._curves[curve_no] + rep_rate = curve.InpRate0 + res, _ = self.get_curve(curve_no) + time_window_s = (1/rep_rate)/res # in seconds + + return time_window_s * 1e9 # in nannoseconds + + def get_integral_counts(self, curve_no): + curve = self._curves[curve_no] + integral_counts = curve.IntegralCount + + return integral_counts + + def info(self): + txthdr = self._header + binhdr = self._bin_header + boards = self._boards + curves = self._curves + r = [] + w = r.append + yesno = lambda x: 'true' if x else 'false' + + w("Ident : %s" % txthdr.Ident) + w("Format Version : %s" % txthdr.FormatVersion) + w("Creator Name : %s" % txthdr.CreatorName) + w("Creator Version : %s" % txthdr.CreatorVersion) + w("Time of Creation : %s" % txthdr.FileTime) + w("File Comment : %s" % txthdr.CommentField) + + w("No of Curves : %s" % binhdr.Curves) + w("Bits per HistoBin: %s" % binhdr.BitsPerHistoBin) + w("RoutingChannels : %s" % binhdr.RoutingChannels) + w("No of Boards : %s" % binhdr.NumberOfBoards) + w("Active Curve : %s" % binhdr.ActiveCurve) + w("Measurement Mode : %s" % binhdr.MeasMode) + w("Sub-Mode : %s" % binhdr.SubMode) + w("Range No : %s" % binhdr.RangeNo) + w("Offset : %s" % binhdr.Offset) + w("AcquisitionTime : %s" % binhdr.Tacq) + w("Stop at : %s" % binhdr.StopAt) + w("Stop on Ovfl. : %s" % binhdr.StopOnOvfl) + w("Restart : %s" % binhdr.Restart) + w("DispLinLog : %s" % binhdr.DispLinLog) + w("DispTimeAxisFrom : %s" % binhdr.DispTimeFrom) + w("DispTimeAxisTo : %s" % binhdr.DispTimeTo) + w("DispCountAxisFrom: %s" % binhdr.DispCountsFrom) + w("DispCountAxisTo : %s" % binhdr.DispCountsTo) + + for i in range(DISPCURVES): + w("---------------------") + w("Curve No %s" % i) + w(" MapTo : %s" % binhdr.DispCurves[i].MapTo) + w(" Show : %s" % yesno(binhdr.DispCurves[i].Show)) + w("---------------------") + + for i in range(3): + w("---------------------") + w("Parameter No %s" % i) + w(" Start : %f" % binhdr.Params[i].Start) + w(" Step : %f" % binhdr.Params[i].Step) + w(" End : %f" % binhdr.Params[i].End) + w("---------------------") + + w("Repeat Mode : %d" % binhdr.RepeatMode) + w("Repeats per Curve: %d" % binhdr.RepeatsPerCurve) + w("Repeat Time : %d" % binhdr.RepeatTime) + w("Repeat wait Time : %d" % binhdr.RepeatWaitTime) + w("Script Name : %s" % binhdr.ScriptName) + + for i, board in enumerate(boards): + w("---------------------") + w("Board No %d" % i) + w(" HardwareIdent : %s" % board.HardwareIdent) + w(" HardwareVersion : %s" % board.HardwareVersion) + w(" HardwareSerial : %d" % board.HardwareSerial) + w(" SyncDivider : %d" % board.SyncDivider) + w(" CFDZeroCross0 : %d" % board.CFDZeroCross0) + w(" CFDLevel0 : %d" % board.CFDLevel0) + w(" CFDZeroCross1 : %d" % board.CFDZeroCross1) + w(" CFDLevel1 : %d" % board.CFDLevel1) + w(" Resolution : %.6f" % board.Resolution) + + if board.RouterModelCode: + w(" RouterModelCode : %d" % board.RouterModelCode) + w(" RouterEnabled : %d" % board.RouterEnabled) + + w(" RtChan1_InputType : %d" % board.RtChan1_InputType) + w(" RtChan1_InputLevel : %d" % board.RtChan1_InputLevel) + w(" RtChan1_InputEdge : %d" % board.RtChan1_InputEdge) + w(" RtChan1_CFDPresent : %d" % board.RtChan1_CFDPresent) + w(" RtChan1_CFDLevel : %d" % board.RtChan1_CFDLevel) + w(" RtChan1_CFDZeroCross : %d" % board.RtChan1_CFDZeroCross) + + w(" RtChan2_InputType : %d" % board.RtChan2_InputType) + w(" RtChan2_InputLevel : %d" % board.RtChan2_InputLevel) + w(" RtChan2_InputEdge : %d" % board.RtChan2_InputEdge) + w(" RtChan2_CFDPresent : %d" % board.RtChan2_CFDPresent) + w(" RtChan2_CFDLevel : %d" % board.RtChan2_CFDLevel) + w(" RtChan2_CFDZeroCross : %d" % board.RtChan2_CFDZeroCross) + + w(" RtChan3_InputType : %d" % board.RtChan3_InputType) + w(" RtChan3_InputLevel : %d" % board.RtChan3_InputLevel) + w(" RtChan3_InputEdge : %d" % board.RtChan3_InputEdge) + w(" RtChan3_CFDPresent : %d" % board.RtChan3_CFDPresent) + w(" RtChan3_CFDLevel : %d" % board.RtChan3_CFDLevel) + w(" RtChan3_CFDZeroCross : %d" % board.RtChan3_CFDZeroCross) + + w(" RtChan4_InputType : %d" % board.RtChan4_InputType) + w(" RtChan4_InputLevel : %d" % board.RtChan4_InputLevel) + w(" RtChan4_InputEdge : %d" % board.RtChan4_InputEdge) + w(" RtChan4_CFDPresent : %d" % board.RtChan4_CFDPresent) + w(" RtChan4_CFDLevel : %d" % board.RtChan4_CFDLevel) + w(" RtChan4_CFDZeroCross : %d" % board.RtChan4_CFDZeroCross) + + w("---------------------") + + for i, curve in enumerate(curves): + w("---------------------") + w("Curve Index : %d" % curve.CurveIndex) + w("Time of Recording : %s" % timefmt(curve.TimeOfRecording)) + w("HardwareIdent : %s" % curve.HardwareIdent) + w("HardwareVersion : %s" % curve.HardwareVersion) + w("HardwareSerial : %d" % curve.HardwareSerial) + w("SyncDivider : %d" % curve.SyncDivider) + w("CFDZeroCross0 : %d" % curve.CFDZeroCross0) + w("CFDLevel0 : %d" % curve.CFDLevel0 ) + w("CFDZeroCross1 : %d" % curve.CFDZeroCross1) + w("CFDLevel1 : %d" % curve.CFDLevel1) + w("Offset : %d" % curve.Offset) + w("RoutingChannel : %d" % curve.RoutingChannel) + w("ExtDevices : %d" % curve.ExtDevices) + w("Meas. Mode : %d" % curve.MeasMode) + w("Sub-Mode : %d" % curve.SubMode) + w("Par. 1 : %f" % curve.P1) + w("Par. 2 : %.6f" % curve.P2) + w("Par. 3 : %.6f" % curve.P3) + w("Range No : %d" % curve.RangeNo) + w("Resolution : %f" % curve.Resolution) + w("Channels : %d" % curve.Channels) + w("Acq. Time : %d" % curve.Tacq) + w("Stop after : %d" % curve.StopAfter) + w("Stop Reason : %d" % curve.StopReason) + w("InpRate0 : %d" % curve.InpRate0) + w("InpRate1 : %d" % curve.InpRate1) + w("HistCountRate : %d" % curve.HistCountRate) + w("IntegralCount : %d" % curve.IntegralCount) + w("reserved : %d" % curve.reserved) + w("dataoffset : %d" % curve.DataOffset) + + if curve.RouterModelCode: + w("RouterModelCode : %d" % curve.RouterModelCode) + w("RouterEnabled : %d" % curve.RouterEnabled) + w("RtChan_InputType : %d" % curve.RtChan_InputType) + w("RtChan_InputLevel : %d" % curve.RtChan_InputLevel) + w("RtChan_InputEdge : %d" % curve.RtChan_InputEdge) + w("RtChan_CFDPresent : %d" % curve.RtChan_CFDPresent) + w("RtChan_CFDLevel : %d" % curve.RtChan_CFDLevel) + w("RtChan_CFDZeroCross : %d" % curve.RtChan_CFDZeroCross) + + return '\n'.join(r) + +def read_picoharp_phd(datafile): + parser = PicoharpParser(datafile) + return parser \ No newline at end of file diff --git a/src/main/python/Lifetime_analysis/read_ph_phd.py b/src/main/python/Lifetime_analysis/read_ph_phd.py new file mode 100644 index 0000000..1d35203 --- /dev/null +++ b/src/main/python/Lifetime_analysis/read_ph_phd.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +""" +Created on Fri Apr 5 15:52:05 2019 + +@author: Sarthak +""" + +try: + from Lifetime_analysis import picoharp_phd +except: + import picoharp_phd +import numpy as np +import matplotlib.pyplot as plt +#import sys + +#datafile = "E:/Python code/APTES_APTMS.phd" + + +def read_picoharp_phd(datafile): + parser = picoharp_phd.PicoharpParser(datafile) + return parser + +#def phd_to_csv(datafile, return_df = False): +# parser = read_picoharp_phd(datafile) +# name, ext = datafile.rsplit('.', 1) +# +# total_curves = parser.no_of_curves() +# y = [] +# for i in range(total_curves): +# res, curve = parser.get_curve(i) +# time_window = int(np.floor(parser.get_time_window_in_ns(curve_no))) +# curve = curve[0:time_window] +# y.append(curve) +# +# df = pd.DataFrame(y) +# df.T.to_csv(name+".csv", index=False, header=False) +# if return_df == True: +# return df.T + +def smooth(curve, boxwidth): + sm_curve = np.convolve(curve, np.ones(boxwidth)/boxwidth, mode="same") + return sm_curve + +def get_x_y(curve_no, parser, smooth_trace = False, boxwidth = 3): + + assert type(parser) == picoharp_phd.PicoharpParser, 'must be picoharp parser' + res, curve = parser.get_curve(curve_no) + time_window = int(np.floor(parser.get_time_window_in_ns(curve_no))) + curve = curve[0:time_window] + size = len(curve) + x = np.arange(0, size*res, res, np.float) + if smooth_trace == True: + curve = smooth(curve, boxwidth=boxwidth) + return x,curve diff --git a/src/main/python/Lifetime_analysis/skip_rows.ui b/src/main/python/Lifetime_analysis/skip_rows.ui new file mode 100644 index 0000000..35a4e6b --- /dev/null +++ b/src/main/python/Lifetime_analysis/skip_rows.ui @@ -0,0 +1,42 @@ + + + Form + + + + 0 + 0 + 413 + 94 + + + + Enter rows to skip + + + + + + Rows to skip + + + + + + + 10 + + + + + + + Done + + + + + + + + diff --git a/src/main/python/OceanOptics_acquire/OO_PZstageScan_acquire_gui.ui b/src/main/python/OceanOptics_acquire/OO_PZstageScan_acquire_gui.ui new file mode 100644 index 0000000..77dacb8 --- /dev/null +++ b/src/main/python/OceanOptics_acquire/OO_PZstageScan_acquire_gui.ui @@ -0,0 +1,750 @@ + + + MainWindow + + + + 0 + 0 + 1219 + 901 + + + + MainWindow + + + + + + + + + + 15 + + + + Settings + + + + + + Stage Settings for Scan + + + + + + + 12 + + + + X Scan Size (um) + + + + + + + + 12 + + + + 5.000000000000000 + + + + + + + + 12 + + + + Initialize Piezo Stage + + + + + + + + 12 + + + + 0.100000000000000 + + + + + + + + 12 + + + + 5.000000000000000 + + + + + + + + 12 + + + + X Step (um) + + + + + + + + 12 + + + + 100.000000000000000 + + + 50.000000000000000 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 12 + + + + Y Scan Size (um) + + + + + + + + 12 + + + + X Start (um) + + + + + + + + 12 + + + + 1.000000000000000 + + + 0.100000000000000 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 12 + + + + Y Start (um) + + + + + + + + 12 + + + + Y Step (um) + + + + + + + + 12 + + + + Start X-Y Scan + + + + + + + + 12 + + + + Estimate Scan Time + + + + + + + + 12 + + + + 100.000000000000000 + + + 50.000000000000000 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + 15 + + + + OceanOptics Save Settings + + + + + + + 12 + + + + Save Every Spectrum + + + + + + + + 12 + + + + Save Single Spectrum + + + + + + + + + + Spectrometer Settings + + + + + + + 12 + + + + 1 + + + + + + + + 12 + + + + 3 + + + 3000 + + + 100 + + + + + + + + 12 + + + + Scans to Average + + + + + + + + 12 + + + + Intg. Time (ms) + + + + + + + + 12 + + + + Correct Dark Counts + + + true + + + + + + + + 12 + + + + Connect to Spectrometer + + + + + + + + 12 + + + + Live + + + + + + + + + + Scan Save Settings + + + + + + + 12 + + + + + + + + + 12 + + + + Sample Name: + + + + + + + + 12 + + + + Path to Folder + + + + + + + + + + + + + + + + + + 15 + + + + Independent Stage Movements + + + + + + + 12 + + + + Where is the stage? + + + + + + + + 12 + + + + Show Current Position + + + + + + + + 12 + + + + Center the Piezo Stage: + + + + + + + + 12 + + + + Center Stage + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 12 + + + + Absolute Movements + + + + + + + + 12 + + + + X (um) + + + + + + + + 12 + + + + + + + + + 12 + + + + Y (um) + + + + + + + + 12 + + + + + + + + + 12 + + + + OK + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 12 + + + + Relative Movements + + + + + + + + 12 + + + + X (um) + + + + + + + + 12 + + + + + + + + + 12 + + + + Y (um) + + + + + + + + 12 + + + + + + + + + 12 + + + + Ok + + + + + + + + + + + + + + 12 + + + + + + + + + 15 + + + + Information: + + + + + + + + 15 + + + + Scan Progress: + + + + + + + + 12 + + + + 0 + + + + + + + + + + + 0 + 0 + 1219 + 21 + + + + + + + + PlotWidget + QGraphicsView +
pyqtgraph
+
+
+ + +
diff --git a/src/main/python/OceanOptics_acquire/OO_acquire_gui.ui b/src/main/python/OceanOptics_acquire/OO_acquire_gui.ui new file mode 100644 index 0000000..33c8b19 --- /dev/null +++ b/src/main/python/OceanOptics_acquire/OO_acquire_gui.ui @@ -0,0 +1,240 @@ + + + MainWindow + + + + 0 + 0 + 1206 + 668 + + + + MainWindow + + + + + + + + + + 15 + + + + Acquire Settings + + + + + + + 15 + + + + Save Settings + + + + + + + 12 + + + + Configure Folder to Save + + + + + + + + 12 + + + + Save Every Spectrum + + + + + + + + 12 + + + + Save Single Spectrum + + + + + + + + + + + 12 + + + + Correct Dark Counts + + + true + + + + + + + + 15 + + + + Close Connection + + + + + + + + 12 + + + + + + + + + 15 + + + + ALWAYS CLOSE CONNECTION!!! +BEFORE EXITING APP + + + + + + + + 12 + + + + Intg. Time (ms) + + + + + + + + 12 + + + + Scans to Average + + + + + + + + 10 + + + + 3 + + + 3000 + + + + + + + + 10 + + + + 1 + + + + + + + + 15 + + + + Live + + + + + + + + 12 + + + + Connect to Spectrometer + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 1206 + 21 + + + + + + + + PlotWidget + QGraphicsView +
pyqtgraph
+
+
+ + +
diff --git a/src/main/python/OceanOptics_acquire/OceanOptics_acquire_plot.py b/src/main/python/OceanOptics_acquire/OceanOptics_acquire_plot.py new file mode 100644 index 0000000..8de85c0 --- /dev/null +++ b/src/main/python/OceanOptics_acquire/OceanOptics_acquire_plot.py @@ -0,0 +1,297 @@ +# -*- coding: utf-8 -*- +""" +Created on Wed Mar 27 16:50:26 2019 + +@author: Sarthak +""" + +import pyqtgraph as pg +from pyqtgraph.Qt import QtCore, QtGui, QtWidgets#, QColorDialog +import numpy as np +import pickle +import sys +import seabreeze.spectrometers as sb +from pipython import GCSDevice +import time + +pg.mkQApp() +pg.setConfigOption('background', 'w') + +#uiFile = "OO_acquire_gui.ui" "OO_PZstageScan_acquire_gui" +uiFile = "OO_PZstageScan_acquire_gui.ui" + +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.connect_checkBox.stateChanged.connect(self._handle_spectrometer_connection) + self.ui.live_pushButton.clicked.connect(self.live) + + self.ui.path_to_folder_pushButton.clicked.connect(self.save_file_location) + self.ui.save_single_spec_pushButton.clicked.connect(self.save_single_spec) + + self.ui.init_stage_pushButton.clicked.connect(self.init_piezo_stage) + self.ui.show_curr_pos_pushButton.clicked.connect(self.current_piezo_stage_pos) + self.ui.center_stage_pushButton.clicked.connect(self.center_piezo) + self.ui.abs_mov_pushButton.clicked.connect(self.abs_mov) + self.ui.rel_mov_pushButton.clicked.connect(self.rel_mov) + self.ui.estimate_scan_time_pushButton.clicked.connect(self.estimate_scan_time) + self.ui.start_x_y_sacan_pushButton.clicked.connect(self.x_y_scan) + +# self.ui.clear_pushButton.clicked.connect(self.clear_plot) + self.pi_device = None + self.spec = None + + self.ui.status_textBrowser.setText("Welcome!\nGUI Initiated!") + + self.show() + + def _handle_spectrometer_connection(self): + if self.ui.connect_checkBox.isChecked(): + self.connect_spectrometer() + else: + self.close_connection() + + def connect_spectrometer(self): + if self.spec is None: + devices = sb.list_devices() + self.spec = sb.Spectrometer(devices[0]) + self.ui.status_textBrowser.append("Ocean Optics Device Connected!\n\n Device:\n\n"+str(sb.list_devices()[0])) + else: + self.ui.status_textBrowser.append("Already Connected") + + def init_piezo_stage(self): + if self.pi_device is None: + self.pi_device = GCSDevice("E-710") # Creates a Controller instant + self.pi_device.ConnectNIgpib(board=0,device=4) # Connect to GPIB board + self.ui.status_textBrowser.append('Connected: {}'.format(self.pi_device.qIDN().strip())) + # print('connected: {}'.format(self.pi_device.qIDN().strip())) + + self.axes = self.pi_device.axes[0:2] # selecting x and y axis of the stage + + self.pi_device.INI() + self.pi_device.REF(axes=self.axes) + + self.pi_device.SVO(axes=self.axes, values=[True,True]) # Turn on servo control for both axes + self.ui.status_textBrowser.append("Current Stage Position:\n{}".format(self.pi_device.qPOS(axes=self.axes))) + # print(self.pi_device.qPOS(axes=self.axes)) + else: + self.ui.status_textBrowser.append("Piezo Stage Is Already Initialized!") + + def center_piezo(self): + if self.pi_device is None: + self.init_piezo_stage() + self.pi_device.MOV(axes=self.axes, values=[50,50]) + self.ui.status_textBrowser.append("Piezo Stage Centered: [50x,50y]") + + def current_piezo_stage_pos(self): + if self.pi_device is None: + self.init_piezo_stage() + self.ui.status_textBrowser.append("Current Stage Position:\n{}".format(self.pi_device.qPOS(axes=self.axes))) + + def abs_mov(self): + if self.pi_device is None: + self.init_piezo_stage() + x_abs_pos = self.ui.x_abs_doubleSpinBox.value() + y_abs_pos = self.ui.y_abs_doubleSpinBox.value() + self.pi_device.MOV(axes=self.axes, values=[x_abs_pos,y_abs_pos]) + + def rel_mov(self): + if self.pi_device is None: + self.init_piezo_stage() + x_rel_pos = self.ui.x_rel_doubleSpinBox.value() + y_rel_pos = self.ui.y_rel_doubleSpinBox.value() + self.pi_device.MVR(axes=self.axes, values=[x_rel_pos,y_rel_pos]) + + def estimate_scan_time(self): + x_scan_size = self.ui.x_size_doubleSpinBox.value() + y_scan_size = self.ui.y_size_doubleSpinBox.value() + + x_step = self.ui.x_step_doubleSpinBox.value() + y_step = self.ui.y_step_doubleSpinBox.value() + + if y_scan_size == 0: + y_scan_size = 1 + + if x_scan_size == 0: + x_scan_size = 1 + + if y_step == 0: + y_step = 1 + + if x_step == 0: + x_step = 1 + + y_range = int(np.ceil(y_scan_size/y_step)) + x_range = int(np.ceil(x_scan_size/x_step)) + + total_points = x_range*y_range + + intg_time_ms = self.ui.intg_time_spinBox.value() + scans_to_avg = self.ui.scan_to_avg_spinBox.value() + + total_time = total_points*(intg_time_ms*1e-3)*(scans_to_avg) # in seconds + + self.ui.status_textBrowser.append("Estimated scan time: "+str(np.float16(total_time/60))+" mins") + + def x_y_scan(self): + if self.pi_device is None: + self.init_piezo_stage() + + if self.spec is None: + self.ui.status_textBrowser.append("Spectrometer not connected!\nForce connecting the spectrometer...") + self.connect_spectrometer() + + start_time = time.time() + x_start = self.ui.x_start_doubleSpinBox.value() + y_start = self.ui.y_start_doubleSpinBox.value() + + x_scan_size = self.ui.x_size_doubleSpinBox.value() + y_scan_size = self.ui.y_size_doubleSpinBox.value() + + x_step = self.ui.x_step_doubleSpinBox.value() + y_step = self.ui.y_step_doubleSpinBox.value() + + if y_scan_size == 0: + y_scan_size = 1 + + if x_scan_size == 0: + x_scan_size = 1 + + if y_step == 0: + y_step = 1 + + if x_step == 0: + x_step = 1 + + y_range = int(np.ceil(y_scan_size/y_step)) + x_range = int(np.ceil(x_scan_size/x_step)) + + # Define empty array for saving intensities + data_array = np.zeros(shape=(x_range*y_range,2048)) + + self.ui.status_textBrowser.append("Starting Scan...") + # Move to the starting position + self.pi_device.MOV(axes=self.axes, values=[x_start,y_start]) + + self.ui.status_textBrowser.append("Scan in Progress...") + + k = 0 + for i in range(y_range): + for j in range(x_range): +# print(self.pi_device.qPOS(axes=self.axes)) + + self._read_spectrometer() + data_array[k,:] = self.y + self.ui.plot.plot(self.spec.wavelengths(), self.y, pen='r', clear=True) + pg.QtGui.QApplication.processEvents() + + self.pi_device.MVR(axes=self.axes[0], values=[x_step]) + + self.ui.progressBar.setValue(100*((k+1)/(x_range*y_range))) + k+=1 + # TODO + # if statement needs to be modified to keep the stage at the finish y-pos for line scans in x, and same for y + if i == y_range-1: # this if statement is there to keep the stage at the finish position (in x) and not bring it back like we were doing during the scan + self.pi_device.MVR(axes=self.axes[1], values=[y_step]) + else: + self.pi_device.MVR(axes=self.axes, values=[-x_scan_size, y_step]) + + self.ui.status_textBrowser.append("Scan Complete!\nSaving Data...") + + save_dict = {"Wavelengths": self.spec.wavelengths(), "Intensities": data_array, + "Scan Parameters":{"X scan start (um)": x_start, "Y scan start (um)": y_start, + "X scan size (um)": x_scan_size, "Y scan size (um)": y_scan_size, + "X step size (um)": x_step, "Y step size (um)": y_step}, + "OceanOptics Parameters":{"Integration Time (ms)": self.ui.intg_time_spinBox.value(), + "Scans Averages": self.ui.scan_to_avg_spinBox.value(), + "Correct Dark Counts": self.ui.correct_dark_counts_checkBox.isChecked()} + } + + pickle.dump(save_dict, open(self.save_folder+"/"+self.ui.lineEdit.text()+"_raw_PL_spectra_data.pkl", "wb")) + + self.ui.status_textBrowser.append("Data saved!\nTotal time taken:"+str(np.float16((time.time()-start_time)/60))+" mins") + + def save_file_location(self): + self.save_folder = QtWidgets.QFileDialog.getExistingDirectory(self, caption="Select Folder") + + def save_single_spec(self): + save_array = np.zeros(shape=(2048,2)) + self._read_spectrometer() + save_array[:,1] = self.y + save_array[:,0] = self.spec.wavelengths() + + np.savetxt(self.save_folder+"/"+self.ui.lineEdit.text()+".txt", save_array, fmt = '%.5f', + header = 'Wavelength (nm), Intensity (counts)', delimiter = ' ') + + def live(self): + save_array = np.zeros(shape=(2048,2)) + + self.ui.plot.setLabel('left', 'Intensity', units='a.u.') + self.ui.plot.setLabel('bottom', 'Wavelength', units='nm') + j = 0 + while self.spec is not None:#self.ui.connect_checkBox.isChecked(): # this while loop works! + self._read_spectrometer() + save_array[:,1] = self.y + + self.ui.plot.plot(self.spec.wavelengths(), self.y, pen='r', clear=True) + + if self.ui.save_every_spec_checkBox.isChecked(): + save_array[:,0] = self.spec.wavelengths() + np.savetxt(self.save_folder+"/"+self.ui.lineEdit.text()+str(j)+".txt", save_array, fmt = '%.5f', + header = 'Wavelength (nm), Intensity (counts)', delimiter = ' ') + + pg.QtGui.QApplication.processEvents() + j += 1 + + def _read_spectrometer(self): + if self.spec is not None: + + intg_time_ms = self.ui.intg_time_spinBox.value() + self.spec.integration_time_micros(intg_time_ms*1e3) + + scans_to_avg = self.ui.scan_to_avg_spinBox.value() + Int_array = np.zeros(shape=(2048,scans_to_avg)) + + for i in range(scans_to_avg): #software average + data = self.spec.spectrum(correct_dark_counts=self.ui.correct_dark_counts_checkBox.isChecked()) + Int_array[:,i] = data[1] + self.y = np.mean(Int_array, axis=-1) + + else: + self.ui.status_textBrowser.append("Connect to Spectrometer!") + raise Exception("Must connect to spectrometer first!") + + + def close_connection(self): + if self.spec is not None: + self.spec.close() + self.ui.status_textBrowser.append("Ocean Optics Device Disconnected") + del self.spec + self.spec = None + + 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 + + +win = MainWindow() + + +## Start Qt event loop unless running in interactive mode or using pyside. +if __name__ == '__main__': + import sys + if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): + QtGui.QApplication.instance().exec_() diff --git a/src/main/python/PLQE_analysis/column_selection_gui.ui b/src/main/python/PLQE_analysis/column_selection_gui.ui new file mode 100644 index 0000000..c20125c --- /dev/null +++ b/src/main/python/PLQE_analysis/column_selection_gui.ui @@ -0,0 +1,107 @@ + + + MainWindow + + + + 0 + 0 + 800 + 600 + + + + Select number of columns + + + + + + + Data preview + + + + + + + + Select data columns + + + + + + + + Ref + + + + + + + Inpath + + + + + + + Outpath + + + + + + + 2 + + + + + + + 3 + + + + + + + 4 + + + + + + + Done + + + + + + + + + + + + + + + + + 0 + 0 + 800 + 31 + + + + + + + + diff --git a/src/main/python/PLQE_analysis/plqe_analysis.py b/src/main/python/PLQE_analysis/plqe_analysis.py new file mode 100644 index 0000000..3e42a4f --- /dev/null +++ b/src/main/python/PLQE_analysis/plqe_analysis.py @@ -0,0 +1,212 @@ +# 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 + +# 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) + if ".txt" in self.filename[0]: + self.data = np.loadtxt(self.filename[0], delimiter = '\t', skiprows = 1) + elif ".csv" in self.filename[0]: + self.data = np.loadtxt(self.filename[0], delimiter = ',', skiprows = 1) + elif ".qua" in self.filename[0]:#TODO: Include a Pop-up window for input for skipping header + self.data = np.genfromtxt(self.filename[0], delimiter = '\t', skip_header = 28) + self.cs_window = ColSelectionWindow(self.data) + self.cs_window.col_selection_signal.connect(self.open_with_col_selection) + 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 open_with_col_selection(self): + ref_data_col = self.cs_window.ui.ref_spinBox.value() - 1 #subtract since spinboxes refer to column num and not index + inpath_data_col = self.cs_window.ui.inpath_spinBox.value() - 1 + outpath_data_col = self.cs_window.ui.outpath_spinBox.value() - 1 + self.ref_data = np.copy(self.data[:,ref_data_col]) + self.inpath_data = np.copy(self.data[:,inpath_data_col]) + self.outpath_data = np.copy(self.data[:,outpath_data_col]) + + 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, pen='r') + 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() + +"""Table view GUI""" +ui_file_path = (base_path / "column_selection_gui.ui").resolve() +col_selection_WindowTemplate, col_selection_TemplateBaseClass = pg.Qt.loadUiType(ui_file_path) + +class ColSelectionWindow(col_selection_TemplateBaseClass): + + col_selection_signal = QtCore.pyqtSignal() #signal to help with pass info back to MainWindow + + def __init__(self, data): + col_selection_TemplateBaseClass.__init__(self) + # Create the param window + self.ui = col_selection_WindowTemplate() + self.ui.setupUi(self) + self.ui.done_pushButton.clicked.connect(self.done) + + self.table_widget = pg.TableWidget() + self.ui.data_preview_groupBox.layout().addWidget(self.table_widget) + + self.table_widget.setData(data) + + #self.setWindowFlag(QtCore.Qt.WindowCloseButtonHint, False) + self.show() + + def done(self): + self.col_selection_signal.emit() + self.ui.textBrowser.setText("Data successfully loaded.") + #self.close() + + def closeEvent(self, event): + self.col_selection_signal.emit() + +"""Run the Main Window""" +def run(): + win = MainWindow() + QtGui.QApplication.instance().exec_() + return win + +#run() diff --git a/src/main/python/PLQE_analysis/plqe_analysis_gui.ui b/src/main/python/PLQE_analysis/plqe_analysis_gui.ui new file mode 100644 index 0000000..7720b49 --- /dev/null +++ b/src/main/python/PLQE_analysis/plqe_analysis_gui.ui @@ -0,0 +1,150 @@ + + + Form + + + + 0 + 0 + 575 + 524 + + + + PLQE Analysis + + + + + + 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/src/main/python/Spectrum_analysis/Spectra_fit_funcs.py b/src/main/python/Spectrum_analysis/Spectra_fit_funcs.py new file mode 100644 index 0000000..a96f782 --- /dev/null +++ b/src/main/python/Spectrum_analysis/Spectra_fit_funcs.py @@ -0,0 +1,241 @@ +# -*- coding: utf-8 -*- +""" +Created on Sun Mar 31 15:46:54 2019 + +@author: Sarthak +""" + +# -*- coding: utf-8 -*- +""" +Created on Thu Jan 31 20:48:03 2019 + +@author: Sarthak +""" +import numpy as np +from lmfit.models import GaussianModel, LorentzianModel + +class Spectra_Fit(object): + """ + Fit a spectrum. + + Attributes: + data: spectrum data (x-axis and y-axis) + ref: reference spectrum (both x and y-axis) for background correction + """ + + def __init__(self, data, ref, wlref = None, fit_in_eV = True): + self.data = data + self.ref = ref + self.wlref = wlref + self.fit_in_eV = fit_in_eV + + def background_correction(self): + """Return the background corrected spectrum""" + x = self.data[:, 0] # wavelengths + y = self.data[:, 1] # intensity + yref = self.ref[:, 1] + y = y - yref # background correction + if self.wlref is not None: + wlref = self.wlref[:,1] + y = y/wlref + + if self.fit_in_eV is True: + x = np.sort(1240/self.data[:, 0]) # converting to eV and sorting in ascending order + y = [y[i] for i in np.argsort(1240/x)] # sorting argument of y acc to x sorting index + # need to do this because data is collected in wavelength + + return [x,y] + +class Single_Gaussian(Spectra_Fit): + """Fit a single gaussian to the spectrum + + Attributes: + data: spectrum data (x-axis and y-axis) + ref: reference spectrum (both x and y-axis) for background correction + """ + + def gaussian_model(self): + x,y = self.background_correction() + gmodel = GaussianModel(prefix = 'g1_') # calling gaussian model + pars = gmodel.guess(y, x=x) # parameters - center, width, height + result = gmodel.fit(y, pars, x=x, nan_policy='propagate') + return result + + # 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() + if self.fit_in_eV is True: + peak_pos = 1240/peak_pos + sigma = 1240/sigma + min_max_range = np.sort(1240/np.asarray(min_max_range)) + gmodel = GaussianModel(prefix = 'g1_') # calling gaussian model + pars = gmodel.guess(y, x=x) # parameters - center, width, height + 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 + +class Single_Lorentzian(Spectra_Fit): + """Fit a single Lorentzian to the spectrum + + Attributes: + data: spectrum data (x-axis and y-axis) + ref: reference spectrum (both x and y-axis) for background correction + """ + + def lorentzian_model(self): + x,y = self.background_correction() + lmodel = LorentzianModel(prefix = 'l1_') # calling lorentzian model + pars = lmodel.guess(y, x=x) # parameters - center, width, height + 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() + if self.fit_in_eV is True: + peak_pos = 1240/peak_pos + sigma = 1240/sigma + min_max_range = np.sort(1240/np.asarray(min_max_range)) + lmodel = LorentzianModel(prefix = 'l1_') # calling lorentzian model + pars = lmodel.guess(y, x=x) # parameters - center, width, height + 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 + +class Double_Gaussian(Spectra_Fit): + """Fit two gaussians to the spectrum + + Attributes: + data: spectrum data (x-axis and y-axis) + ref: reference spectrum (both x and y-axis) for background correction + """ + + 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() + if self.fit_in_eV is True: + peak_pos = 1240/np.asarray(peak_pos) + sigma = 1240/np.asarray(sigma) + min_max_range = np.sort(1240/np.asarray(min_max_range)) + gmodel_1 = GaussianModel(prefix='g1_') # calling gaussian model + pars = gmodel_1.guess(y, x=x) # parameters - center, width, height + 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(peak_pos[1], min = min_max_range[1][0], max = min_max_range[1][1]) + pars['g2_sigma'].set(sigma[1], min = pars['g1_sigma'].value) + pars['g2_amplitude'].set(min = 0) + + gmodel = gmodel_1 + gmodel_2 + result = gmodel.fit(y, pars, x=x, nan_policy='propagate') + return result + +class Multi_Gaussian(Spectra_Fit): + + # 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, fit_in_eV = True): + Spectra_Fit.__init__(self, data, ref, wlref, fit_in_eV=True) + 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 + + composite_model = None + composite_pars = None + + x,y = self.background_correction() + + assert self.num_of_gaussians == len(self.peak_pos), ("Number of gaussians must be equal to the number of peak positions") + assert len(self.min_max_range) == len(self.peak_pos), ("Number of bounds on the range must be equal to the number of peak positions") + + if self.fit_in_eV is True: + peak_pos = 1240/np.asarray(peak_pos) + sigma = 1240/np.asarray(sigma) + min_max_range = np.sort(1240/np.asarray(min_max_range)) + + + 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() + 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(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[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) + + + 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 \ No newline at end of file diff --git a/src/main/python/Spectrum_analysis/Spectra_plot_fit.py b/src/main/python/Spectrum_analysis/Spectra_plot_fit.py new file mode 100644 index 0000000..69f182b --- /dev/null +++ b/src/main/python/Spectrum_analysis/Spectra_plot_fit.py @@ -0,0 +1,1020 @@ +# -*- coding: utf-8 -*- +""" +Created on Wed Mar 27 16:50:26 2019 + +@author: Sarthak +""" + +# 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 matplotlib +import pickle +import lmfit +from lmfit.models import GaussianModel, LinearModel +from scipy import interpolate +import customplotting.mscope as cpm + +sys.path.append(os.path.abspath('../H5_Pkl')) +from H5_Pkl import h5_pkl_view +sys.path.append(os.path.abspath('../Export_Windows')) +try: + from Export_window import ExportFigureWindow, ExportPlotWindow +except: + from Export_Windows.Export_window import ExportFigureWindow, ExportPlotWindow +# local modules +try: + from Spectra_fit_funcs import Spectra_Fit, Single_Gaussian, Single_Lorentzian, Double_Gaussian, Multi_Gaussian + from pyqtgraph_MATPLOTLIBWIDGET import MatplotlibWidget +except: + from Spectrum_analysis.Spectra_fit_funcs import Spectra_Fit, Single_Gaussian, Single_Lorentzian, Double_Gaussian, Multi_Gaussian + from Spectrum_analysis.pyqtgraph_MATPLOTLIBWIDGET import MatplotlibWidget + +matplotlib.use('Qt5Agg') + +"""Recylce params for plotting""" +plt.rc('xtick', labelsize = 10) +plt.rc('xtick.major', pad = 1) +plt.rc('ytick', labelsize = 10) +plt.rc('lines', lw = 1.5, markersize = 3.5) +plt.rc('legend', fontsize = 10) +plt.rc('axes', linewidth=1.5) + +pg.mkQApp() +pg.setConfigOption('background', 'w') + + +base_path = Path(__file__).parent +file_path = (base_path / "Spectra_plot_fit_gui.ui").resolve() + +uiFile = file_path + +WindowTemplate, TemplateBaseClass = pg.Qt.loadUiType(uiFile) + +def updateDelay(scale, time): + """ Hack fix for scalebar inaccuracy""" + QtCore.QTimer.singleShot(time, scale.updateBar) + +class MainWindow(TemplateBaseClass): + + def __init__(self): + pg.setConfigOption('imageAxisOrder', 'row-major') + 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) + + ##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.add_to_mem_pushButton.clicked.connect(self.add_trace_to_mem) + self.ui.export_single_figure_pushButton.clicked.connect(self.export_plot_window) + self.ui.export_scan_figure_pushButton.clicked.connect(self.export_window) + self.ui.analyze_spectra_fits_pushButton.clicked.connect(self.analyze_spectra_fits) + + 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) + + self.ui.export_data_pushButton.clicked.connect(self.export_data) + self.ui.clear_export_data_pushButton.clicked.connect(self.clear_export_data) + + # 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.ui.plot = pg.PlotWidget() + self.ui.plot.setTitle(title="Spectrum Plot") + #self.ui.plot.show() + + 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 + + #variables accounting for data received from FLIM analysis + self.opened_from_flim = False #switched to True in FLIM_plot when "analyze lifetime" clicked + self.sum_data_from_flim = [] + + # fit scan file variable set to None for analyze_spectra_fits + self.fit_scan_file = None + + #container for data to append to txt file + self.data_list = [] + + #for adding traces to memory for plotting/exporting all at once + self.x_mem = [] + self.y_mem = [] + self.best_fit_mem = [] + self.legend = [] + self.single_spec_fit_called = False + + self.show() + + """ Open Single Spectrum files """ + def open_file(self): + try: + self.single_spec_filename = QtWidgets.QFileDialog.getOpenFileName(self) + try: + try: + self.file = np.loadtxt(self.single_spec_filename[0], skiprows = 1, delimiter=" ") + except: + self.file = np.loadtxt(self.single_spec_filename[0], skiprows = 16, delimiter='\t') + except: + self.file = np.genfromtxt(self.single_spec_filename[0], skip_header=1, skip_footer=3, delimiter='\t') + self.opened_from_flim = False + except: + pass + + def open_bck_file(self): + try: + filename = QtWidgets.QFileDialog.getOpenFileName(self) + try: + try: + self.bck_file = np.loadtxt(filename[0], skiprows=1, delimiter=" ") + except: + 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: + try: + self.wlref_file = np.loadtxt(filename[0], skiprows=1, delimiter= " ") + except: + 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, filter="Scan files (*.pkl *.h5)") + self.filename_for_viewer_launch = filename[0] + if ".pkl" in filename[0]: + self.spec_scan_file = pickle.load(open(filename[0], 'rb')) + if self.ui.launch_data_viewer_checkBox.isChecked(): + self.launch_h5_pkl_viewer() + self.scan_file_type = "pkl" + elif ".h5" in filename[0]: + self.spec_scan_file = h5py.File(filename[0], 'r') + if self.ui.launch_data_viewer_checkBox.isChecked(): + self.launch_h5_pkl_viewer() + self.scan_file_type = "h5" + self.get_data_params() + 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) + with pg.BusyCursor(): + self.filename_for_viewer_launch = filename[0] + self.fit_scan_file = pickle.load(open(filename[0], 'rb')) + if self.ui.launch_data_viewer_checkBox.isChecked(): + self.launch_h5_pkl_viewer() # TODO Needs to implement reading the fit result datatype in PKL Viewer + 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.getOpenFileNames(self) + files = self.pkl_to_convert[0] + for i in range(len(files)): + self.filename_for_viewer_launch = files[i] + if self.ui.launch_data_viewer_checkBox_2.isChecked(): + self.launch_h5_pkl_viewer() + except: + pass + + def launch_h5_pkl_viewer(self): + """ Launches H5/PKL viewer to give an insight into the data and its structure""" + viewer_window = h5_pkl_view.H5PklView(sys.argv) + viewer_window.settings['data_filename'] = self.filename_for_viewer_launch + + def analyze_spectra_fits(self): + """Analyze fits to the individual spectrum within a spectra scan fit file""" + if self.fit_scan_file is None: + self.open_fit_scan_file() + + #result_no = int(self.ui.result_spinBox.value()) + #self.matplotlibwidget = MatplotlibWidget(size=(12,8), dpi=300) + #self.fit_scan_file['result_'+str(0)].plot(fig=self.matplotlibwidget.getFigure().add_subplot(111)) + #self.matplotlibwidget.draw() + #self.matplotlibwidget.show() + analyze_window = Analyze(scan_fit_file=self.fit_scan_file) + analyze_window.run() + + 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(False) + self.ui.save_all_checkBox.setEnabled(False) + 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.save_all_checkBox.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.save_all_checkBox.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 check_loaded_files(self): + """ + Check if 'subtract background' or 'white light correction' is checked + and if required files have been loaded. + """ + if self.ui.subtract_bck_radioButton.isChecked() and self.bck_file is None: + self.ui.result_textBrowser.setText("You need to load a background file.") + 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!") + elif self.wlref_file is None and self.ui.WLRef_checkBox.isChecked(): + self.ui.result_textBrowser.setText("You need to load a White Light Ref file.") + else: + return True + + def plot(self): + try: + if self.opened_from_flim: + flim_data = self.sum_data_from_flim.T + interp = interpolate.interp1d(flim_data[:,0], flim_data[:,1]) + x_range = [flim_data[:,0][0], flim_data[:,0][-1]] + xnew = np.linspace(x_range[0], x_range[1], 100 ) + ynew = interp(xnew) + self.file = np.zeros((xnew.shape[0], 2)) + self.file[:,0] = xnew + self.file[:,1] = ynew + self.x = xnew + self.y = ynew + + elif self.file is None: #elif + self.ui.result_textBrowser.setText("You need to load a data file.") + else: + self.x = self.file[:,0] + self.y = self.file[:,1] + + self.check_loaded_files() + + if self.check_loaded_files() == True: #check the following conditions if all required files have been provided + if self.ui.subtract_bck_radioButton.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_radioButton.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_radioButton.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.check_eV_state() + self.ui.plot.show() + self.ui.plot.plot(self.x, self.y, clear=self.clear_check(), pen='r') + + except Exception as e: + self.ui.result_textBrowser.append(str(e)) + pass + self.ui.plot.setLabel('left', 'Intensity', units='a.u.') + if self.ui.fit_in_eV.isChecked(): + self.ui.plot.setLabel('bottom', 'Energy (eV)') + else: + self.ui.plot.setLabel('bottom', 'Wavelength (nm)') + + self.single_spec_fit_called = False + + def normalize(self): + self.y = (self.y) / np.amax(self.y) + + def check_eV_state(self): + if self.ui.fit_in_eV.isChecked(): + self.x = np.sort(1240/self.file[:,0]) + self.y = [self.y[i] for i in np.argsort(1240/self.file[:,0])] + else: + self.x = self.file[:,0] + self.y = self.file[:,1] + + 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 + + def fit_and_plot(self): + fit_func = self.ui.fitFunc_comboBox.currentText() + + try: + self.plot() + if self.opened_from_flim: + self.file = np.zeros((self.x.shape[0], 2)) + self.file[:,0] = self.x + self.file[:,1] = self.y + bck = lmfit.models.LinearModel(prefix='line_') + gmodel = GaussianModel(prefix='g1_') + pars = bck.make_params(intercept=self.y.min(), slope=0) + pars += gmodel.guess(self.y, x=self.x) + comp_model = gmodel + bck + self.result = comp_model.fit(self.y, pars, x=self.x, nan_policy='propagate') + 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()) + + + if self.ui.plot_without_bck_radioButton.isChecked(): #if plot w/o bck, create dummy bck_file + self.bck_file = np.zeros(shape=(self.file.shape[0], 2)) + self.bck_file[:,0] = self.file[:,0] + + # if self.ui.subtract_bck_radioButton.isChecked() == False: + # self.ui.result_textBrowser.setText("You need to check the subtract background option!") + if self.check_loaded_files is None: + pass + elif self.opened_from_flim: + pass + else: + self.check_eV_state() + if fit_func == "Single Gaussian": + single_gauss = Single_Gaussian(self.file, self.bck_file, wlref=self.wlref_file, fit_in_eV=self.ui.fit_in_eV.isChecked()) + 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.plot() + 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_radioButton.isChecked() == True: + single_lorentzian = Single_Lorentzian(self.file, self.bck_file, wlref=self.wlref_file, fit_in_eV=self.ui.fit_in_eV.isChecked()) + + 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.plot() + 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_radioButton.isChecked() == True: + double_gauss = Double_Gaussian(self.file, self.bck_file, wlref=self.wlref_file, fit_in_eV=self.ui.fit_in_eV.isChecked()) + 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.plot() + 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 Gaussian": #and self.ui.subtract_bck_radioButton.isChecked() == True: + #currently only works for triple gaussian (n=3) + multiple_gauss = Multi_Gaussian(self.file, self.bck_file, 3, wlref=self.wlref_file, fit_in_eV=self.ui.fit_in_eV.isChecked()) + 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.plot() + 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()) + + self.data_list.append(self.ui.result_textBrowser.toPlainText()) + self.single_spec_fit_called = True + + except Exception as e: + self.ui.result_textBrowser.append(str(e)) + + def export_window(self): + self.export_window = ExportImages() + self.export_window.export_fig_signal.connect(self.pub_ready_plot_export) + + def export_plot_window(self): + self.exportplotwindow = ExportPlotWindow() + self.exportplotwindow.export_fig_signal.connect(self.pub_ready_plot_export) + + def pub_ready_plot_export(self): + filename = QtWidgets.QFileDialog.getSaveFileName(self,caption="Filename with EXTENSION") + """Recylce 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) + try: + try: + try: + data = self.spec_scan_file + except: + data = self.fit_scan_file + if self.export_window.ui.reverse_checkBox.isChecked(): + colormap = str(self.export_window.ui.cmap_comboBox.currentText())+"_r" + else: + colormap = str(self.export_window.ui.cmap_comboBox.currentText()) + if str(self.export_window.ui.dataChannel_comboBox.currentText()) == "Fitted": + 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, FLIM_adjust = False, stepsize = data['Scan Parameters']['X step size (um)'], cmap=colormap, cbar_label=label, + vmin=self.export_window.ui.vmin_spinBox.value(), vmax=self.export_window.ui.vmax_spinBox.value()) + elif str(self.export_window.ui.dataChannel_comboBox.currentText()) == "Raw": + cpm.plot_confocal(self.sums, FLIM_adjust = False, figsize=(10,10), stepsize = data['Scan Parameters']['X step size (um)'], cmap=colormap, + vmin=self.export_window.ui.vmin_spinBox.value(), vmax=self.export_window.ui.vmax_spinBox.value()) + plt.tick_params(direction='out', length=8, width=3.5) + plt.tight_layout() + plt.savefig(filename[0],bbox_inches='tight', dpi=300) + plt.close() + except: + if self.x_mem == []: + self.ui.result_textBrowser.setText("Add traces to memory first!") + else: + plt.figure(figsize=(8,6)) + plt.tick_params(direction='out', length=8, width=3.5) + for i in range(len(self.x_mem)): + plt.plot(self.x_mem[i], self.y_mem[i], label=str(self.legend[i])) + if self.single_spec_fit_called == True: + plt.plot(self.x_mem[i], self.best_fit_mem[i],'k') + + if self.ui.fit_in_eV.isChecked(): + plt.xlabel("Energy (eV)", fontsize=20, fontweight='bold') + else: + plt.xlabel("Wavelength (nm)", fontsize=20, fontweight='bold') + plt.ylabel("Intensity (a.u.)", fontsize=20, fontweight='bold') + plt.xlim([self.exportplotwindow.ui.lowerX_spinBox.value(),self.exportplotwindow.ui.upperX_spinBox.value()]) + plt.ylim([self.exportplotwindow.ui.lowerY_spinBox.value(),self.exportplotwindow.ui.upperY_doubleSpinBox.value()]) + plt.legend() + 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 export_data(self): + """ Save fit params and srv calculations stored in data_list as .txt """ + folder = os.path.dirname(self.single_spec_filename[0]) + filename_ext = os.path.basename(self.single_spec_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 clear_export_data(self): + self.data_list = [] + self.x_mem = [] + self.y_mem = [] + self.legend = [] + self.best_fit_mem = [] + + def add_trace_to_mem(self): + try: + self.x_mem.append(self.x) + self.y_mem.append(self.y) + if self.single_spec_fit_called == True: + self.best_fit_mem.append(self.result.best_fit) + self.legend.append(self.ui.lineEdit.text()) + except Exception as e: + print(e) + + + """ Scan spectra functions """ + def get_data_params(self): + data = self.spec_scan_file + if self.scan_file_type == "pkl": + self.intensities = data['Intensities'] + self.wavelengths = data['Wavelengths'] + # try: + self.x_scan_size = data['Scan Parameters']['X scan size (um)'] + self.y_scan_size = data['Scan Parameters']['Y scan size (um)'] + self.x_step_size = data['Scan Parameters']['X step size (um)'] + self.y_step_size = data['Scan Parameters']['Y step size (um)'] + # except: # TODO test and debug loading pkl file w/o scan parameters + # self.configure_scan_params() + # while not hasattr(self, "scan_params_entered"): + # pass + # self.x_scan_size = self.param_window.ui.x_scan_size_spinBox.value() + # self.y_scan_size = self.param_window.ui.y_scan_size_spinBox.value() + # self.x_step_size = self.param_window.ui.x_step_size_spinBox.value() + # self.y_step_size = self.param_window.ui.y_step_size_spinBox.value() + + else: #run this if scan file is h5 + self.x_scan_size = data['Scan Parameters'].attrs['X scan size (um)'] + self.y_scan_size = data['Scan Parameters'].attrs['Y scan size (um)'] + self.x_step_size = data['Scan Parameters'].attrs['X step size (um)'] + self.y_step_size = data['Scan Parameters'].attrs['Y step size (um)'] + self.intensities = data['Intensities'][()] #get dataset values + self.wavelengths = data['Wavelengths'][()] + + self.numb_x_pixels = int(np.ceil(self.x_scan_size/self.x_step_size)) + self.numb_y_pixels = int(np.ceil(self.y_scan_size/self.y_step_size)) + + """Open param window and get peak center range values and assign it to variables to use later""" + # def configure_scan_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 plot_fit_scan(self): + try: + if self.ui.use_raw_scan_settings.isChecked(): + num_x = self.numb_x_pixels + num_y =self.numb_y_pixels + 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)) +# sigma = np.zeros(shape=(numb_of_points,1)) +# height = np.zeros(shape=(numb_of_points,1)) + + if type(self.fit_scan_file['result_0']) == dict: + for i in range(numb_of_points): + fwhm[i, 0] = 2.3548200*self.fit_scan_file['result_'+str(i)]['g1_sigma'] + pk_pos[i, 0] = self.fit_scan_file['result_'+str(i)]['g1_center'] +# sigma[i, 0] = self.fit_scan_file['result_'+str(i)]['g1_sigma'] +# height[i, 0] = self.fit_scan_file['result_'+str(i)].values['g1_height'] + + elif type(self.fit_scan_file['result_0']) == lmfit.model.ModelResult: + 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 num_y == 1: + x = np.linspace(0, self.x_scan_size, num_x) + self.graph_layout=pg.GraphicsLayoutWidget() + self.plot = self.graph_layout.addPlot(title="Line Scan") + self.plot.plot(x, self.img[:,0], pen="r") + self.graph_layout.show() + elif num_x == 1: + y = np.linspace(0, self.y_scan_size, num_y) + self.graph_layout=pg.GraphicsLayoutWidget() + self.plot = self.graph_layout.addPlot(title="Line Scan") + self.plot.plot(y, self.img[0,:], pen="r") + self.graph_layout.show() + + else: + self.fit_scan_viewbox = pg.ImageView() + if self.ui.use_raw_scan_settings.isChecked(): + self.fit_scan_viewbox.setImage(self.img, scale= + (self.x_step_size, + self.y_step_size)) + scale = pg.ScaleBar(size=2,suffix='um') + scale.setParentItem(self.fit_scan_viewbox.view) + scale.anchor((1, 1), (1, 1), offset=(-30, -30)) + self.fit_scan_viewbox.view.sigRangeChanged.connect(lambda: updateDelay(scale, 10)) + else: + self.fit_scan_viewbox.setImage(self.img) + + self.fit_scan_viewbox.view.invertY(False) + self.fit_scan_viewbox.show() + + except Exception as e: + self.ui.result_textBrowser2.append(str(e)) + pass + + def plot_raw_scan(self): + try: + # TODO test line scan plots + + intensities = self.intensities.T #this is only there because of how we are saving the data in the app + intensities = np.reshape(intensities, newshape=(2048,self.numb_x_pixels, self.numb_y_pixels)) + self.raw_scan_viewbox = pg.ImageView() + self.raw_scan_viewbox.setImage(intensities, scale= + (self.x_step_size, + self.y_step_size), xvals=self.wavelengths) + + #roi_plot = self.ui.raw_scan_viewBox.getRoiPlot() + #roi_plot.plot(data['Wavelengths'], intensities) + self.raw_scan_viewbox.view.invertY(False) + scale = pg.ScaleBar(size=2,suffix='um') + scale.setParentItem(self.raw_scan_viewbox.view) + scale.anchor((1, 1), (1, 1), offset=(-30, -30)) + self.raw_scan_viewbox.view.sigRangeChanged.connect(lambda: updateDelay(scale, 10)) + self.raw_scan_viewbox.show() + + except Exception as e: + self.ui.result_textBrowser2.append(str(e)) + + def plot_intensity_sums(self): + try: + # TODO test line scan plots + + #intensities = np.reshape(intensities, newshape=(2048, numb_pixels_X*numb_pixels_Y)) + + sums = np.sum(self.intensities, axis=-1) + self.sums = np.reshape(sums, newshape=(self.numb_x_pixels, self.numb_y_pixels)) + self.intensity_sums_viewBox = pg.ImageView() + + self.intensity_sums_viewBox.setImage(self.sums, scale= + (self.x_step_size, + self.y_step_size)) + self.intensity_sums_viewBox.view.invertY(False) + + scale = pg.ScaleBar(size=2,suffix='um') + scale.setParentItem(self.intensity_sums_viewBox.view) + scale.anchor((1, 1), (1, 1), offset=(-30, -30)) + self.intensity_sums_viewBox.view.sigRangeChanged.connect(lambda: updateDelay(scale, 10)) + self.intensity_sums_viewBox.show() + + 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") + print("Using Single Gaussian to Fit\nThis is the only fitting functions implemented") + + with pg.BusyCursor(): + 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()) + + if self.bck_file is None: + print("Load Background file!") + ref = self.bck_file + index = (ref[:,0]>start_nm) & (ref[:,0]start_nm) & (x + + MainWindow + + + + 0 + 0 + 2115 + 1483 + + + + + 0 + 0 + + + + Spectral Analysis + + + + + + + + + + 12 + + + + Fit Settings + + + + + + false + + + + 10 + + + + n: + + + + + + + false + + + + 10 + + + + 1 + + + 1 + + + + + + + false + + + + 10 + + + + Plot components (>1 Gaussian) + + + true + + + + + + + false + + + + 10 + + + + Bounds + + + + + + 0 + + + + + + + + + + + + + + max + + + + + + + min + + + + + + + 9999.000000000000000 + + + 780.000000000000000 + + + + + + + 9999.000000000000000 + + + 760.000000000000000 + + + + + + + Peak Center 1 (nm) + + + + + + + + + + + + + + + + + + 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 + + + + + + + Sigma 1 (nm) + + + + + + + Peak Center 1 (nm) + + + + + + + 9999.000000000000000 + + + 770.000000000000000 + + + + + + + 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/Plot in eV + + + true + + + + + + + + 10 + + + + Fit Single Spectrum + + + + + + + false + + + Save Entire Fit Model (File will be large and will take longer) + + + + + + + false + + + + 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 + + + + 0 + + + false + + + + Single Spectrum + + + + + + + 0 + 0 + + + + + 350 + 16777215 + + + + + 12 + + + + Load Settings + + + + + + + 10 + + + + Spectrum File + + + + + + + + 10 + + + + Export Figure + + + + + + + + 10 + + + + Clear Export Memory + + + + + + + + 10 + + + + Correct for White Light + + + + + + + + 10 + + + + White Light Ref File + + + + + + + + 10 + + + + Enter Legend Here... + + + + + + + + 10 + + + + For Single Spectrum + + + + + + + + 10 + + + + Subtract Background + + + true + + + + + + + + 10 + + + + Export Fit Data + + + + + + + + 10 + + + + Add Trace to Memory + + + + + + + + 10 + + + + Plot without Background + + + + + + + + 10 + 50 + false + + + + Plot + + + + + + + + 10 + + + + Background File + + + + + + + + 10 + + + + Clear Plots Everytime + + + true + + + + + + + + 10 + + + + Normalize + + + + + + + + 10 + false + + + + Clear Plot + + + + + + + Export Settings + + + + + + + For Data + + + + + + + For Figure + + + + + + + Plot Control + + + + + + + + + + + 10 + + + + + + + + + Scan Spectra Data + + + + + + + + + 210 + 16777215 + + + + + 12 + + + + Load Settings + + + + + + + 12 + + + + Spectra Scan +File + + + + + + + + 12 + + + + Background +File + + + + + + + + 12 + + + + Load Only + Fit File + + + + + + + + 12 + + + + For Scan Data + + + + + + + + + + + 12 + + + + + + + + + 12 + + + + Export Fitted +Scan + + + + + + + + 12 + + + + Launch Data Viewer + + + + + + + + 12 + + + + After Fitting Scan Data + + + + + + + + 12 + + + + Intensity Sums + + + + + + + + 12 + + + + For Raw Scan Data + + + + + + + + 12 + + + + Plot + + + + + + + + 12 + + + + Plot + + + + + + + + 12 + + + + + pk_pos + + + + + fwhm + + + + + + + + + 12 + + + + Plot + + + + + + + + 12 + + + + Export Settings + + + + + + + + 12 + + + + Analyze Spectra Fits + + + + + + + + 12 + + + + For Fit Scale + + + + + + + + 12 + + + + Use Raw Scan Settings + + + true + + + + + + + + 12 + + + + # X points + + + + + + + + 12 + + + + # Y points + + + + + + + 2000 + + + 100 + + + + + + + 2000 + + + 100 + + + + + + + + + + .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 + + + + .pkl to .h5 + + + + + + + 20 + 20 + 323 + 40 + + + + + 12 + + + + Import .pkl file + + + + + + 390 + 20 + 327 + 43 + + + + + 12 + + + + Launch Data Viewer + + + + + + + + + + + 0 + 0 + 2115 + 38 + + + + + + + + diff --git a/src/main/python/Spectrum_analysis/__init__.py b/src/main/python/Spectrum_analysis/__init__.py new file mode 100644 index 0000000..b4011af --- /dev/null +++ b/src/main/python/Spectrum_analysis/__init__.py @@ -0,0 +1 @@ +from __future__ import absolute_import, division, print_function \ No newline at end of file diff --git a/src/main/python/Spectrum_analysis/analyze_fit_results.ui b/src/main/python/Spectrum_analysis/analyze_fit_results.ui new file mode 100644 index 0000000..7669c5a --- /dev/null +++ b/src/main/python/Spectrum_analysis/analyze_fit_results.ui @@ -0,0 +1,91 @@ + + + Form + + + + 0 + 0 + 664 + 136 + + + + Analze Fit Results + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 12 + + + + Check! + + + + + + + + 12 + + + + 1000000000 + + + + + + + + 12 + + + + Result number: + + + Qt::AlignCenter + + + + + + + + 15 + + + + Analyze Fit Results + + + Qt::AlignCenter + + + + + + + + + + + + + diff --git a/src/main/python/Spectrum_analysis/pyqtgraph_MATPLOTLIBWIDGET.py b/src/main/python/Spectrum_analysis/pyqtgraph_MATPLOTLIBWIDGET.py new file mode 100644 index 0000000..ba2f59c --- /dev/null +++ b/src/main/python/Spectrum_analysis/pyqtgraph_MATPLOTLIBWIDGET.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +""" +Created on Mon Sep 2 13:02:50 2019 + +@author: Sarthak +""" + +from pyqtgraph.Qt import QtGui, QtCore, USE_PYSIDE, USE_PYQT5 +import matplotlib + +#if not USE_PYQT5: +# if USE_PYSIDE: +# matplotlib.rcParams['backend.qt4']='PySide' +# +# from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas +# from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar +#else: +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas +from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar + +from matplotlib.figure import Figure + +if matplotlib.get_backend() is not 'Qt5Agg': + matplotlib.use('Qt5Agg') + +class MatplotlibWidget(QtGui.QWidget): + """ + Implements a Matplotlib figure inside a QWidget. + Use getFigure() and redraw() to interact with matplotlib. + + Example:: + + mw = MatplotlibWidget() + subplot = mw.getFigure().add_subplot(111) + subplot.plot(x,y) + mw.draw() + """ + + def __init__(self, size=(5.0, 4.0), dpi=100): + QtGui.QWidget.__init__(self) + self.fig = Figure(size, dpi=dpi) + self.canvas = FigureCanvas(self.fig) + self.canvas.setParent(self) + self.toolbar = NavigationToolbar(self.canvas, self) + + self.vbox = QtGui.QVBoxLayout() + self.vbox.addWidget(self.toolbar) + self.vbox.addWidget(self.canvas) + + self.setLayout(self.vbox) + + def getFigure(self): + return self.fig + + def draw(self): + self.canvas.draw() \ No newline at end of file diff --git a/src/main/python/Spectrum_analysis/scan_params_input.ui b/src/main/python/Spectrum_analysis/scan_params_input.ui new file mode 100644 index 0000000..4df8d40 --- /dev/null +++ b/src/main/python/Spectrum_analysis/scan_params_input.ui @@ -0,0 +1,68 @@ + + + Form + + + + 0 + 0 + 542 + 211 + + + + Form + + + + + + X scan size + + + + + + + + + + Y scan size + + + + + + + + + + X step size + + + + + + + + + + Y step size + + + + + + + + + + Done + + + + + + + + diff --git a/src/main/python/Table/Table_widget.py b/src/main/python/Table/Table_widget.py new file mode 100644 index 0000000..4c7b447 --- /dev/null +++ b/src/main/python/Table/Table_widget.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- +""" +Created on Mon Sep 2 17:04:49 2019 + +@author: Sarthak +""" + +from pathlib import Path +import pyqtgraph as pg +from pyqtgraph.python2_3 import asUnicode +from pyqtgraph.Qt import QtCore, QtGui + + +pg.mkQApp() +pg.setConfigOption('background', 'w') + + +base_path = Path(__file__).parent +file_path = (base_path / "Table_widget_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) + + self.clear() + + self.ui.clear_pushButton.clicked.connect(self.clear) + self.ui.add_row_pushButton.clicked.connect(self.add_row) + self.ui.add_column_pushButton.clicked.connect(self.add_column) + + """Saving and Copying --- implemented from pyqtgraph TableWidget""" + self.contextMenu = QtGui.QMenu() + self.contextMenu.addAction('Copy Selection').triggered.connect(self.copySel) + self.contextMenu.addAction('Copy All').triggered.connect(self.copyAll) + self.contextMenu.addAction('Save Selection').triggered.connect(self.saveSel) + self.contextMenu.addAction('Save All').triggered.connect(self.saveAll) + + self.show() + + def clear(self): + self.ui.tableWidget.clear() + self.verticalHeadersSet = False + self.horizontalHeadersSet = False + + def add_row(self): + row_position = self.ui.tableWidget.rowCount() + self.ui.tableWidget.insertRow(row_position) + + def add_column(self): + column_position = self.ui.tableWidget.columnCount() + self.ui.tableWidget.insertColumn(column_position) + + def save_table(self):# Needs to be implemented + print(self.ui.tableWidget.currentItem().text()) + + def serialize(self, useSelection=False): + """Convert entire table (or just selected area) into tab-separated text values""" + if useSelection: + selection = self.ui.tableWidget.selectedRanges()[0] + rows = list(range(selection.topRow(), + selection.bottomRow() + 1)) + columns = list(range(selection.leftColumn(), + selection.rightColumn() + 1)) + else: + rows = list(range(self.ui.tableWidget.rowCount())) + columns = list(range(self.ui.tableWidget.columnCount())) + + data = [] + if self.horizontalHeadersSet: + row = [] + if self.verticalHeadersSet: + row.append(asUnicode('')) + + for c in columns: + row.append(asUnicode(self.ui.tableWidget.horizontalHeaderItem(c).text())) + data.append(row) + + for r in rows: + row = [] + if self.verticalHeadersSet: + row.append(asUnicode(self.ui.tableWidget.verticalHeaderItem(r).text())) + for c in columns: + item = self.ui.tableWidget.item(r, c) + if item is not None: + row.append(asUnicode(item.text())) + else: + row.append(asUnicode('')) + data.append(row) + + s = '' + for row in data: + s += ('\t'.join(row) + '\n') + return s + + + def copySel(self): + """Copy selected data to clipboard.""" + QtGui.QApplication.clipboard().setText(self.serialize(useSelection=True)) + + def copyAll(self): + """Copy all data to clipboard.""" + QtGui.QApplication.clipboard().setText(self.serialize(useSelection=False)) + + + def saveSel(self): + """Save selected data to file.""" + self.save(self.serialize(useSelection=True)) + + + def saveAll(self): + """Save all data to file.""" + self.save(self.serialize(useSelection=False)) + + + def save(self, data): + fileName = QtGui.QFileDialog.getSaveFileName(self, "Save As..", "", "Tab-separated values (*.tsv)") + if fileName == '': + return + open(fileName[0], 'w').write(data) + + def contextMenuEvent(self, ev): + self.contextMenu.popup(ev.globalPos()) + + def keyPressEvent(self, ev): + if ev.key() == QtCore.Qt.Key_C and ev.modifiers() == QtCore.Qt.ControlModifier: + ev.accept() + self.copySel() +# else: +# QtGui.QTableWidget.keyPressEvent(self, ev) +"""Run the Main Window""" +def run(): + win = MainWindow() + QtGui.QApplication.instance().exec_() + return win + +#run() \ No newline at end of file diff --git a/src/main/python/Table/Table_widget_gui.ui b/src/main/python/Table/Table_widget_gui.ui new file mode 100644 index 0000000..d5fae7e --- /dev/null +++ b/src/main/python/Table/Table_widget_gui.ui @@ -0,0 +1,70 @@ + + + Form + + + + 0 + 0 + 658 + 438 + + + + TableWidget + + + + + + Add Row + + + + + + + Add Column + + + + + + + Clear + + + + + + + 12 + + + 6 + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/python/Table/__init__.py b/src/main/python/Table/__init__.py new file mode 100644 index 0000000..1a21e72 --- /dev/null +++ b/src/main/python/Table/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +""" +Created on Mon Sep 2 17:52:35 2019 + +@author: Sarthak +""" + diff --git a/src/main/python/UV_Vis_analysis/uv_vis_analysis.py b/src/main/python/UV_Vis_analysis/uv_vis_analysis.py new file mode 100644 index 0000000..6302a40 --- /dev/null +++ b/src/main/python/UV_Vis_analysis/uv_vis_analysis.py @@ -0,0 +1,188 @@ +# 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.correct_for_scattering_checkBox.stateChanged.connect(self.scattering_checkBox_state) + self.scatter_corrected = False + 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 = 2) + 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 scattering_checkBox_state(self): + if self.ui.correct_for_scattering_checkBox.isChecked(): + self.scatter_corrected = True + self.ui.mean_radioButton.setEnabled(True) + self.ui.fourth_orderpoly_radioButton.setEnabled(True) + else: + self.scatter_corrected = False + self.ui.mean_radioButton.setEnabled(False) + self.ui.fourth_orderpoly_radioButton.setEnabled(False) + + def plot_absorbance(self): + try: + self.plotted_absorbance = self.Absorbance #by default set to original absorbance data + if self.scatter_corrected == True: + if self.ui.fourth_orderpoly_radioButton.isChecked(): + p = (np.polyfit(self.Wavelength[(self.Wavelength>self.correction_region_min) & (self.Wavelengthself.correction_region_min) & (self.Wavelengthself.correction_region_min) & (self.Wavelengthself.correction_region_min) & (self.Wavelengthself.correction_region_min) & (self.Wavelengthself.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/src/main/python/UV_Vis_analysis/uv_vis_analysis_gui.ui b/src/main/python/UV_Vis_analysis/uv_vis_analysis_gui.ui new file mode 100644 index 0000000..85bd935 --- /dev/null +++ b/src/main/python/UV_Vis_analysis/uv_vis_analysis_gui.ui @@ -0,0 +1,268 @@ + + + MainWindow + + + + 0 + 0 + 742 + 924 + + + + UV-Vis Analysis + + + + + + + UV-Vis plot + + + + + + + + Scattering Correction + + + + + + + Plot + + + + + + + false + + + 4th Order Polynomial + + + true + + + + + + + Clear + + + + + + + Correct for scattering + + + + + + + false + + + Simple Mean + + + + + + + + + + 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 + 21 + + + + + File + + + + + + + + + Load data + + + + + + diff --git a/src/main/python/__init__.py b/src/main/python/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/main/python/__init__.py @@ -0,0 +1 @@ + diff --git a/src/main/python/main.py b/src/main/python/main.py new file mode 100644 index 0000000..63b241d --- /dev/null +++ b/src/main/python/main.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- +""" +Created on Wed Apr 10 14:41:08 2019 + +@author: Sarthak +""" + +# system imports +import sys +from pathlib import Path + +import pyqtgraph as pg +from pyqtgraph.Qt import QtGui, QtCore + +from fbs_runtime.application_context.PyQt5 import ApplicationContext, cached_property +from PyQt5.QtWidgets import QMainWindow +from PyQt5 import uic + +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 +from Image_analysis import Image_analysis +from Table import Table_widget +from Export_Windows import Multi_Trace_Exporter + +class AppContext(ApplicationContext): + def run(self): + self.main_window.show() + return self.app.exec_() + + def get_main_ui(self): + qtCreatorFile = self.get_resource("DataBrowser_GUI.ui") + return qtCreatorFile + + @cached_property + def main_window(self): + return MainWindow(self.get_main_ui()) + +# pg.mkQApp() +# #pg.setConfigOption('background', 'w') + +# base_path = Path(__file__).parent +# file_path = (base_path / "DataBrowser_GUI.ui").resolve() + +# uiFile = file_path + +# WindowTemplate, TemplateBaseClass = pg.Qt.loadUiType(uiFile) + +class MainWindow(QMainWindow): + + def __init__(self, uiFile): + super(MainWindow, self).__init__() + + # Create the main window + self.ui = uic.loadUi(uiFile, self) + # 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", "Image Analysis", "Table View", + "Mulit-Trace Exporter"]) + 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_()) + elif analysis_software == "Image Analysis": + self.image_window = Image_analysis.MainWindow() + self.image_window.show() + elif analysis_software == "Table View": + self.table_widget = Table_widget.MainWindow() + self.table_widget.show() + elif analysis_software == "Mulit-Trace Exporter": + self.trace_exporter = Multi_Trace_Exporter.MainWindow() + self.trace_exporter.show() + + +def run(): + appctxt = AppContext() # 1. Instantiate ApplicationContext + appctxt.app.setStyle("Fusion") + exit_code = appctxt.run() + sys.exit(exit_code) # 2. Invoke appctxt.app.exec_() + +if __name__ == '__main__': + run() \ No newline at end of file diff --git a/src/main/python/main_0.py b/src/main/python/main_0.py new file mode 100644 index 0000000..4038d57 --- /dev/null +++ b/src/main/python/main_0.py @@ -0,0 +1,15 @@ +from fbs_runtime.application_context.PyQt5 import ApplicationContext +from PyQt5.QtWidgets import QMainWindow + +import sys + +class AppContext(ApplicationContext): + pass + +if __name__ == '__main__': + appctxt = AppContext() # 1. Instantiate ApplicationContext + window = QMainWindow() + window.resize(250, 150) + window.show() + exit_code = appctxt.app.exec_() # 2. Invoke appctxt.app.exec_() + sys.exit(exit_code) \ No newline at end of file diff --git a/src/main/resources/base/DataBrowser_GUI.ui b/src/main/resources/base/DataBrowser_GUI.ui new file mode 100644 index 0000000..054b4ef --- /dev/null +++ b/src/main/resources/base/DataBrowser_GUI.ui @@ -0,0 +1,97 @@ + + + MainWindow + + + + 0 + 0 + 435 + 221 + + + + GLabViz - DataBrowser + + + + + + + + 30 + 75 + true + + + + GLabViz + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 15 + + + + Analysis Tool : + + + + + + + + 15 + + + + + + + + + 15 + + + + Load + + + + + + + + + 0 + 0 + 435 + 21 + + + + + + + + From 44098293afb429e355f473f6ff7172deaab9a5ec Mon Sep 17 00:00:00 2001 From: Sarthak Jariwala Date: Thu, 19 Nov 2020 16:42:29 -0800 Subject: [PATCH 04/10] Picoharp parser now works properly on mac --- .../Lifetime_analysis/picoharp_phd.py | 294 +++++++++--------- 1 file changed, 150 insertions(+), 144 deletions(-) diff --git a/PythonGUI_apps/Lifetime_analysis/picoharp_phd.py b/PythonGUI_apps/Lifetime_analysis/picoharp_phd.py index 455e1bc..072dfa3 100644 --- a/PythonGUI_apps/Lifetime_analysis/picoharp_phd.py +++ b/PythonGUI_apps/Lifetime_analysis/picoharp_phd.py @@ -1,22 +1,27 @@ -# -*- coding: utf-8 -*- """ Majority of this PicoHarp Parser was written by skyjur. Check out the original code here --- https://github.com/skyjur/picoharp300-curvefit-ui -Modified by Sarthak --- -Created on Fri Apr 5 15:50:36 2019 - -@author: Sarthak +Modified by Sarthak Jariwala +Fixed CurveHdr (Sarthak) """ -""" -PicoHarp 300 file parser -""" import datetime import numpy as np -from ctypes import c_uint, c_ulong, c_char, c_int, c_int64, c_float, \ - Structure, sizeof, memmove, addressof +from ctypes import ( + Structure, + addressof, + c_char, + c_float, + c_int, + c_int32, + c_int64, + c_uint, + c_ulong, + memmove, + sizeof +) DISPCURVES = 8 MAXCURVES = 512 @@ -26,150 +31,151 @@ class tParamStruct(Structure): _pack_ = 4 _fields_ = [ - ('Start', c_float), - ('Step', c_float), - ('End', c_float), + ("Start", c_float), + ("Step", c_float), + ("End", c_float), ] class tCurveMapping(Structure): _pack_ = 4 _fields_ = [ - ('MapTo', c_int), - ('Show', c_int), + ("MapTo", c_int), + ("Show", c_int), ] class TxtHdr(Structure): _pack_ = 4 _fields_ = [ - ('Ident', c_char * 16), - ('FormatVersion', c_char * 6), - ('CreatorName', c_char * 18), - ('CreatorVersion', c_char * 12), - ('FileTime', c_char * 18), - ('CRLF', c_char * 2), - ('CommentField', c_char * 256), + ("Ident", c_char * 16), + ("FormatVersion", c_char * 6), + ("CreatorName", c_char * 18), + ("CreatorVersion", c_char * 12), + ("FileTime", c_char * 18), + ("CRLF", c_char * 2), + ("CommentField", c_char * 256), ] class BinHdr(Structure): _pack_ = 4 _fields_ = [ - ('Curves', c_int), - ('BitsPerHistoBin', c_int), - ('RoutingChannels', c_int), - ('NumberOfBoards', c_int), - ('ActiveCurve', c_int), - ('MeasMode', c_int), - ('SubMode', c_int), - ('RangeNo', c_int), - ('Offset', c_int), - ('Tacq', c_int), # in m - ('StopAt', c_int), - ('StopOnOvfl', c_int), - ('Restart', c_int), - ('DispLinLog', c_int), - ('DispTimeFrom', c_int), - ('DispTimeTo', c_int), - ('DispCountsFrom', c_int), - ('DispCountsTo', c_int), - ('DispCurves', tCurveMapping * DISPCURVES), - ('Params', tParamStruct * 3), - ('RepeatMode', c_int), - ('RepeatsPerCurve', c_int), - ('RepeatTime', c_int), - ('RepeatWaitTime', c_int), - ('ScriptName', c_char * 20), + ("Curves", c_int), + ("BitsPerHistoBin", c_int), + ("RoutingChannels", c_int), + ("NumberOfBoards", c_int), + ("ActiveCurve", c_int), + ("MeasMode", c_int), + ("SubMode", c_int), + ("RangeNo", c_int), + ("Offset", c_int), + ("Tacq", c_int), # in m + ("StopAt", c_int), + ("StopOnOvfl", c_int), + ("Restart", c_int), + ("DispLinLog", c_int), + ("DispTimeFrom", c_int), + ("DispTimeTo", c_int), + ("DispCountsFrom", c_int), + ("DispCountsTo", c_int), + ("DispCurves", tCurveMapping * DISPCURVES), + ("Params", tParamStruct * 3), + ("RepeatMode", c_int), + ("RepeatsPerCurve", c_int), + ("RepeatTime", c_int), + ("RepeatWaitTime", c_int), + ("ScriptName", c_char * 20), ] class BoardHdr(Structure): _pack_ = 4 _fields_ = [ - ('HardwareIdent', c_char * 16), - ('HardwareVersion', c_char * 8), - ('HardwareSerial', c_int), - ('SyncDivider', c_int), - ('CFDZeroCross0', c_int), - ('CFDLevel0', c_int), - ('CFDZeroCross1', c_int), - ('CFDLevel1', c_int), - ('Resolution', c_float), - ('RouterModelCode', c_int), - ('RouterEnabled', c_int), - ('RtChan1_InputType;', c_int), - ('RtChan1_InputLevel', c_int), - ('RtChan1_InputEdge', c_int), - ('RtChan1_CFDPresent', c_int), - ('RtChan1_CFDLevel', c_int), - ('RtChan1_CFDZeroCross', c_int), - ('RtChan2_InputType;', c_int), - ('RtChan2_InputLevel', c_int), - ('RtChan2_InputEdge', c_int), - ('RtChan2_CFDPresent', c_int), - ('RtChan2_CFDLevel', c_int), - ('RtChan2_CFDZeroCross', c_int), - ('RtChan3_InputType;', c_int), - ('RtChan3_InputLevel', c_int), - ('RtChan3_InputEdge', c_int), - ('RtChan3_CFDPresent', c_int), - ('RtChan3_CFDLevel', c_int), - ('RtChan3_CFDZeroCross', c_int), - ('RtChan4_InputType;', c_int), - ('RtChan4_InputLevel', c_int), - ('RtChan4_InputEdge', c_int), - ('RtChan4_CFDPresent', c_int), - ('RtChan4_CFDLevel', c_int), - ('RtChan4_CFDZeroCross', c_int), + ("HardwareIdent", c_char * 16), + ("HardwareVersion", c_char * 8), + ("HardwareSerial", c_int), + ("SyncDivider", c_int), + ("CFDZeroCross0", c_int), + ("CFDLevel0", c_int), + ("CFDZeroCross1", c_int), + ("CFDLevel1", c_int), + ("Resolution", c_float), + ("RouterModelCode", c_int), + ("RouterEnabled", c_int), + ("RtChan1_InputType;", c_int), + ("RtChan1_InputLevel", c_int), + ("RtChan1_InputEdge", c_int), + ("RtChan1_CFDPresent", c_int), + ("RtChan1_CFDLevel", c_int), + ("RtChan1_CFDZeroCross", c_int), + ("RtChan2_InputType;", c_int), + ("RtChan2_InputLevel", c_int), + ("RtChan2_InputEdge", c_int), + ("RtChan2_CFDPresent", c_int), + ("RtChan2_CFDLevel", c_int), + ("RtChan2_CFDZeroCross", c_int), + ("RtChan3_InputType;", c_int), + ("RtChan3_InputLevel", c_int), + ("RtChan3_InputEdge", c_int), + ("RtChan3_CFDPresent", c_int), + ("RtChan3_CFDLevel", c_int), + ("RtChan3_CFDZeroCross", c_int), + ("RtChan4_InputType;", c_int), + ("RtChan4_InputLevel", c_int), + ("RtChan4_InputEdge", c_int), + ("RtChan4_CFDPresent", c_int), + ("RtChan4_CFDLevel", c_int), + ("RtChan4_CFDZeroCross", c_int), ] class CurveHdr(Structure): _pack_ = 4 _fields_ = [ - ('CurveIndex', c_int), - ('TimeOfRecording', c_ulong), - ('HardwareIdent', c_char * 16), - ('HardwareVersion', c_char * 8), - ('HardwareSerial', c_int), - ('SyncDivider', c_int), - ('CFDZeroCross0', c_int), - ('CFDLevel0', c_int), - ('CFDZeroCross1', c_int), - ('CFDLevel1', c_int), - ('Offset', c_int), - ('RoutingChannel', c_int), - ('ExtDevices', c_int), - ('MeasMode', c_int), - ('SubMode', c_int), - ('P1', c_float), - ('P2', c_float), - ('P3', c_float), - ('RangeNo', c_int), - ('Resolution', c_float), - ('Channels', c_int), - ('Tacq', c_int), - ('StopAfter', c_int), - ('StopReason', c_int), - ('InpRate0', c_int), - ('InpRate1', c_int), - ('HistCountRate', c_int), - ('IntegralCount', c_int64), - ('reserved', c_int), - ('DataOffset', c_int), - ('RouterModelCode', c_int), - ('RouterEnabled', c_int), - ('RtChan_InputType;', c_int), - ('RtChan_InputLevel', c_int), - ('RtChan_InputEdge', c_int), - ('RtChan_CFDPresent', c_int), - ('RtChan_CFDLevel', c_int), - ('RtChan_CFDZeroCross', c_int), + ("CurveIndex", c_int32), + ("TimeOfRecording", c_uint), + ("HardwareIdent", c_char * 16), + ("HardwareVersion", c_char * 8), + ("HardwareSerial", c_int32), + ("SyncDivider", c_int32), + ("CFDZeroCross0", c_int32), + ("CFDLevel0", c_int32), + ("CFDZeroCross1", c_int32), + ("CFDLevel1", c_int32), + ("Offset", c_int32), + ("RoutingChannel", c_int32), + ("ExtDevices", c_int32), + ("MeasMode", c_int32), + ("SubMode", c_int32), + ("P1", c_float), + ("P2", c_float), + ("P3", c_float), + ("RangeNo", c_int32), + ("Resolution", c_float), + ("Channels", c_int32), + ("Tacq", c_int32), + ("StopAfter", c_int32), + ("StopReason", c_int32), + ("InpRate0", c_int32), + ("InpRate1", c_int32), + ("HistCountRate", c_int32), + ("IntegralCount", c_int64), + ("reserved", c_int32), + ("DataOffset", c_int32), + ("RouterModelCode", c_int32), + ("RouterEnabled", c_int32), + ("RtChan_InputType;", c_int32), + ("RtChan_InputLevel", c_int32), + ("RtChan_InputEdge", c_int32), + ("RtChan_CFDPresent", c_int32), + ("RtChan_CFDLevel", c_int32), + ("RtChan_CFDZeroCross", c_int32), ] -class ParseError(Exception): pass +class ParseError(Exception): + pass def _read(f, CType): @@ -180,8 +186,8 @@ def _read(f, CType): def _validate_header(header): - if not header.Ident == 'PicoHarp 300' or not header.FormatVersion == '2.0': - raise ParseError('Does not look like a PicoHarp 300 file.') + if not header.Ident == "PicoHarp 300" or not header.FormatVersion == "2.0": + raise ParseError("Does not look like a PicoHarp 300 file.") class Curve(object): @@ -189,23 +195,20 @@ class Curve(object): data = None def __repr__(self): - return 'Curve' % ( - self.res, - len(self.data) - ) + return "Curve" % (self.res, len(self.data)) def timefmt(t): d = datetime.datetime.fromtimestamp(t) - return d.strftime('%a %b %d %H:%M:%S %Y') + return d.strftime("%a %b %d %H:%M:%S %Y") class PicoharpParser(object): _ready = False def __init__(self, filename): - if isinstance(filename, (str)):#, unicode)): - filename = open(filename, mode='rb') + if isinstance(filename, (str)): # , unicode)): + filename = open(filename, mode="rb") self.f = filename self._prepare() @@ -214,7 +217,7 @@ def _prepare(self): header = self._header = _read(self.f, TxtHdr) """SJ commented this --- it was giving ParseError""" -# _validate_header(header) + # _validate_header(header) bin_header = self._bin_header = _read(self.f, BinHdr) @@ -240,19 +243,26 @@ def get_curve(self, n): array = np.fromfile(self.f, c_uint, header.Channels) return res, array - + + def get_all_curves(self): + all_curves = [] + for i in range(len(self._curves)): + all_curves.append(self.get_curve(i)) + + return all_curves + def get_time_window_in_ns(self, curve_no): curve = self._curves[curve_no] rep_rate = curve.InpRate0 res, _ = self.get_curve(curve_no) - time_window_s = (1/rep_rate)/res # in seconds - - return time_window_s * 1e9 # in nannoseconds - + time_window_s = (1 / rep_rate) / res # in seconds + + return time_window_s * 1e9 # in nannoseconds + def get_integral_counts(self, curve_no): curve = self._curves[curve_no] integral_counts = curve.IntegralCount - + return integral_counts def info(self): @@ -262,7 +272,7 @@ def info(self): curves = self._curves r = [] w = r.append - yesno = lambda x: 'true' if x else 'false' + yesno = lambda x: "true" if x else "false" w("Ident : %s" % txthdr.Ident) w("Format Version : %s" % txthdr.FormatVersion) @@ -367,7 +377,7 @@ def info(self): w("HardwareSerial : %d" % curve.HardwareSerial) w("SyncDivider : %d" % curve.SyncDivider) w("CFDZeroCross0 : %d" % curve.CFDZeroCross0) - w("CFDLevel0 : %d" % curve.CFDLevel0 ) + w("CFDLevel0 : %d" % curve.CFDLevel0) w("CFDZeroCross1 : %d" % curve.CFDZeroCross1) w("CFDLevel1 : %d" % curve.CFDLevel1) w("Offset : %d" % curve.Offset) @@ -401,8 +411,4 @@ def info(self): w("RtChan_CFDLevel : %d" % curve.RtChan_CFDLevel) w("RtChan_CFDZeroCross : %d" % curve.RtChan_CFDZeroCross) - return '\n'.join(r) - -def read_picoharp_phd(datafile): - parser = PicoharpParser(datafile) - return parser \ No newline at end of file + return "\n".join(r) From 25297c538dd13a57c5f6a1464d3b6185658cb32a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Feb 2021 22:47:48 +0000 Subject: [PATCH 05/10] Bump bleach from 3.1.4 to 3.3.0 Bumps [bleach](https://github.com/mozilla/bleach) from 3.1.4 to 3.3.0. - [Release notes](https://github.com/mozilla/bleach/releases) - [Changelog](https://github.com/mozilla/bleach/blob/master/CHANGES) - [Commits](https://github.com/mozilla/bleach/compare/v3.1.4...v3.3.0) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4d5abd3..f74b5d1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ altgraph==0.16.1 asteval==0.9.14 attrs==19.1.0 backcall==0.1.0 -bleach==3.1.4 +bleach==3.3.0 bokeh==1.3.2 certifi==2019.6.16 Click==7.0 From 440dd45ec230eda0d6bbfb853a3eff88a54ea966 Mon Sep 17 00:00:00 2001 From: Sarthak Jariwala Date: Tue, 16 Feb 2021 16:04:43 -0800 Subject: [PATCH 06/10] FIX: reading error for files with single trace --- PythonGUI_apps/Lifetime_analysis/Lifetime_plot_fit.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/PythonGUI_apps/Lifetime_analysis/Lifetime_plot_fit.py b/PythonGUI_apps/Lifetime_analysis/Lifetime_plot_fit.py index 7967a1e..c7eb5a6 100644 --- a/PythonGUI_apps/Lifetime_analysis/Lifetime_plot_fit.py +++ b/PythonGUI_apps/Lifetime_analysis/Lifetime_plot_fit.py @@ -129,6 +129,10 @@ def open_with_skip_rows_window(self): skip_rows = self.skip_rows_window.ui.skip_rows_spinBox.value() if ".txt" in self.filename[0]: self.file = np.loadtxt(self.filename[0], skiprows=skip_rows) + + if self.file.ndim == 1: # if there is only one trace, reshape to 2D + self.file = self.file.reshape(self.file.shape[0], 1) + elif ".csv" in self.filename[0]: self.file = np.genfromtxt(self.filename[0], skip_header=skip_rows, delimiter=",") @@ -149,6 +153,10 @@ def open_irf_with_skip_rows_window(self): irf_skip_rows = self.irf_skip_rows_window.ui.skip_rows_spinBox.value() if ".txt" in self.irf_filename[0]: self.irf_file = np.loadtxt(self.irf_filename[0], skiprows=irf_skip_rows) + + if self.irf_file.ndim == 1: # if there is only one trace, reshape to 2d array + self.irf_file = self.irf_file.reshape(self.irf_file.shape[0], 1) + elif ".csv" in self.irf_filename[0]: self.irf_file = np.genfrontxt(self.irf_filename[0], skip_header=irf_skip_rows, delimiter=",") From e3efda4df67ff2045f5897593dea09c4aa6d8e7b Mon Sep 17 00:00:00 2001 From: SarthakJariwala Date: Wed, 3 Mar 2021 13:32:29 -0800 Subject: [PATCH 07/10] fix import error --- PythonGUI_apps/Lifetime_analysis/Lifetime_plot_fit.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PythonGUI_apps/Lifetime_analysis/Lifetime_plot_fit.py b/PythonGUI_apps/Lifetime_analysis/Lifetime_plot_fit.py index c7eb5a6..89a32b9 100644 --- a/PythonGUI_apps/Lifetime_analysis/Lifetime_plot_fit.py +++ b/PythonGUI_apps/Lifetime_analysis/Lifetime_plot_fit.py @@ -25,12 +25,12 @@ # local module imports 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.read_ph_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 + from read_ph_phd import read_picoharp_phd """Recylce params for plotting""" plt.rc('xtick', labelsize = 20) From ba958651c0167351fdae954181b9ebc46677f121 Mon Sep 17 00:00:00 2001 From: SarthakJariwala Date: Wed, 3 Mar 2021 13:50:57 -0800 Subject: [PATCH 08/10] dont port to fbs structure --- src/build/settings/base.json | 6 - src/build/settings/linux.json | 6 - src/build/settings/mac.json | 3 - src/main/icons/Icon.ico | Bin 168229 -> 0 bytes src/main/icons/README.md | 11 - src/main/icons/base/16.png | Bin 544 -> 0 bytes src/main/icons/base/24.png | Bin 783 -> 0 bytes src/main/icons/base/32.png | Bin 912 -> 0 bytes src/main/icons/base/48.png | Bin 1497 -> 0 bytes src/main/icons/base/64.png | Bin 1657 -> 0 bytes src/main/icons/linux/1024.png | Bin 26841 -> 0 bytes src/main/icons/linux/128.png | Bin 3177 -> 0 bytes src/main/icons/linux/256.png | Bin 5641 -> 0 bytes src/main/icons/linux/512.png | Bin 11653 -> 0 bytes src/main/icons/mac/1024.png | Bin 47311 -> 0 bytes src/main/icons/mac/128.png | Bin 4978 -> 0 bytes src/main/icons/mac/256.png | Bin 10278 -> 0 bytes src/main/icons/mac/512.png | Bin 21699 -> 0 bytes src/main/python/DataBrowser_GUI.ui | 97 -- .../python/Export_Windows/Export_window.py | 77 - .../Export_Windows/Multi_Trace_Exporter.py | 147 -- .../Export_Windows/Multi_Trace_Exporter.ui | 85 - src/main/python/Export_Windows/__init__.py | 1 - .../python/Export_Windows/export_fig_gui.ui | 171 -- src/main/python/Export_Windows/export_plot.ui | 186 --- src/main/python/FLIM_analysis/FLIM_plot.py | 374 ----- .../python/FLIM_analysis/flim_plot_gui.ui | 185 --- .../FLIM_analysis/step_size_labview_files.ui | 61 - src/main/python/H5_Pkl/h5_pkl_view.py | 124 -- src/main/python/H5_Pkl/h5_pkl_view_gui.ui | 89 - src/main/python/H5_Pkl/h5_tree.py | 100 -- src/main/python/H5_Pkl/h5_view_and_plot.py | 141 -- .../python/H5_Pkl/h5_view_and_plot_gui.ui | 280 ---- src/main/python/H5_Pkl/pkl_tree.py | 103 -- .../python/Image_analysis/Image_analysis.py | 222 --- .../Image_analysis/image_analysis_gui.ui | 208 --- .../python/Lifetime_analysis/Fit_functions.py | 132 -- .../Fit_functions_with_irf.py | 329 ---- .../Lifetime_analysis_gui_layout.ui | 1392 ---------------- .../Lifetime_analysis/Lifetime_plot_fit.py | 653 -------- src/main/python/Lifetime_analysis/__init__.py | 1 - .../python/Lifetime_analysis/picoharp_phd.py | 408 ----- .../python/Lifetime_analysis/read_ph_phd.py | 54 - .../python/Lifetime_analysis/skip_rows.ui | 42 - .../OO_PZstageScan_acquire_gui.ui | 750 --------- .../OceanOptics_acquire/OO_acquire_gui.ui | 240 --- .../OceanOptics_acquire_plot.py | 297 ---- .../PLQE_analysis/column_selection_gui.ui | 107 -- .../python/PLQE_analysis/plqe_analysis.py | 212 --- .../python/PLQE_analysis/plqe_analysis_gui.ui | 150 -- .../Spectrum_analysis/Spectra_fit_funcs.py | 241 --- .../Spectrum_analysis/Spectra_plot_fit.py | 1020 ------------ .../Spectrum_analysis/Spectra_plot_fit_gui.ui | 1464 ----------------- src/main/python/Spectrum_analysis/__init__.py | 1 - .../Spectrum_analysis/analyze_fit_results.ui | 91 - .../pyqtgraph_MATPLOTLIBWIDGET.py | 56 - .../Spectrum_analysis/scan_params_input.ui | 68 - src/main/python/Table/Table_widget.py | 145 -- src/main/python/Table/Table_widget_gui.ui | 70 - src/main/python/Table/__init__.py | 7 - .../python/UV_Vis_analysis/uv_vis_analysis.py | 188 --- .../UV_Vis_analysis/uv_vis_analysis_gui.ui | 268 --- src/main/python/__init__.py | 1 - src/main/python/main.py | 111 -- src/main/python/main_0.py | 15 - src/main/resources/base/DataBrowser_GUI.ui | 97 -- 66 files changed, 11287 deletions(-) delete mode 100644 src/build/settings/base.json delete mode 100644 src/build/settings/linux.json delete mode 100644 src/build/settings/mac.json delete mode 100644 src/main/icons/Icon.ico delete mode 100644 src/main/icons/README.md delete mode 100644 src/main/icons/base/16.png delete mode 100644 src/main/icons/base/24.png delete mode 100644 src/main/icons/base/32.png delete mode 100644 src/main/icons/base/48.png delete mode 100644 src/main/icons/base/64.png delete mode 100644 src/main/icons/linux/1024.png delete mode 100644 src/main/icons/linux/128.png delete mode 100644 src/main/icons/linux/256.png delete mode 100644 src/main/icons/linux/512.png delete mode 100644 src/main/icons/mac/1024.png delete mode 100644 src/main/icons/mac/128.png delete mode 100644 src/main/icons/mac/256.png delete mode 100644 src/main/icons/mac/512.png delete mode 100644 src/main/python/DataBrowser_GUI.ui delete mode 100644 src/main/python/Export_Windows/Export_window.py delete mode 100644 src/main/python/Export_Windows/Multi_Trace_Exporter.py delete mode 100644 src/main/python/Export_Windows/Multi_Trace_Exporter.ui delete mode 100644 src/main/python/Export_Windows/__init__.py delete mode 100644 src/main/python/Export_Windows/export_fig_gui.ui delete mode 100644 src/main/python/Export_Windows/export_plot.ui delete mode 100644 src/main/python/FLIM_analysis/FLIM_plot.py delete mode 100644 src/main/python/FLIM_analysis/flim_plot_gui.ui delete mode 100644 src/main/python/FLIM_analysis/step_size_labview_files.ui delete mode 100644 src/main/python/H5_Pkl/h5_pkl_view.py delete mode 100644 src/main/python/H5_Pkl/h5_pkl_view_gui.ui delete mode 100644 src/main/python/H5_Pkl/h5_tree.py delete mode 100644 src/main/python/H5_Pkl/h5_view_and_plot.py delete mode 100644 src/main/python/H5_Pkl/h5_view_and_plot_gui.ui delete mode 100644 src/main/python/H5_Pkl/pkl_tree.py delete mode 100644 src/main/python/Image_analysis/Image_analysis.py delete mode 100644 src/main/python/Image_analysis/image_analysis_gui.ui delete mode 100644 src/main/python/Lifetime_analysis/Fit_functions.py delete mode 100644 src/main/python/Lifetime_analysis/Fit_functions_with_irf.py delete mode 100644 src/main/python/Lifetime_analysis/Lifetime_analysis_gui_layout.ui delete mode 100644 src/main/python/Lifetime_analysis/Lifetime_plot_fit.py delete mode 100644 src/main/python/Lifetime_analysis/__init__.py delete mode 100644 src/main/python/Lifetime_analysis/picoharp_phd.py delete mode 100644 src/main/python/Lifetime_analysis/read_ph_phd.py delete mode 100644 src/main/python/Lifetime_analysis/skip_rows.ui delete mode 100644 src/main/python/OceanOptics_acquire/OO_PZstageScan_acquire_gui.ui delete mode 100644 src/main/python/OceanOptics_acquire/OO_acquire_gui.ui delete mode 100644 src/main/python/OceanOptics_acquire/OceanOptics_acquire_plot.py delete mode 100644 src/main/python/PLQE_analysis/column_selection_gui.ui delete mode 100644 src/main/python/PLQE_analysis/plqe_analysis.py delete mode 100644 src/main/python/PLQE_analysis/plqe_analysis_gui.ui delete mode 100644 src/main/python/Spectrum_analysis/Spectra_fit_funcs.py delete mode 100644 src/main/python/Spectrum_analysis/Spectra_plot_fit.py delete mode 100644 src/main/python/Spectrum_analysis/Spectra_plot_fit_gui.ui delete mode 100644 src/main/python/Spectrum_analysis/__init__.py delete mode 100644 src/main/python/Spectrum_analysis/analyze_fit_results.ui delete mode 100644 src/main/python/Spectrum_analysis/pyqtgraph_MATPLOTLIBWIDGET.py delete mode 100644 src/main/python/Spectrum_analysis/scan_params_input.ui delete mode 100644 src/main/python/Table/Table_widget.py delete mode 100644 src/main/python/Table/Table_widget_gui.ui delete mode 100644 src/main/python/Table/__init__.py delete mode 100644 src/main/python/UV_Vis_analysis/uv_vis_analysis.py delete mode 100644 src/main/python/UV_Vis_analysis/uv_vis_analysis_gui.ui delete mode 100644 src/main/python/__init__.py delete mode 100644 src/main/python/main.py delete mode 100644 src/main/python/main_0.py delete mode 100644 src/main/resources/base/DataBrowser_GUI.ui diff --git a/src/build/settings/base.json b/src/build/settings/base.json deleted file mode 100644 index 7d4aafc..0000000 --- a/src/build/settings/base.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "app_name": "GLabViz", - "author": "Sarthak Jariwala", - "main_module": "src/main/python/main.py", - "version": "0.0.0" -} \ No newline at end of file diff --git a/src/build/settings/linux.json b/src/build/settings/linux.json deleted file mode 100644 index 7a64c95..0000000 --- a/src/build/settings/linux.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "categories": "Utility;", - "description": "", - "author_email": "", - "url": "" -} \ No newline at end of file diff --git a/src/build/settings/mac.json b/src/build/settings/mac.json deleted file mode 100644 index f7bd610..0000000 --- a/src/build/settings/mac.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "mac_bundle_identifier": "" -} \ No newline at end of file diff --git a/src/main/icons/Icon.ico b/src/main/icons/Icon.ico deleted file mode 100644 index 3312d8606e5ccf262e3e8f3397189f9105049a19..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 168229 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 WW*AAnCk45!Wo2xK?K9y1_WuD=+-6k( diff --git a/src/main/icons/README.md b/src/main/icons/README.md deleted file mode 100644 index c6c4194..0000000 --- a/src/main/icons/README.md +++ /dev/null @@ -1,11 +0,0 @@ -![Sample app icon](linux/128.png) - -This directory contains the icons that are displayed for your app. Feel free to -change them. - -The difference between the icons on Mac and the other platforms is that on Mac, -they contain a ~5% transparent margin. This is because otherwise they look too -big (eg. in the Dock or in the app switcher). - -You can create Icon.ico from the .png files with -[an online tool](http://icoconvert.com/Multi_Image_to_one_icon/). \ No newline at end of file diff --git a/src/main/icons/base/16.png b/src/main/icons/base/16.png deleted file mode 100644 index f7d02dc268f79c183376b30dddf5ea0cf5827dfe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 544 zcmV+*0^j|KP)TmGsBvSN0G7{E%tE_-gl|fEv;0aRau+G)01lU7P2{XIYtU(DY*`z5wZh-t$ z17;xV-$KBe25cyY`gJ9TKA2b zQ`OPi4q_$n+Q`u1Rq4iCqev!2*O%K1W{B_hXa`+h=PJlsp!i@kPT6X zFgMtf4&AdjLi@^LEPz?gPFhert=$)fabl?(6T3=?Mx5-d`oLZ)@tUkX_s2+$#>F(4wKl3YIpWCbP|H9s!5%<RuTU6aQntuM7@! zJkGa2f3Hqhe%Cykf0SrKM}494z#-;LdXvexZ{ zdcbzc3-LACtO$GJb`m+Ec0Hhy4FPV@8I-klM7`(%Jex>U7OJ7wQzqfr0= N002ovPDHLkV1g}~Xzc(1 diff --git a/src/main/icons/base/32.png b/src/main/icons/base/32.png deleted file mode 100644 index 36b25e8602defb2a39c5163a8826612e8623c71a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 912 zcmV;B18@9^P)nVc} zqV&-)A}~WJ5emdiD0(Q3;#A@fR`PP;{{vTJ1{iUxCP-K{Un_#u9&?SH$Yd5ZC`0I*8@X_GI=oM zz9F&f(Al|ODu7)(S+1?l){woPsgFZ>gcIZ6<)+ik{N?9^qM!?Kv&Omu>U|%YET71* zAztmlAb~8t=EF+T*8XL^PN=11b08sU|oDRuTRV9=W z!Be2<7Ze=9KL8L2Y!_v<8w6JTBMu}^TA~VeW=%(3BQD66LBg#bRP7JHTT1|fEuwZq zfe;lZKH6Zw7y=*+*q#OlQx=miQ?1^s0T9IvQRx~BlH6t_{eiylEBD|<07wYUijrXD zfa_c&{O#0GZ3sZijU_7pL<9ygCVAmaiTGH@P)z}#?S^jXsHO(cbAwiOoB%p*P^eA_ zAi@pMNvnDV0K#tAGd$F)R}4UjSRW!o0Asb;k6c#v@c#ht_{ighnspvsSH3sb)(*q9 z{|Mkm->6V^;D)C=)HFv5ooN?KSKX#{gBCzh-O%N&?&zO)E6)*KOX~&&KuEQEQuDQ; zal;6}a@D#3G;ZhsXsR|0V8o4R0I^h40qD7*0idfoE`Vrmr~!Tr m+-P@Y&Y*^FZxxMdb^HaNZoLAL0b_Om0000aA!)0& zq7kJSE%ku$NC7DC9il+u-WDTFy-ei`!#%zv4U(y`HT`KwbS@|kx( zOVLW-O)6QuzuQ;yXsf3q=!YkyC#4)pk{JKDU*G2K)yYbNpK(C4WKcN|E} zx&~n9RsIj?rgxO0SOxLTw?E14_w>ZhR|Md(R*~!L@^@mhzKk3N za3bn#xut+1k7IN1>OCwCzJ0>HxlI9laxk!FV~fC8-=IBM216@Y0%LBPW>6k7Mg=(y6pxij`_7J%*d zdHCL^e9we}WG~+5EA%o3AbGBe_|{#bTsZ9x00|HN;3&OXH3PtM3DwxscT{?9;CM~>s|3`Q3IkvjKc4%ri~m zWMWS<<*L)F_qrHxRSIXeNR4qc@qG=RgM}UtDG}?FRH&DL3#g?5NaI07E1ZA=3)k7+ zwHX0;%mF|WRPr~+nw@9>v}&+)adlipH8?^FkJrRl7}SVZ;0gsUqyS%+2CiBLE~o(U zVi7!373L<3j6X@u=KwK$K~itQ>?ipVLX*kZXXH(zW)}%ZG#@bAApj$~VrtN+KoX~` zDOKhbuxwCtLSsZ%&8msD*}qDq)I&{KM*{S^bO3osi>RSo6hS(R&8*nlMj-%O_fcjZ z=KWyqg?!eHk*7d0HFPCn0wm7`LA@kedOh4UYduDNbZ&Hd!IJeX#jF5mFW>=;YAh_q z-n-x->EHcSxy;!lZ{^Iav zRy+@;O>CF-k}Xu|WqgfHt-c$jJk!eSj#wz)Q`X-N%dnU*&RZxKy~THG9;XHw;|ZuZP2vrwU2S3|^&U1sNK^$*Qb zTPWXClvXuFpxgvb=6qqpc4N z>kU+6pd2;IRDiLb<@JgHu*mcVE@*R60b8t>1t1evld1v$c@xGJ07f;~q_Vo#j)lx7 zjB5ZgVMh%M1tyVo(+T4`02aBE$}RwmlDzI3fJ~T8ssa|WJz*9V0E(o;l^1Q8j&1j-5CD?n7x3Ru@rhH00000NkvXXu0mjf7k9Up diff --git a/src/main/icons/base/64.png b/src/main/icons/base/64.png deleted file mode 100644 index 4b0a42323f9d02912c17717cc21d9d6bb6f5c613..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1657 zcmV-<28Q{GP)OaPm)zxYuRG&^ZnM{1a=Sabx3e2*pEB)SyP2K; z?|Ei+?zm#)fd-F;xA$z7b}9WDjoj)Z!)y=z`{k* zbHkzr{`F*}r?XW$0Np*%Ivox(3hnfjDRQiPPvW=#1%OLuqrL0G_%L+)TpXW5JC{t+ zms;Z%z z@gF@0Fmx>3+_P2w7P{MfDqhzge_c{{-QK@2?HPcX!RUhl1;2s3Tk}?$hUM@yEQgPT z0)U-+1;QB^z{cY&pUvkeZMmR|QE?rX|nzHI|KZ7(Jgf#r?7{JwUqL0Wjehs<}tL`}& zK+5`@-=%9xK#PfK=?f85)ssM2LH>$B5#th~ z?ZoV=&qv3}leg_pT(JZIu0IB@zY5(tvk_C@N0C&QM^!g3`dUApgc4F1(N1D@=^Uit zIY`5MWdRI-5N_}4kl%&wy*xK&07zUx!CR6j(3bJ1`Z?H#CZL3B!~;OrnXyUo#I1W1 zmx};m=cA8H5`G1`>v%`e00a=*f`i)@k+Lc6-BW?>y)h_$3L5;P)zl6Erg=!i^N@z4 zIQC`z8L5WcV|Naz9{|97p!izekb&ip6#ryUQNeYbkgoBt4?t5EoyFrXcB*twS1NW&^d6XA^T?y%8iB)zu;B?K&m2zlpydR)RfdVwY=7h zTs9oBfgsvq0d06GVY zG*ECY{R=?2>I{NY0JH|GYtvFxRwl070-#YaPXkLrj%|u;IZ%5MwhMrniW=c}w&`Jm z3oKl>4?yQ&kp_~G3;TjP%hJFHf)_xc@LKq@nG{ZbWyR^bV*nZj>%BpZ%P2oq4euD? zoRBv+!3qGCV0uv~zm6IgZqxP30O%Yzo)m0pkeGs0*dJ6lHPQvS@HxWrbd&;sN|<-- zBUZM-!8%7Z$UICATz3V4MuGp_P%JSIXZH-WoK!g7$yKn6GKBK7#x;<0*WCl4bFg`e ztXLJg?m7UCg8ekumP@XS0ibgbl?E4G7Yab5;9?pCxGo-m&Y{vY2y)#s02&1cX%OtX zw*YhwylL>jbzcBz6im|Kq3iwu&^fG3gU7D>4nU)zr6CZ`BClOv8vvG6$p<1g0NeoJ z4gmILf|lzPz!d)U(T&Y_ZVs{U_wB#V0Gu2i%?}ix#Un3tJ`f5>N07&9@BzS7CPxo7 z9gKa5PJ=)I zhOXNJz$7vaf&iGhZX19u5ovH40GI2I0Wi6p1{VQvyIv6hU2dhp1pu~PcLe~G8)>Kz zfFsvk1E8ytG&lrMk?XDlU~-xU2LLK|T?hbOcGF-3fNQP`1;At{4V(a6bzL|BT|8;9 z03gJ5&j2u4rNIP%FxNc=pvx!?WdI0w-4_5%OKD&b_89;;1+E_w?YeIObU_;SK^oYy zXjuZlTps|}PkB8T-~QW^mWJvh;f{H9;U8~ZU#|ZE!$D!oHJQ}700000NkvXXu0mjf DAnWoV diff --git a/src/main/icons/linux/1024.png b/src/main/icons/linux/1024.png deleted file mode 100644 index 2248377200ccb2f2bfb3e721089213cc7b47451e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26841 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 fqxwknp~-~COe_WRi9&kd-X~+QpfOe)k z9)i}?{~JgIGi+D{iXkp4s&3ef^c0GV9zP;DnB?ZRHyt~v`0nx=J#8R= zsbMMc{n*&tZ1(uK(cvW?6!yOQowW3ijRNy2FCo12&El)?ubNJSi~s)ODSrzQTk*HB z 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 diff --git a/src/main/icons/linux/256.png b/src/main/icons/linux/256.png deleted file mode 100644 index 578fdc787602fd2e6e5608e8dcba51f1eb638e68..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5641 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 WbgP9LPnVs<-})a=`f`{6 diff --git a/src/main/icons/linux/512.png b/src/main/icons/linux/512.png deleted file mode 100644 index 0fbac4f9c0612b5a799a1544a857315ab8ef20c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11653 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 SOW=iR2z%V(SpHGxoBszVs1fA= diff --git a/src/main/icons/mac/1024.png b/src/main/icons/mac/1024.png deleted file mode 100644 index c1c8691dd52d9a3cfa4ed7d8bcfe92be80f4e835..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 47311 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 BTt)x@ diff --git a/src/main/icons/mac/128.png b/src/main/icons/mac/128.png deleted file mode 100644 index de9bee605f235a04db3a1848323f0780ddd0c27a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4978 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

XL;iL7 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 - - MainWindow - - - - 0 - 0 - 435 - 221 - - - - GLabViz - DataBrowser - - - - - - - - 30 - 75 - true - - - - GLabViz - - - Qt::AlignCenter - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - 15 - - - - Analysis Tool : - - - - - - - - 15 - - - - - - - - - 15 - - - - Load - - - - - - - - - 0 - 0 - 435 - 21 - - - - - - - - diff --git a/src/main/python/Export_Windows/Export_window.py b/src/main/python/Export_Windows/Export_window.py deleted file mode 100644 index 0e5cc63..0000000 --- a/src/main/python/Export_Windows/Export_window.py +++ /dev/null @@ -1,77 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Sun Nov 3 20:43:12 2019 - -@author: sarth -""" -from pathlib import Path -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui - -"""Export Images GUI""" -base_path = Path(__file__).parent -ui_file_path = (base_path / "export_fig_gui.ui").resolve() -exportFig_WindowTemplate, exportFig_TemplateBaseClass = pg.Qt.loadUiType(ui_file_path) - -class ExportFigureWindow(exportFig_TemplateBaseClass): - - export_fig_signal = QtCore.pyqtSignal() - - def __init__(self): - exportFig_TemplateBaseClass.__init__(self) - - self.ui = exportFig_WindowTemplate() - self.ui.setupUi(self) - self.ui.cmap_comboBox.addItems(['viridis', 'plasma', 'inferno', 'magma', - 'cividis','Greys', 'Purples', 'Blues', - 'Greens', 'Oranges', 'Reds', 'YlOrBr', - 'YlOrRd', 'OrRd', 'PuRd', 'RdPu', 'BuPu', - 'GnBu', 'PuBu', 'YlGnBu', 'PuBuGn', 'BuGn', - 'YlGn', 'binary', 'gist_yarg', 'gist_gray', - 'gray', 'bone', 'pink', 'spring', 'summer', - 'autumn', 'winter', 'cool', 'Wistia', 'hot', - 'afmhot', 'gist_heat', 'copper', 'rainbow', 'jet']) - self.ui.cbar_checkBox.stateChanged.connect(self.cbar_title_state) - self.ui.exportFig_pushButton.clicked.connect(self.export) - self.show() - - def cbar_title_state(self): - if self.ui.cbar_checkBox.isChecked(): - self.ui.cbar_label.setEnabled(True) - else: - self.ui.cbar_label.setEnabled(False) - - def export(self): - self.export_fig_signal.emit() - self.close() - -"""Export plot GUI""" -ui_file_path = (base_path / "export_plot.ui").resolve() -export_WindowTemplate, export_TemplateBaseClass = pg.Qt.loadUiType(ui_file_path) - -class ExportPlotWindow(export_TemplateBaseClass): - - export_fig_signal = QtCore.pyqtSignal() - - def __init__(self): - export_TemplateBaseClass.__init__(self) - - self.ui = export_WindowTemplate() - self.ui.setupUi(self) - #self.ui.traceColor_comboBox.addItems(["C0","C1","C2","C3","C4","C5","C6","C7", "r", "g", "b", "y", "k"]) - #self.ui.fitColor_comboBox.addItems(["k", "r", "b", "y", "g","C0","C1","C2","C3","C4","C5","C6","C7"]) - self.ui.export_pushButton.clicked.connect(self.export) - #self.ui.legend_checkBox.stateChanged.connect(self.legend_title) - self.show() - - #def legend_title(self): - # if self.ui.legend_checkBox.isChecked(): - # self.ui.legend1_lineEdit.setEnabled(True) - # self.ui.legend2_lineEdit.setEnabled(True) - # else: - # self.ui.legend1_lineEdit.setEnabled(False) - # self.ui.legend2_lineEdit.setEnabled(False) - - def export(self): - self.export_fig_signal.emit() - self.close() \ No newline at end of file diff --git a/src/main/python/Export_Windows/Multi_Trace_Exporter.py b/src/main/python/Export_Windows/Multi_Trace_Exporter.py deleted file mode 100644 index 5d5555d..0000000 --- a/src/main/python/Export_Windows/Multi_Trace_Exporter.py +++ /dev/null @@ -1,147 +0,0 @@ -import pyqtgraph as pg -from pathlib import Path -from pyqtgraph.Qt import QtCore, QtGui, QtWidgets -try: - from Lifetime_analysis.read_ph_phd import read_picoharp_phd, get_x_y -except Exception as e: - print(e) -import matplotlib.pyplot as plt - -"""Recylce params for plotting""" -plt.rc('xtick', labelsize = 20) -plt.rc('xtick.major', pad = 3) -plt.rc('ytick', labelsize = 20) -plt.rc('lines', lw = 2.5, markersize = 7.5) -plt.rc('legend', fontsize = 20) -plt.rc('axes', linewidth=3.5) - -pg.mkQApp() - -base_path = Path(__file__).parent -file_path = (base_path / "Multi_Trace_Exporter.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) - - self.temp_layout = pg.GraphicsLayoutWidget() - - # file system tree - self.fs_model = QtWidgets.QFileSystemModel() - self.fs_model.setRootPath(QtCore.QDir.currentPath()) - self.ui.treeView.setModel(self.fs_model) - self.ui.treeView.setIconSize(QtCore.QSize(25,25)) - self.ui.treeView.setSortingEnabled(True) - - self.tree_selectionModel = self.ui.treeView.selectionModel() - self.tree_selectionModel.selectionChanged.connect(self.on_treeview_selection_change) - - self.ui.comboBox.currentIndexChanged.connect(self.add_trace_to_temp_plot) - self.ui.add_pushButton.clicked.connect(self.add_trace_to_mem) - self.ui.export_pushButton.clicked.connect(self.pub_ready_plot_export) - - self.x_i = [] - self.y_i = [] - self.x_mem = [] - self.y_mem = [] - self.legend = [] - - self.show() - - def on_treeview_selection_change(self): - try: - fname = self.fs_model.filePath(self.tree_selectionModel.currentIndex()) - _ , ext = fname.rsplit('.',1) - - self.ui.comboBox.clear() - self.ui.textBrowser.clear() - self.x_i = [] - self.y_i = [] - - if ext in ['phd']: - self.parser = read_picoharp_phd(fname) - curve_list = [] - - for i in range(self.parser.no_of_curves()): - curve_list.append("Curve "+str(i)) - x, y = get_x_y(i, self.parser, smooth_trace=self.ui.smooth_checkBox.isChecked(), boxwidth=self.ui.smooth_spinBox.value()) - self.x_i.append(x) - self.y_i.append(y) - - self.ui.comboBox.addItems(curve_list) - self.ui.textBrowser.setText(str(self.parser.info())) - - else: - self.ui.textBrowser.setText(str("Select a PicoHarp File")) - except Exception as e: - print(e) - - def add_trace_to_temp_plot(self): - try: - #self.temp_layout = pg.GraphicsLayoutWidget() - self.temp_layout.clear() - self.temp_plot = self.temp_layout.addPlot(title = "Current Selection") - self.temp_plot.plot(self.x_i[self.ui.comboBox.currentIndex()], self.y_i[self.ui.comboBox.currentIndex()], pen='r') - self.temp_plot.setLogMode(False, True) - self.temp_layout.show() - except Exception as e: - print(e) - - def add_trace_to_mem(self): - try: - self.x_mem.append(self.x_i[self.ui.comboBox.currentIndex()]) - self.y_mem.append(self.y_i[self.ui.comboBox.currentIndex()]) - self.legend.append(self.ui.lineEdit.text()) - except Exception as e: - print(e) - - def pub_ready_plot_export(self): - 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) - for i in range(len(self.x_mem)): - if self.ui.Normalize_checkBox.isChecked(): - plt.plot(self.x_mem[i], self.y_mem[i]/max(self.y_mem[i]), label=str(self.legend[i])) - else: - plt.plot(self.x_mem[i], self.y_mem[i], label=str(self.legend[i])) - - plt.yscale('log') - plt.xlabel("Time (ns)", fontsize=20, fontweight='bold') - plt.ylabel("Intensity (norm.)", fontsize=20, fontweight='bold') - plt.legend() - plt.tight_layout() - - plt.savefig(filename[0],bbox_inches='tight', dpi=300) - plt.close() - - self.clear_memory() - - except Exception as e: - print(e) - pass - - def clear_memory(self): - self.x_mem = [] - self.y_mem = [] - self.legend = [] - - - - -def run(): - win = MainWindow() - QtGui.QApplication.instance().exec_() - return win - -#run() \ No newline at end of file diff --git a/src/main/python/Export_Windows/Multi_Trace_Exporter.ui b/src/main/python/Export_Windows/Multi_Trace_Exporter.ui deleted file mode 100644 index 7b59ef1..0000000 --- a/src/main/python/Export_Windows/Multi_Trace_Exporter.ui +++ /dev/null @@ -1,85 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 1108 - 1063 - - - - MainWindow - - - - - - - - - - Smoothen Trace - - - - - - - Enter Trace Legend Here - - - - - - - Add - - - - - - - Normalize (for export) - - - - - - - 1 - - - - - - - Export - - - - - - - - - - - - - - - 0 - 0 - 1108 - 38 - - - - - - - - diff --git a/src/main/python/Export_Windows/__init__.py b/src/main/python/Export_Windows/__init__.py deleted file mode 100644 index 7c68785..0000000 --- a/src/main/python/Export_Windows/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- \ No newline at end of file diff --git a/src/main/python/Export_Windows/export_fig_gui.ui b/src/main/python/Export_Windows/export_fig_gui.ui deleted file mode 100644 index e80e065..0000000 --- a/src/main/python/Export_Windows/export_fig_gui.ui +++ /dev/null @@ -1,171 +0,0 @@ - - - ExportFigure - - - - 0 - 0 - 420 - 369 - - - - Form - - - - - - - 15 - - - - 1000000000 - - - - - - - - 15 - - - - Color Bar Label - - - - - - - - 15 - - - - Data Channel to Save - - - - - - - - 15 - - - - - - - - - 15 - - - - - - - - - 15 - - - - ColorMap - - - - - - - - 15 - - - - 1000000000 - - - - - - - - 15 - - - - ColorBar Min - - - - - - - - 15 - - - - ColorBar Max - - - - - - - false - - - - 15 - - - - - - - - - 15 - - - - Export Figure - - - - - - - - 15 - - - - Reversed - - - - - - - - 15 - - - - - - - - - - - - diff --git a/src/main/python/Export_Windows/export_plot.ui b/src/main/python/Export_Windows/export_plot.ui deleted file mode 100644 index 840264f..0000000 --- a/src/main/python/Export_Windows/export_plot.ui +++ /dev/null @@ -1,186 +0,0 @@ - - - Form - - - - 0 - 0 - 930 - 435 - - - - Form - - - - - - - 10 - - - - 4 - - - -100.000000000000000 - - - 10000000000000000000000.000000000000000 - - - 1.500000000000000 - - - - - - - - 10 - - - - Lower - - - - - - - - 10 - - - - Lower - - - - - - - - 10 - - - - Upper - - - - - - - - 15 - - - - Export Graph - - - - - - - - 12 - - - - Y limits - - - - - - - - 10 - - - - Upper - - - - - - - - 12 - - - - X limits - - - - - - - - 10 - - - - 4 - - - -10000.000000000000000 - - - 1000000000000000000.000000000000000 - - - 0.010000000000000 - - - - - - - - 10 - - - - 4 - - - -10000000.000000000000000 - - - 100000000000.000000000000000 - - - - - - - - 10 - - - - 4 - - - -1000000000.000000000000000 - - - 10000000000000.000000000000000 - - - 10000.000000000000000 - - - - - - - - diff --git a/src/main/python/FLIM_analysis/FLIM_plot.py b/src/main/python/FLIM_analysis/FLIM_plot.py deleted file mode 100644 index 5f5744f..0000000 --- a/src/main/python/FLIM_analysis/FLIM_plot.py +++ /dev/null @@ -1,374 +0,0 @@ -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 - -sys.path.append(os.path.abspath('../Lifetime_analysis')) -sys.path.append(os.path.abspath('../Spectrum_analysis')) -sys.path.append(os.path.abspath('../H5_Pkl')) -sys.path.append(os.path.abspath('../Export_Windows')) -from Lifetime_analysis import Lifetime_plot_fit -from Spectrum_analysis import Spectra_plot_fit -from H5_Pkl import h5_pkl_view -try: - from Export_window import ExportFigureWindow -except: - from Export_Windows.Export_window import ExportFigureWindow -# local modules - -pg.mkQApp() -pg.setConfigOption('background', 'w') - - -base_path = Path(__file__).parent -file_path = (base_path / "flim_plot_gui.ui").resolve() - -uiFile = file_path - -WindowTemplate, TemplateBaseClass = pg.Qt.loadUiType(uiFile) - -def updateDelay(scale, time): - """ Hack fix for scalebar inaccuracy """ - QtCore.QTimer.singleShot(time, scale.updateBar) - -class MainWindow(TemplateBaseClass): - - hist_data_signal = QtCore.pyqtSignal() - - def __init__(self): - pg.setConfigOption('imageAxisOrder', 'row-major') - 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_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.export_window) - 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.ui.analyze_lifetime_pushButton.clicked.connect(self.on_analyze_lifetime) - self.ui.analyze_psf_pushButton.clicked.connect(self.on_analyze_psf) - - self.show() - - def open_file(self): - """ Open FLIM scan file """ - try: - self.filename = QtWidgets.QFileDialog.getOpenFileName(self, filter="Scan files (*.pkl *.h5 *.txt)") - if ".pkl" in self.filename[0]: - self.flim_scan_file = pickle.load(open(self.filename[0], 'rb')) - self.scan_file_type = "pkl" - self.launch_h5_pkl_viewer() - self.get_data_params() - elif ".h5" in self.filename[0]: - self.flim_scan_file = h5py.File(self.filename[0], 'r') - self.scan_file_type = "h5" - self.launch_h5_pkl_viewer() - self.get_data_params() - elif ".txt" in self.filename[0]: - self.intensity_sums = np.loadtxt(self.filename[0]).T - self.stepsize_window = StepSizeWindow() - self.stepsize_window.stepsize_signal.connect(self.get_stepsize) - self.scan_file_type = "txt" - # self.pkl_file = pickle.load(open(self.filename[0], 'rb')) - except Exception as err: - print(format(err)) - - def launch_h5_pkl_viewer(self): - """ Launches H5/PKL viewer to give an insight into the data and its structure""" - viewer_window = h5_pkl_view.H5PklView(sys.argv) - viewer_window.settings['data_filename'] = self.filename[0] - - 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 get_stepsize(self): - """ Get step size from user input -- specfically written for loading - txt files from legacy labview code, but can also be run on txt file - saved using the new FLIM acquistion code """ - self.stepsize = self.stepsize_window.ui.stepsize_doubleSpinBox.value() - self.x_step_size = self.stepsize - self.y_step_size = self.stepsize - - def get_data_params(self): - - data = self.flim_scan_file - if self.scan_file_type == "pkl": - self.x_scan_size = data['Scan Parameters']['X scan size (um)'] - self.y_scan_size = data['Scan Parameters']['Y scan size (um)'] - self.x_step_size = data['Scan Parameters']['X step size (um)'] - self.y_step_size = data['Scan Parameters']['Y step size (um)'] - self.hist_data = data['Histogram data'] - self.time_data = data['Time data'] - else: #run this if scan file is h5 - self.x_scan_size = data['Scan Parameters'].attrs['X scan size (um)'] - self.y_scan_size = data['Scan Parameters'].attrs['Y scan size (um)'] - self.x_step_size = data['Scan Parameters'].attrs['X step size (um)'] - self.y_step_size = data['Scan Parameters'].attrs['Y step size (um)'] - self.hist_data = data['Histogram data'][()] #get dataset values - self.time_data = data['Time data'][()] - - self.numb_x_pixels = int(self.x_scan_size/self.x_step_size) - self.numb_y_pixels = int(self.y_scan_size/self.y_step_size) - - - def plot_intensity_sums(self): - try: - if self.scan_file_type is "pkl" or self.scan_file_type is "h5": - pg.setConfigOption('imageAxisOrder', 'row-major') - self.hist_data = np.reshape(self.hist_data, newshape=(self.hist_data.shape[0], self.numb_x_pixels*self.numb_y_pixels)) - self.intensity_sums = np.sum(self.hist_data, axis=0) #sum intensities for each pixel - self.intensity_sums = np.reshape(self.intensity_sums, newshape=(self.numb_x_pixels, self.numb_y_pixels)) - else: - pg.setConfigOption('imageAxisOrder', 'col-major') - self.ui.intensity_sums_viewBox.view.invertY(False) # stop y axis invert - self.ui.intensity_sums_viewBox.setImage(self.intensity_sums, scale= - (self.x_step_size, - self.y_step_size)) - if self.scan_file_type is "pkl" or self.scan_file_type is "h5": - 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)) - self.ui.intensity_sums_viewBox.view.sigRangeChanged.connect(lambda: updateDelay(scale, 10)) - 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.mean(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 on_analyze_psf(self): - self.spectrum_window = Spectra_plot_fit.MainWindow() - self.spectrum_window.show() - self.spectrum_window.opened_from_flim = True - sum_data = self.ui.intensity_sums_viewBox.getRoiPlot().getPlotItem().curves[0].getData() - self.spectrum_window.sum_data_from_flim = np.asarray(sum_data) - self.spectrum_window.ui.plot_without_bck_radioButton.setChecked(True) - self.spectrum_window.ui.result_textBrowser.setText("Data successfully loaded from FLIM analysis.") - - def plot_raw_scan(self): - try: - self.hist_image = np.reshape(self.hist_data, newshape=(self.hist_data.shape[0],self.numb_x_pixels,self.numb_y_pixels)) - self.times = self.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= - (self.x_step_size, - self.y_step_size), 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)) - self.ui.raw_hist_data_viewBox.view.sigRangeChanged.connect(lambda: updateDelay(scale, 10)) - - 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"): #make sure second plot is properly cleared everytime - self.roi2_plot.clear() - c = self.ui.raw_hist_data_viewBox.getRoiPlot().getPlotItem().curves.pop() - c.scene().removeItem(c) - self.roi2_plot = self.ui.raw_hist_data_viewBox.getRoiPlot().plot(xvals, data, pen='r') - - def get_raw_hist_curve(self, curve_index): - #curve_index = 0 for original roi - #curve_index = 1 for second comparison roi - curves = self.ui.raw_hist_data_viewBox.getRoiPlot().getPlotItem().curves - return curves[curve_index].getData() - - def on_analyze_lifetime(self): - self.lifetime_window = Lifetime_plot_fit.MainWindow() - self.lifetime_window.show() - self.lifetime_window.opened_from_flim = True - self.lifetime_window.hist_data_from_flim = np.asarray(self.get_raw_hist_curve(0)) - self.lifetime_window.ui.Result_textBrowser.setText("Data successfully loaded from FLIM analysis.") - - def export_window(self): - self.export_window = ExportFigureWindow() - self.export_window.ui.vmin_spinBox.setValue(np.min(self.intensity_sums)) - self.export_window.ui.vmax_spinBox.setValue(np.max(self.intensity_sums)) - self.export_window.export_fig_signal.connect(self.save_intensities_image) - - def save_intensities_image(self): - try: - 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 - save_to = folder + "\\" + filename + "_intensity_sums.png" - if self.export_window.ui.reverse_checkBox.isChecked(): - colormap = str(self.export_window.ui.cmap_comboBox.currentText())+"_r" - else: - colormap = str(self.export_window.ui.cmap_comboBox.currentText()) - if self.export_window.ui.cbar_checkBox.isChecked(): - label = str(self.export_window.ui.cbar_label.text()) - else: - label = "PL Intensity (a.u.)" - cpm.plot_confocal(self.intensity_sums, FLIM_adjust=False, - stepsize=np.abs(self.x_step_size),cmap=colormap, - cbar_label=label, vmin=self.export_window.ui.vmin_spinBox.value(), - vmax=self.export_window.ui.vmax_spinBox.value()) - plt.savefig(save_to, bbox_inches='tight', dpi=300) - except Exception as e: - print(format(e)) - - def save_intensities_array(self): - try: - 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 - save_to = folder + "\\" + 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 - -"""Skip rows GUI""" -ui_file_path = (base_path / "step_size_labview_files.ui").resolve() -stepsize_WindowTemplate, stepsize_TemplateBaseClass = pg.Qt.loadUiType(ui_file_path) - -class StepSizeWindow(stepsize_TemplateBaseClass): - - stepsize_signal = QtCore.pyqtSignal() #signal to help with pass info back to MainWindow - - def __init__(self): - stepsize_TemplateBaseClass.__init__(self) - - # Create the param window - self.ui = stepsize_WindowTemplate() - self.ui.setupUi(self) - self.ui.done_pushButton.clicked.connect(self.done) - self.setWindowFlag(QtCore.Qt.WindowCloseButtonHint, False) - self.show() - - def done(self): - self.stepsize_signal.emit() - self.close() - -"""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/src/main/python/FLIM_analysis/flim_plot_gui.ui b/src/main/python/FLIM_analysis/flim_plot_gui.ui deleted file mode 100644 index bbfef2a..0000000 --- a/src/main/python/FLIM_analysis/flim_plot_gui.ui +++ /dev/null @@ -1,185 +0,0 @@ - - - Form - - - - 0 - 0 - 738 - 876 - - - - FLIM Analysis - - - - - - 0 - - - - Analysis - - - - - - - - Load Scan - - - - - - - - - Plot - - - - - - - Save intensities array - - - - - - - Save intensities image - - - - - - - Analyze PSF - - - - - - - - - - 500 - 300 - - - - - - - - - 12 - - - - Histogram Intensity Sums - - - - - - - - 12 - - - - Raw Histogram Data - - - - - - - - 500 - 300 - - - - - - - - - - Plot - - - - - - - Analyze lifetime - - - - - - - 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/src/main/python/FLIM_analysis/step_size_labview_files.ui b/src/main/python/FLIM_analysis/step_size_labview_files.ui deleted file mode 100644 index 2078fb7..0000000 --- a/src/main/python/FLIM_analysis/step_size_labview_files.ui +++ /dev/null @@ -1,61 +0,0 @@ - - - Form - - - - 0 - 0 - 255 - 75 - - - - Form - - - - - - - 12 - - - - Step Size (um) - - - - - - - Done! - - - - - - - - 12 - - - - 4 - - - 100.000000000000000 - - - 0.100000000000000 - - - 0.100000000000000 - - - - - - - - diff --git a/src/main/python/H5_Pkl/h5_pkl_view.py b/src/main/python/H5_Pkl/h5_pkl_view.py deleted file mode 100644 index 7be99a5..0000000 --- a/src/main/python/H5_Pkl/h5_pkl_view.py +++ /dev/null @@ -1,124 +0,0 @@ -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/src/main/python/H5_Pkl/h5_pkl_view_gui.ui b/src/main/python/H5_Pkl/h5_pkl_view_gui.ui deleted file mode 100644 index d147ddc..0000000 --- a/src/main/python/H5_Pkl/h5_pkl_view_gui.ui +++ /dev/null @@ -1,89 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 856 - 925 - - - - H5/pkl View - - - - - - - Qt::Horizontal - - - - File - - - - - - - - - File: - - - - - - - Browse... - - - - - - - true - - - 0 - - - - Data View - - - - - - Once a file is loaded, data will show up here. - - - - - - - - - - - - - - - - - 0 - 0 - 856 - 21 - - - - - - - - diff --git a/src/main/python/H5_Pkl/h5_tree.py b/src/main/python/H5_Pkl/h5_tree.py deleted file mode 100644 index 6d82566..0000000 --- a/src/main/python/H5_Pkl/h5_tree.py +++ /dev/null @@ -1,100 +0,0 @@ -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("") - return self.f - - 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/src/main/python/H5_Pkl/h5_view_and_plot.py b/src/main/python/H5_Pkl/h5_view_and_plot.py deleted file mode 100644 index 18f3635..0000000 --- a/src/main/python/H5_Pkl/h5_view_and_plot.py +++ /dev/null @@ -1,141 +0,0 @@ -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 - - - -class H5ViewPlot(BaseApp): - - name = "h5_view_plot" - - def __init__(self, argv): - pg.setConfigOption('imageAxisOrder', 'row-major') - 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.f = 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: - if self.f['Cube/Info/Cube'].attrs['AcqMode'] == b'Hyperspectral Acquisition': # This works for our PhotonEtc. Hyperspectral Camera output - x_start = int(self.f['Cube/Info/Cube'].attrs['LowerWavelength']) - x_end = int(self.f['Cube/Info/Cube'].attrs['UpperWavelength']) - else: - 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/src/main/python/H5_Pkl/h5_view_and_plot_gui.ui b/src/main/python/H5_Pkl/h5_view_and_plot_gui.ui deleted file mode 100644 index a7bcc01..0000000 --- a/src/main/python/H5_Pkl/h5_view_and_plot_gui.ui +++ /dev/null @@ -1,280 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 856 - 925 - - - - H5 View and Plot - - - - - - - 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 - 21 - - - - - - - - PlotWidget - QGraphicsView -
pyqtgraph
-
- - ImageView - QGraphicsView -
pyqtgraph
-
-
- - -
diff --git a/src/main/python/H5_Pkl/pkl_tree.py b/src/main/python/H5_Pkl/pkl_tree.py deleted file mode 100644 index e60a85e..0000000 --- a/src/main/python/H5_Pkl/pkl_tree.py +++ /dev/null @@ -1,103 +0,0 @@ -from ScopeFoundry.data_browser import DataBrowserView -from qtpy import QtWidgets -import h5py -import pickle -import numpy as np -import lmfit - -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) - elif type(value) == lmfit.model.ModelResult: - value = "lmfit.model.ModelResult" - # 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/src/main/python/Image_analysis/Image_analysis.py b/src/main/python/Image_analysis/Image_analysis.py deleted file mode 100644 index 50b2d1c..0000000 --- a/src/main/python/Image_analysis/Image_analysis.py +++ /dev/null @@ -1,222 +0,0 @@ -import sys -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 -from PIL import Image - -# local modules - -pg.mkQApp() - - -base_path = Path(__file__).parent -file_path = (base_path / "image_analysis_gui.ui").resolve() - -uiFile = file_path - -WindowTemplate, TemplateBaseClass = pg.Qt.loadUiType(uiFile) - -def updateDelay(scale, time): - """ Hack fix for scalebar inaccuracy""" - QtCore.QTimer.singleShot(time, scale.updateBar) - -class MainWindow(TemplateBaseClass): - - def __init__(self): - pg.setConfigOption('imageAxisOrder', 'col-major') - super(TemplateBaseClass, self).__init__() - - # Create the main window - self.ui = WindowTemplate() - self.ui.setupUi(self) - - #setup image plot - self.image_plot_layout=pg.GraphicsLayoutWidget() - self.ui.image_groupBox.layout().addWidget(self.image_plot_layout) - self.image_plot = self.image_plot_layout.addPlot() - self.img_item = pg.ImageItem() - self.image_plot.addItem(self.img_item) - self.image_plot_view = self.image_plot.getViewBox() - - #setup lookup table - self.hist_lut = pg.HistogramLUTItem() - self.image_plot_layout.addItem(self.hist_lut) - - #region of interest - allows user to select scan area - self.roi = pg.ROI([0,0],[10, 10], movable=True) - self.roi.addScaleHandle([1, 1], [0, 0]) - self.roi.addRotateHandle([0, 0], [1, 1]) - self.roi.translateSnap = True - self.roi.scaleSnap = True - self.roi.sigRegionChanged.connect(self.line_profile_update_plot) - self.image_plot.addItem(self.roi) - - #setup rgb plot - self.rgb_plot_layout=pg.GraphicsLayoutWidget() - self.ui.rgb_plot_groupBox.layout().addWidget(self.rgb_plot_layout) - self.rgb_plot = self.rgb_plot_layout.addPlot() - - #set up ui signals - self.ui.load_image_pushButton.clicked.connect(self.load_image) - self.ui.custom_pixel_size_checkBox.stateChanged.connect(self.switch_custom_pixel_size) - self.ui.update_settings_pushButton.clicked.connect(self.reload_image) - self.ui.spot_radioButton.toggled.connect(self.update_camera) - - self.update_camera() #initialize camera pixel size - self.update_scaling_factor() #initialize scaling_factor - self.show() - - #row major. invert y false, rotate false - def load_image(self): - """ - Prompts the user to select a text file containing image data. - """ - try: - file = QtWidgets.QFileDialog.getOpenFileName(self, 'Open file', os.getcwd()) - self.original_image = Image.open(file[0]) - self.original_image = self.original_image.rotate(-90, expand=True) #correct image orientation - self.resize_to_scaling_factor(self.original_image) - except Exception as err: - print(format(err)) - - def resize_to_scaling_factor(self, image): - """ - Handles loading of image according to scaling_factor - """ - self.update_scaling_factor() - - if self.ui.spot_radioButton.isChecked() and self.ui.resize_image_checkBox.isChecked(): - image = self.original_image.resize((round(image.size[0]*self.scaling_factor), round(image.size[1]*self.scaling_factor))) - self.image_plot.getAxis("bottom").setScale(scale = 1) - self.image_plot.getAxis("left").setScale(scale = 1) - else: - image = self.original_image - self.image_plot.getAxis("bottom").setScale(scale = self.scaling_factor) - self.image_plot.getAxis("left").setScale(scale = self.scaling_factor) - - if self.ui.greyscale_checkBox.isChecked(): - image = image.convert("L") #convert to greyscale - - self.image_array = np.array(image) - width = self.image_array.shape[0] - height = self.image_array.shape[1] - - try: - #set image bounds with qrect - self.img_item_rect = QtCore.QRectF(0, 0, width, height) - self.img_item.setImage(image=self.image_array) - self.img_item.setRect(self.img_item_rect) - - # if self.ui.greyscale_checkBox.isChecked(): - # self.hist_lut.setImageItem(self.img_item) - - if self.ui.vertical_radioButton.isChecked(): - roi_height = self.scaling_factor * height - self.roi.setSize([width, roi_height]) - elif self.ui.horizontal_radioButton.isChecked(): - roi_height = self.scaling_factor * width - self.roi.setSize([roi_height, height]) - self.roi.setAngle(0) - self.roi.setPos(0, 0) - self.line_profile_update_plot() - except: - pass - - def line_profile_update_plot(self): - """ Handle line profile for intensity sum viewbox """ - self.rgb_plot.clear() - - # Extract image data from ROI - data, coords = self.roi.getArrayRegion(self.image_array, self.img_item, returnMappedCoords=True) - if data is None: - return - - if self.ui.vertical_radioButton.isChecked(): - x_values = coords[0,:,0] - elif self.ui.horizontal_radioButton.isChecked(): - x_values = coords[1,0,:] - - if self.ui.pixera_radioButton.isChecked() or (self.ui.spot_radioButton.isChecked() and not self.ui.resize_image_checkBox.isChecked()): - x_values = x_values * self.scaling_factor - - #calculate average along columns in region - if len(data.shape) <= 2: #if grayscale, average intensities - if self.ui.vertical_radioButton.isChecked(): - avg_to_plot = np.mean(data, axis=-1) - elif self.ui.horizontal_radioButton.isChecked(): - avg_to_plot = np.mean(data, axis=0) - try: - self.rgb_plot.plot(x_values, avg_to_plot) - except: - pass - elif len(data.shape) > 2: #if rgb arrays, plot individual components - r_values = data[:,:,0] - g_values = data[:,:,1] - b_values = data[:,:,2] - if self.ui.vertical_radioButton.isChecked(): - r_avg = np.mean(r_values, axis=-1) #average red values across columns - g_avg = np.mean(g_values, axis=-1) #average green values - b_avg = np.mean(b_values, axis=-1) #average blue values - elif self.ui.horizontal_radioButton.isChecked(): - r_avg = np.mean(r_values, axis=0) - g_avg = np.mean(g_values, axis=0) - b_avg = np.mean(b_values, axis=0) - try: - self.rgb_plot.plot(x_values, r_avg, pen='r') - self.rgb_plot.plot(x_values, g_avg, pen='g') - self.rgb_plot.plot(x_values, b_avg, pen='b') - except Exception as e: - pass - - def update_scaling_factor(self): - """ - Calculate scaling factor - """ - if self.ui.custom_pixel_size_checkBox.isChecked(): - self.camera_pixel_size = self.ui.custom_pixel_size_spinBox.value() - self.scaling_factor = self.camera_pixel_size - else: - self.scaling_factor = self.camera_pixel_size/int(self.ui.magnification_comboBox.currentText()) - self.roi.snapSize = self.scaling_factor #roi snaps to multiples of scaling_factor - - def reload_image(self): - if hasattr(self, "original_image"): - self.resize_to_scaling_factor(self.original_image) #resize image, sets up roi - - def switch_custom_pixel_size(self): - checked = self.ui.custom_pixel_size_checkBox.isChecked() - self.ui.custom_pixel_size_spinBox.setEnabled(checked) - self.ui.magnification_comboBox.setEnabled(not checked) - - def update_camera(self): - if self.ui.spot_radioButton.isChecked(): - self.camera_pixel_size = 7.4 - self.ui.greyscale_checkBox.setChecked(False) - self.ui.resize_image_checkBox.setEnabled(True) - self.update_scaling_factor() - elif self.ui.pixera_radioButton.isChecked(): - self.camera_pixel_size = 3 - self.ui.greyscale_checkBox.setChecked(True) - self.ui.resize_image_checkBox.setEnabled(False) - self.update_scaling_factor() - - 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/src/main/python/Image_analysis/image_analysis_gui.ui b/src/main/python/Image_analysis/image_analysis_gui.ui deleted file mode 100644 index 9fcffd9..0000000 --- a/src/main/python/Image_analysis/image_analysis_gui.ui +++ /dev/null @@ -1,208 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 1029 - 743 - - - - Image Analysis - - - QTabWidget::Triangular - - - - - - - Settings - - - - - - - - - - - - - Greyscale image - - - - - - - Load image - - - - - - - - 50 - - - - - 75 - - - - - 100 - - - - - 150 - - - - - - - - Magnification - - - - - - - Update settings - - - - - - - false - - - - - - - Custom pixel size (um) - - - - - - - Camera - - - - - - SPOT - - - true - - - - - - - Pixera - - - - - - - Resize image - - - - - - - - - - Direction to average pixels - - - - - - Vertical - - - true - - - - - - - Horizontal - - - - - - - - - - - - - - - - 0 - 0 - - - - - 600 - 0 - - - - Image - - - - - - - - RGB Plot - - - - - - - - - - 0 - 0 - 1029 - 31 - - - - - - - - diff --git a/src/main/python/Lifetime_analysis/Fit_functions.py b/src/main/python/Lifetime_analysis/Fit_functions.py deleted file mode 100644 index 7ad5c3e..0000000 --- a/src/main/python/Lifetime_analysis/Fit_functions.py +++ /dev/null @@ -1,132 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Fri Mar 29 11:21:59 2019 - -@author: Sarthak -""" - -import numpy as np -from scipy.optimize import differential_evolution -from scipy.special import gamma - -def stretch_exp_fit(TRPL, t, Tc = (0,1e5), Beta = (0,1), A = (0,1e6), noise=(0,1e6)): - - def exp_stretch(t, tc, beta, a, noise): - return ((a * np.exp(-((1.0 / tc) * t) ** beta)) + noise) - - def avg_tau_from_exp_stretch(tc, beta): - return (tc / beta) * gamma(1.0 / beta) - - def Diff_Ev_Fit_SE(TRPL): - TRPL = TRPL - - def residuals(params):#params are the parameters to be adjusted by differential evolution or leastsq, interp is the data to compare to the model. - #Variable Rates - tc = params[0] - beta = params[1] - a = params[2] - noise = params[3] - - PL_sim = exp_stretch(t,tc,beta,a, noise) - - Resid= np.sum(((PL_sim-TRPL)**2)/(np.sqrt(TRPL)**2)) - return Resid #returns the difference between the PL data and simulated data - - bounds = [Tc, Beta, A, noise] - - result = differential_evolution(residuals, bounds) - return result.x - - p = Diff_Ev_Fit_SE(TRPL) - - tc = p[0] - beta = p[1] - a = p[2] - noise = p[3] - - PL_fit = exp_stretch(t,tc,beta,a, noise) - - avg_tau = avg_tau_from_exp_stretch(tc,beta) - - return tc, beta, a, avg_tau, PL_fit, noise - -def double_exp_fit(TRPL, t, tau1_bounds=(0,1000), a1_bounds=(0,1e6), tau2_bounds=(0,10000), a2_bounds=(0,1e5), noise=(0,1e6)): - - def single_exp(t, tau, a): - return (a * np.exp(-((1.0 / tau)*t))) - - def double_exp(t, tau1, a1, tau2, a2, noise): - return ((single_exp(t, tau1, a1)) + (single_exp(t, tau2, a2)) + noise) - - def avg_tau_from_double_exp(tau1, a1, tau2, a2): - return (((tau1*a1) + (tau2*a2))/(a1+a2)) - - def Diff_Ev_Fit_DE(TRPL): - TRPL = TRPL - - def residuals(params):#params are the parameters to be adjusted by differential evolution or leastsq, interp is the data to compare to the model. - #Variable Rates - tau1 = params[0] - a1 = params[1] - tau2 = params[2] - a2 = params[3] - noise = params[4] - - PL_sim = double_exp(t,tau1, a1, tau2, a2, noise) - - Resid= np.sum(((PL_sim-TRPL)**2)/(np.sqrt(TRPL)**2)) - return Resid #returns the difference between the PL data and simulated data - - bounds = [tau1_bounds, a1_bounds, tau2_bounds, a2_bounds, noise] - - result = differential_evolution(residuals, bounds) - return result.x - - p = Diff_Ev_Fit_DE(TRPL) - - tau1 = p[0] - a1 = p[1] - tau2 = p[2] - a2 = p[3] - noise = p[4] - - PL_fit = double_exp(t, tau1, a1, tau2, a2, noise) - - avg_tau = avg_tau_from_double_exp(tau1, a1, tau2, a2) - - return tau1, a1, tau2, a2, avg_tau, PL_fit, noise - -def single_exp_fit(TRPL, t, tau_bounds=(0,10000), a_bounds=(0,1e6), noise=(0,1e6)): - - def single_exp(t, tau, a, noise): - return (a * np.exp(-((1.0 / tau)*t) ) + noise) - - def Diff_Ev_Fit_singleExp(TRPL): - TRPL = TRPL - - def residuals(params):#params are the parameters to be adjusted by differential evolution or leastsq, interp is the data to compare to the model. - #Variable Rates - tau = params[0] - a = params[1] - noise = params[2] - - PL_sim = single_exp(t, tau, a, noise) - - Resid= np.sum(((PL_sim-TRPL)**2)/(np.sqrt(TRPL)**2)) - return Resid #returns the difference between the PL data and simulated data - - bounds = [tau_bounds, a_bounds, noise] - - result = differential_evolution(residuals, bounds) - return result.x - - p = Diff_Ev_Fit_singleExp(TRPL) - - tau = p[0] - a = p[1] - noise = p[2] - - PL_fit = single_exp(t, tau, a, noise) - - return tau, a, PL_fit, noise - \ No newline at end of file diff --git a/src/main/python/Lifetime_analysis/Fit_functions_with_irf.py b/src/main/python/Lifetime_analysis/Fit_functions_with_irf.py deleted file mode 100644 index 5b454af..0000000 --- a/src/main/python/Lifetime_analysis/Fit_functions_with_irf.py +++ /dev/null @@ -1,329 +0,0 @@ -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 (i - - MainWindow - - - - 0 - 0 - 2620 - 1676 - - - - Lifetime Analysis - - - - - - - - - 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 - - - - - - - 4 - - - 9999999.000000000000000 - - - - - - - 0 - - - - - - - 0 - - - - - - - SRV (cm/s) - - - - - - - 100000.000000000000000 - - - 8000.000000000000000 - - - - - - - Diffusion Coefficient (cm2/s) - - - - - - - Surface Lifetime (ns) - - - - - - - Average Lifetime (ns) - - - - - - - Calculate - - - - - - - SRV (cm/s) - - - - - - - 0 - - - - - - - - 75 - true - - - - SRV1 = SRV2 - - - - - - - - 75 - true - - - - SRV1 = 0 - - - - - - - - - - - - - 2 - 0 - - - - - - - - - 15 - - - - Settings - - - - - - - 12 - - - - Export Settings - - - - - - For Figure: - - - - - - - Export Fit Data - - - - - - - Clear Export Memory - - - - - - - - 12 - 50 - false - - - - Export figure - - - - - - - For Data: - - - - - - - Enter Legend Here... - - - - - - - Add trace to memory - - - - - - - - - - - 12 - - - - Plot Controls - - - - - - - 12 - 50 - false - - - - Plot - - - - - - - Y Axis Log - - - - - - - Plot color - - - - - - - - 12 - false - - - - Clear Plot - - - - - - - - - - - - Normalize - - - - - - - Clear plot everytime - - - - - - - - - - - 12 - - - - - - - - - 12 - - - - Resolution (ns) - - - - - - - false - - - - 12 - - - - true - - - - - - - - 12 - - - - 512 - - - 1 - - - - - - - - 12 - - - - IRF Channel No - - - - - - - - 12 - - - - Data Channel No - - - - - - - - 12 - - - - - - - - - 12 - - - - Load separate IRF file - - - - - - - - 12 - - - - Smooth Data - - - - - - - false - - - - 12 - - - - 1 - - - 1000 - - - - - - - - - - - - 0 - 0 - 2620 - 38 - - - - - File - - - - - - - - - - - - Open - - - - - Exit - - - - - Save - - - - - false - - - Open IRF File - - - - - - PlotWidget - QGraphicsView -
pyqtgraph
-
-
- - -
diff --git a/src/main/python/Lifetime_analysis/Lifetime_plot_fit.py b/src/main/python/Lifetime_analysis/Lifetime_plot_fit.py deleted file mode 100644 index 7967a1e..0000000 --- a/src/main/python/Lifetime_analysis/Lifetime_plot_fit.py +++ /dev/null @@ -1,653 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Mar 27 16:50:26 2019 - -@author: Sarthak -""" - -# system imports -import sys -import os -from pathlib import Path - -# module imports -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui, QtWidgets -import numpy as np -import matplotlib.pyplot as plt - -sys.path.append(os.path.abspath('../Export_Windows')) -try: - from Export_window import ExportPlotWindow -except: - from Export_Windows.Export_window import ExportPlotWindow - -# local module imports -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""" -plt.rc('xtick', labelsize = 20) -plt.rc('xtick.major', pad = 3) -plt.rc('ytick', labelsize = 20) -plt.rc('lines', lw = 2.5, markersize = 7.5) -plt.rc('legend', fontsize = 20) -plt.rc('axes', linewidth=3.5) - -pg.mkQApp() -pg.setConfigOption('background', 'w') -##pg.setConfigOption('crashWarning', True) - -base_path = Path(__file__).parent -file_path = (base_path / "Lifetime_analysis_gui_layout.ui").resolve() - -uiFile = file_path - -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.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.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.export_window)#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.add_to_mem_pushButton.clicked.connect(self.add_trace_to_mem) - self.ui.export_data_pushButton.clicked.connect(self.export_data) - self.ui.clear_export_data_pushButton.clicked.connect(self.clear_export_data) - self.ui.smoothData_checkBox.stateChanged.connect(self.smooth_trace_enabled) - - #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.fit_lifetime_called_w_irf = False - self.fit_lifetime_called_wo_irf = False - self.x_mem = [] # containers for adding x data to memory - self.y_mem = [] # containers for adding y data to memory - self.best_fit_mem = [] # containers for adding best fit data to memory - self.best_fit_mem_x = [] # containers for adding best fit data to memory - self.legend = [] # containers for adding legend to memory - - #variables accounting for data received from FLIM analysis - self.opened_from_flim = False #switched to True in FLIM_plot when "analyze lifetime" clicked - self.hist_data_from_flim = [] #container for flim roi data - - self.show() - - def open_file(self): - """ Open data file """ -# try: - self.filename = QtWidgets.QFileDialog.getOpenFileName(self) - try: - if ".csv" in self.filename[0] or ".txt" in self.filename[0]: #if txt or csv, prompt user to enter # of rows to skip - self.skip_rows_window = SkipRowsWindow() - self.skip_rows_window.skip_rows_signal.connect(self.open_with_skip_rows_window) - self.ui.Res_comboBox.setEnabled(True) - else: - self.file = read_picoharp_phd(self.filename[0]) - self.opened_from_flim = False - except: - pass - - def open_with_skip_rows_window(self): - """ Prompts user to enter how many rows to skip """ - skip_rows = self.skip_rows_window.ui.skip_rows_spinBox.value() - if ".txt" in self.filename[0]: - self.file = np.loadtxt(self.filename[0], skiprows=skip_rows) - elif ".csv" in self.filename[0]: - self.file = np.genfromtxt(self.filename[0], skip_header=skip_rows, delimiter=",") - - def open_irf_file(self): - """ Open file with irf - enabled if 'load separate irf' is checled """ - self.irf_filename = QtWidgets.QFileDialog.getOpenFileName(self) - try: - if ".txt" in self.irf_filename[0] or ".csv" in self.irf_filename[0]: - self.irf_skip_rows_window = SkipRowsWindow() - self.irf_skip_rows_window.skip_rows_signal.connect(self.open_irf_with_skip_rows_window) - self.ui.Res_comboBox.setEnabled(True) - else: - self.irf_file = read_picoharp_phd(self.irf_filename[0]) - except: - pass - - def open_irf_with_skip_rows_window(self): - irf_skip_rows = self.irf_skip_rows_window.ui.skip_rows_spinBox.value() - if ".txt" in self.irf_filename[0]: - self.irf_file = np.loadtxt(self.irf_filename[0], skiprows=irf_skip_rows) - elif ".csv" in self.irf_filename[0]: - self.irf_file = np.genfrontxt(self.irf_filename[0], skip_header=irf_skip_rows, delimiter=",") - - 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 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 smooth_trace_enabled(self): - """Enable smooth spin box when smooth data is checked""" - if self.ui.smoothData_checkBox.isChecked(): - self.ui.smoothData_spinBox.setEnabled(True) - else: - self.ui.smoothData_spinBox.setEnabled(False) - - 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") - """ - if mode == "data": - channel = int(self.ui.Data_channel_spinBox.value()) - elif mode == "irf": - channel = int(self.ui.irf_channel_spinBox.value()) - try: - 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] - - self.resolution = float(self.ui.Res_comboBox.currentText()) - except: - res, y = self.file.get_curve(channel) - time_window = int(np.floor(self.file.get_time_window_in_ns(channel))) - y = y[0:time_window] - self.resolution = res - - length = np.shape(y)[0] - x = np.arange(0, length*self.resolution, self.resolution, np.float) - - if self.ui.smoothData_checkBox.isChecked() and mode=="data": - y = np.convolve(y, np.ones(self.ui.smoothData_spinBox.value())/self.ui.smoothData_spinBox.value(), mode="same") - - if self.ui.normalize_checkBox.isChecked(): - y = y / np.amax(y) - - return x,y - - except Exception as e: - self.ui.Result_textBrowser.setText(str(e)) - - def plot(self): - try: - if self.opened_from_flim: - x, y = self.hist_data_from_flim - else: - x,y = self.acquire_settings() #get data - - self.ui.plot.plot(x, y, clear=self.ui.clear_plot_checkBox.isChecked(), pen=pg.mkPen(self.plot_color)) - self.fit_lifetime_called_w_irf = False - self.fit_lifetime_called_wo_irf = False - - try: - self.ui.Result_textBrowser.setText("Integral Counts :\n" "{:.2E}".format( - self.file.get_integral_counts(int(self.ui.Channel_spinBox.value())))) - except: - self.ui.Result_textBrowser.setText("Integral Counts :\n" "{:.2E}".format(np.sum(y))) - except: - pass - self.ui.plot.setLabel('left', 'Intensity', units='a.u.') - self.ui.plot.setLabel('bottom', 'Time (ns)') - - def make_semilog(self): - """ 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: - if not hasattr(self, "file"): - self.ui.Result_textBrowser.setText("You need to load a data file.") - else: - if self.opened_from_flim: - x, y = self.hist_data_from_flim - else: - 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]):] - x = x[np.asscalar(find_max_int[0]):] - - t = x - 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=self.ui.clear_plot_checkBox.isChecked(), pen=pg.mkPen(self.plot_color)) - - if fit_func == "Stretched Exponential": #stretch exponential tab - tc, beta, a, avg_tau, PL_fit, noise = 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=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)+ - "\nNoise = "+ str(noise)) - self.ui.average_lifetime_spinBox.setValue(avg_tau) - - elif fit_func == "Double Exponential": #double exponential tab - tau1, a1, tau2, a2, avg_tau, PL_fit, noise = 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=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)+ - "\nNoise = "+ str(noise)) - #TODO - once tau_avg implemented, set average lifetime spinbox to tau_avg value - - elif fit_func == "Single Exponential": #single exponential tab - tau, a, PL_fit, noise = 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=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)+ - "\nNoise = "+ str(noise)) - 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.fit_lifetime_called_wo_irf = True - self.fit_lifetime_called_w_irf = False - - self.ui.plot.setLabel('left', 'Intensity', units='a.u.') - self.ui.plot.setLabel('bottom', 'Time (ns)') - return self.out - - except Exception as e: - self.ui.Result_textBrowser.append(format(e)) - - def fit_and_plot_with_irf(self): - """ Fit and plot with IRF """ - try: - self.ui.Result_textBrowser.clear() - if not hasattr(self, "file"): - self.ui.Result_textBrowser.append("You need to load a data file.") - if not hasattr(self, "irf_file") and self.ui.separate_irf_checkBox.isChecked(): - self.ui.Result_textBrowser.append("You need to load an IRF file.") - else: - if self.opened_from_flim: - x,y = self.hist_data_from_flim - else: - 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 - if bestfit_params[3] > bestfit_params[1]: - self.ui.average_lifetime_spinBox.setValue(bestfit_params[3]) - elif bestfit_params[1] > bestfit_params[3]: - self.ui.average_lifetime_spinBox.setValue(bestfit_params[1]) - - 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()) - self.fit_lifetime_called_w_irf = True - self.fit_lifetime_called_wo_irf = False - except Exception as e: - self.ui.Result_textBrowser.append(format(e)) - - 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 - - self.srv1_srv2_equal = self.thickness / (2*((1e-9*self.surface_lifetime) - ((1/self.diffusion_coeffecient)*((self.thickness/np.pi)**2)) )) - self.srv1_zero = self.thickness / ((1e-9*self.surface_lifetime) - ((4/self.diffusion_coeffecient)*((self.thickness/np.pi)**2)) ) - - self.ui.srv1_srv2_equal_label.setText(str(self.srv1_srv2_equal)) - self.ui.srv1_zero_label.setText(str(self.srv1_zero)) - - 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()) - srv_string += "\nSurface Lifetime (ns): " + self.ui.surface_lifetime_label.text() - - srv_string += "\nSRV1 = SRV2"\ - + "\nSRV (cm/s): " + self.ui.srv1_srv2_equal_label.text() - - srv_string += "\nSRV1 = 0"\ - + "\nSRV (cm/s): " + self.ui.srv1_zero_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 clear_export_data(self): - self.data_list = [] - self.clean_up_after_fig_export() - - def clean_up_after_fig_export(self): - self.x_mem = [] - self.y_mem = [] - self.legend = [] - self.best_fit_mem = [] - self.best_fit_mem_x = [] - - def add_trace_to_mem(self): - try: - if self.fit_lifetime_called_w_irf == True: - self.x_mem.append(self.out[:,0]) - self.y_mem.append(self.out[:,1]) - self.best_fit_mem_x.append(self.out[:,0]) - self.best_fit_mem.append(self.out[:,2]) - elif self.fit_lifetime_called_wo_irf == True: - self.x_mem.append(self.acquire_settings()[0]) - self.y_mem.append(self.acquire_settings()[1]) - self.best_fit_mem_x.append(self.out[:,0]) - self.best_fit_mem.append(self.out[:,2]) - else: - self.x_mem.append(self.acquire_settings()[0]) - self.y_mem.append(self.acquire_settings()[1]) - self.legend.append(self.ui.lineEdit.text()) - except Exception as e: - print(e) - - def export_window(self): - self.exportplotwindow = ExportPlotWindow() - self.exportplotwindow.export_fig_signal.connect(self.pub_ready_plot_export) - - def pub_ready_plot_export(self): - try: - if self.x_mem == []: - self.ui.result_textBrowser.setText("Add traces to memory first!") - - else: - filename = QtWidgets.QFileDialog.getSaveFileName(self,caption="Filename with EXTENSION") - - plt.figure(figsize=(8,6)) - plt.tick_params(direction='out', length=8, width=3.5) - for i in range(len(self.x_mem)): - plt.plot(self.x_mem[i], self.y_mem[i], label=str(self.legend[i])) - if self.fit_lifetime_called_w_irf == True or self.fit_lifetime_called_wo_irf == True: - plt.plot(self.best_fit_mem_x[i], self.best_fit_mem[i],'k--') - - plt.yscale('log') - plt.xlabel("Time (ns)", fontsize=20, fontweight='bold') - plt.ylabel("Intensity (norm.)", fontsize=20, fontweight='bold') - plt.legend() - plt.tight_layout() - plt.xlim([self.exportplotwindow.ui.lowerX_spinBox.value(),self.exportplotwindow.ui.upperX_spinBox.value()]) - plt.ylim([self.exportplotwindow.ui.lowerY_spinBox.value(),self.exportplotwindow.ui.upperY_doubleSpinBox.value()]) - - plt.savefig(filename[0],bbox_inches='tight', dpi=300) - plt.close() - self.clean_up_after_fig_export() - - except Exception as e: - self.ui.Result_textBrowser.append(format(e)) - pass - - 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 - -"""Skip rows GUI""" -ui_file_path = (base_path / "skip_rows.ui").resolve() -skiprows_WindowTemplate, skiprows_TemplateBaseClass = pg.Qt.loadUiType(ui_file_path) - -class SkipRowsWindow(skiprows_TemplateBaseClass): - - skip_rows_signal = QtCore.pyqtSignal() #signal to help with pass info back to MainWindow - - def __init__(self): - skiprows_TemplateBaseClass.__init__(self) - - # Create the param window - self.ui = skiprows_WindowTemplate() - self.ui.setupUi(self) - self.ui.done_pushButton.clicked.connect(self.done) - self.setWindowFlag(QtCore.Qt.WindowCloseButtonHint, False) - self.show() - - def done(self): - self.skip_rows_signal.emit() - self.close() - - -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/src/main/python/Lifetime_analysis/__init__.py b/src/main/python/Lifetime_analysis/__init__.py deleted file mode 100644 index b4011af..0000000 --- a/src/main/python/Lifetime_analysis/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import absolute_import, division, print_function \ No newline at end of file diff --git a/src/main/python/Lifetime_analysis/picoharp_phd.py b/src/main/python/Lifetime_analysis/picoharp_phd.py deleted file mode 100644 index 455e1bc..0000000 --- a/src/main/python/Lifetime_analysis/picoharp_phd.py +++ /dev/null @@ -1,408 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Majority of this PicoHarp Parser was written by skyjur. -Check out the original code here --- -https://github.com/skyjur/picoharp300-curvefit-ui - -Modified by Sarthak --- -Created on Fri Apr 5 15:50:36 2019 - -@author: Sarthak -""" - -""" -PicoHarp 300 file parser -""" -import datetime -import numpy as np -from ctypes import c_uint, c_ulong, c_char, c_int, c_int64, c_float, \ - Structure, sizeof, memmove, addressof - -DISPCURVES = 8 -MAXCURVES = 512 -MAXCHANNELS = 65536 - - -class tParamStruct(Structure): - _pack_ = 4 - _fields_ = [ - ('Start', c_float), - ('Step', c_float), - ('End', c_float), - ] - - -class tCurveMapping(Structure): - _pack_ = 4 - _fields_ = [ - ('MapTo', c_int), - ('Show', c_int), - ] - - -class TxtHdr(Structure): - _pack_ = 4 - _fields_ = [ - ('Ident', c_char * 16), - ('FormatVersion', c_char * 6), - ('CreatorName', c_char * 18), - ('CreatorVersion', c_char * 12), - ('FileTime', c_char * 18), - ('CRLF', c_char * 2), - ('CommentField', c_char * 256), - ] - - -class BinHdr(Structure): - _pack_ = 4 - _fields_ = [ - ('Curves', c_int), - ('BitsPerHistoBin', c_int), - ('RoutingChannels', c_int), - ('NumberOfBoards', c_int), - ('ActiveCurve', c_int), - ('MeasMode', c_int), - ('SubMode', c_int), - ('RangeNo', c_int), - ('Offset', c_int), - ('Tacq', c_int), # in m - ('StopAt', c_int), - ('StopOnOvfl', c_int), - ('Restart', c_int), - ('DispLinLog', c_int), - ('DispTimeFrom', c_int), - ('DispTimeTo', c_int), - ('DispCountsFrom', c_int), - ('DispCountsTo', c_int), - ('DispCurves', tCurveMapping * DISPCURVES), - ('Params', tParamStruct * 3), - ('RepeatMode', c_int), - ('RepeatsPerCurve', c_int), - ('RepeatTime', c_int), - ('RepeatWaitTime', c_int), - ('ScriptName', c_char * 20), - ] - - -class BoardHdr(Structure): - _pack_ = 4 - _fields_ = [ - ('HardwareIdent', c_char * 16), - ('HardwareVersion', c_char * 8), - ('HardwareSerial', c_int), - ('SyncDivider', c_int), - ('CFDZeroCross0', c_int), - ('CFDLevel0', c_int), - ('CFDZeroCross1', c_int), - ('CFDLevel1', c_int), - ('Resolution', c_float), - ('RouterModelCode', c_int), - ('RouterEnabled', c_int), - ('RtChan1_InputType;', c_int), - ('RtChan1_InputLevel', c_int), - ('RtChan1_InputEdge', c_int), - ('RtChan1_CFDPresent', c_int), - ('RtChan1_CFDLevel', c_int), - ('RtChan1_CFDZeroCross', c_int), - ('RtChan2_InputType;', c_int), - ('RtChan2_InputLevel', c_int), - ('RtChan2_InputEdge', c_int), - ('RtChan2_CFDPresent', c_int), - ('RtChan2_CFDLevel', c_int), - ('RtChan2_CFDZeroCross', c_int), - ('RtChan3_InputType;', c_int), - ('RtChan3_InputLevel', c_int), - ('RtChan3_InputEdge', c_int), - ('RtChan3_CFDPresent', c_int), - ('RtChan3_CFDLevel', c_int), - ('RtChan3_CFDZeroCross', c_int), - ('RtChan4_InputType;', c_int), - ('RtChan4_InputLevel', c_int), - ('RtChan4_InputEdge', c_int), - ('RtChan4_CFDPresent', c_int), - ('RtChan4_CFDLevel', c_int), - ('RtChan4_CFDZeroCross', c_int), - ] - - -class CurveHdr(Structure): - _pack_ = 4 - _fields_ = [ - ('CurveIndex', c_int), - ('TimeOfRecording', c_ulong), - ('HardwareIdent', c_char * 16), - ('HardwareVersion', c_char * 8), - ('HardwareSerial', c_int), - ('SyncDivider', c_int), - ('CFDZeroCross0', c_int), - ('CFDLevel0', c_int), - ('CFDZeroCross1', c_int), - ('CFDLevel1', c_int), - ('Offset', c_int), - ('RoutingChannel', c_int), - ('ExtDevices', c_int), - ('MeasMode', c_int), - ('SubMode', c_int), - ('P1', c_float), - ('P2', c_float), - ('P3', c_float), - ('RangeNo', c_int), - ('Resolution', c_float), - ('Channels', c_int), - ('Tacq', c_int), - ('StopAfter', c_int), - ('StopReason', c_int), - ('InpRate0', c_int), - ('InpRate1', c_int), - ('HistCountRate', c_int), - ('IntegralCount', c_int64), - ('reserved', c_int), - ('DataOffset', c_int), - ('RouterModelCode', c_int), - ('RouterEnabled', c_int), - ('RtChan_InputType;', c_int), - ('RtChan_InputLevel', c_int), - ('RtChan_InputEdge', c_int), - ('RtChan_CFDPresent', c_int), - ('RtChan_CFDLevel', c_int), - ('RtChan_CFDZeroCross', c_int), - ] - - -class ParseError(Exception): pass - - -def _read(f, CType): - data = f.read(sizeof(CType)) - obj = CType() - memmove(addressof(obj), data, len(data)) - return obj - - -def _validate_header(header): - if not header.Ident == 'PicoHarp 300' or not header.FormatVersion == '2.0': - raise ParseError('Does not look like a PicoHarp 300 file.') - - -class Curve(object): - res = None - data = None - - def __repr__(self): - return 'Curve' % ( - self.res, - len(self.data) - ) - - -def timefmt(t): - d = datetime.datetime.fromtimestamp(t) - return d.strftime('%a %b %d %H:%M:%S %Y') - - -class PicoharpParser(object): - _ready = False - - def __init__(self, filename): - if isinstance(filename, (str)):#, unicode)): - filename = open(filename, mode='rb') - self.f = filename - self._prepare() - - def _prepare(self): - self.f.seek(0) - - header = self._header = _read(self.f, TxtHdr) - """SJ commented this --- it was giving ParseError""" -# _validate_header(header) - - bin_header = self._bin_header = _read(self.f, BinHdr) - - self._boards = [] - for i in range(bin_header.NumberOfBoards): - self._boards.append(_read(self.f, BoardHdr)) - - self._curves = [] - for i in range(bin_header.Curves): - self._curves.append(_read(self.f, CurveHdr)) - - def header(self): - return [(k, getattr(self._header, k)) for k, t in self._header._fields_] - - def no_of_curves(self): - return self._bin_header.Curves - - def get_curve(self, n): - header = self._curves[n] - res = header.Resolution - - self.f.seek(header.DataOffset) - array = np.fromfile(self.f, c_uint, header.Channels) - - return res, array - - def get_time_window_in_ns(self, curve_no): - curve = self._curves[curve_no] - rep_rate = curve.InpRate0 - res, _ = self.get_curve(curve_no) - time_window_s = (1/rep_rate)/res # in seconds - - return time_window_s * 1e9 # in nannoseconds - - def get_integral_counts(self, curve_no): - curve = self._curves[curve_no] - integral_counts = curve.IntegralCount - - return integral_counts - - def info(self): - txthdr = self._header - binhdr = self._bin_header - boards = self._boards - curves = self._curves - r = [] - w = r.append - yesno = lambda x: 'true' if x else 'false' - - w("Ident : %s" % txthdr.Ident) - w("Format Version : %s" % txthdr.FormatVersion) - w("Creator Name : %s" % txthdr.CreatorName) - w("Creator Version : %s" % txthdr.CreatorVersion) - w("Time of Creation : %s" % txthdr.FileTime) - w("File Comment : %s" % txthdr.CommentField) - - w("No of Curves : %s" % binhdr.Curves) - w("Bits per HistoBin: %s" % binhdr.BitsPerHistoBin) - w("RoutingChannels : %s" % binhdr.RoutingChannels) - w("No of Boards : %s" % binhdr.NumberOfBoards) - w("Active Curve : %s" % binhdr.ActiveCurve) - w("Measurement Mode : %s" % binhdr.MeasMode) - w("Sub-Mode : %s" % binhdr.SubMode) - w("Range No : %s" % binhdr.RangeNo) - w("Offset : %s" % binhdr.Offset) - w("AcquisitionTime : %s" % binhdr.Tacq) - w("Stop at : %s" % binhdr.StopAt) - w("Stop on Ovfl. : %s" % binhdr.StopOnOvfl) - w("Restart : %s" % binhdr.Restart) - w("DispLinLog : %s" % binhdr.DispLinLog) - w("DispTimeAxisFrom : %s" % binhdr.DispTimeFrom) - w("DispTimeAxisTo : %s" % binhdr.DispTimeTo) - w("DispCountAxisFrom: %s" % binhdr.DispCountsFrom) - w("DispCountAxisTo : %s" % binhdr.DispCountsTo) - - for i in range(DISPCURVES): - w("---------------------") - w("Curve No %s" % i) - w(" MapTo : %s" % binhdr.DispCurves[i].MapTo) - w(" Show : %s" % yesno(binhdr.DispCurves[i].Show)) - w("---------------------") - - for i in range(3): - w("---------------------") - w("Parameter No %s" % i) - w(" Start : %f" % binhdr.Params[i].Start) - w(" Step : %f" % binhdr.Params[i].Step) - w(" End : %f" % binhdr.Params[i].End) - w("---------------------") - - w("Repeat Mode : %d" % binhdr.RepeatMode) - w("Repeats per Curve: %d" % binhdr.RepeatsPerCurve) - w("Repeat Time : %d" % binhdr.RepeatTime) - w("Repeat wait Time : %d" % binhdr.RepeatWaitTime) - w("Script Name : %s" % binhdr.ScriptName) - - for i, board in enumerate(boards): - w("---------------------") - w("Board No %d" % i) - w(" HardwareIdent : %s" % board.HardwareIdent) - w(" HardwareVersion : %s" % board.HardwareVersion) - w(" HardwareSerial : %d" % board.HardwareSerial) - w(" SyncDivider : %d" % board.SyncDivider) - w(" CFDZeroCross0 : %d" % board.CFDZeroCross0) - w(" CFDLevel0 : %d" % board.CFDLevel0) - w(" CFDZeroCross1 : %d" % board.CFDZeroCross1) - w(" CFDLevel1 : %d" % board.CFDLevel1) - w(" Resolution : %.6f" % board.Resolution) - - if board.RouterModelCode: - w(" RouterModelCode : %d" % board.RouterModelCode) - w(" RouterEnabled : %d" % board.RouterEnabled) - - w(" RtChan1_InputType : %d" % board.RtChan1_InputType) - w(" RtChan1_InputLevel : %d" % board.RtChan1_InputLevel) - w(" RtChan1_InputEdge : %d" % board.RtChan1_InputEdge) - w(" RtChan1_CFDPresent : %d" % board.RtChan1_CFDPresent) - w(" RtChan1_CFDLevel : %d" % board.RtChan1_CFDLevel) - w(" RtChan1_CFDZeroCross : %d" % board.RtChan1_CFDZeroCross) - - w(" RtChan2_InputType : %d" % board.RtChan2_InputType) - w(" RtChan2_InputLevel : %d" % board.RtChan2_InputLevel) - w(" RtChan2_InputEdge : %d" % board.RtChan2_InputEdge) - w(" RtChan2_CFDPresent : %d" % board.RtChan2_CFDPresent) - w(" RtChan2_CFDLevel : %d" % board.RtChan2_CFDLevel) - w(" RtChan2_CFDZeroCross : %d" % board.RtChan2_CFDZeroCross) - - w(" RtChan3_InputType : %d" % board.RtChan3_InputType) - w(" RtChan3_InputLevel : %d" % board.RtChan3_InputLevel) - w(" RtChan3_InputEdge : %d" % board.RtChan3_InputEdge) - w(" RtChan3_CFDPresent : %d" % board.RtChan3_CFDPresent) - w(" RtChan3_CFDLevel : %d" % board.RtChan3_CFDLevel) - w(" RtChan3_CFDZeroCross : %d" % board.RtChan3_CFDZeroCross) - - w(" RtChan4_InputType : %d" % board.RtChan4_InputType) - w(" RtChan4_InputLevel : %d" % board.RtChan4_InputLevel) - w(" RtChan4_InputEdge : %d" % board.RtChan4_InputEdge) - w(" RtChan4_CFDPresent : %d" % board.RtChan4_CFDPresent) - w(" RtChan4_CFDLevel : %d" % board.RtChan4_CFDLevel) - w(" RtChan4_CFDZeroCross : %d" % board.RtChan4_CFDZeroCross) - - w("---------------------") - - for i, curve in enumerate(curves): - w("---------------------") - w("Curve Index : %d" % curve.CurveIndex) - w("Time of Recording : %s" % timefmt(curve.TimeOfRecording)) - w("HardwareIdent : %s" % curve.HardwareIdent) - w("HardwareVersion : %s" % curve.HardwareVersion) - w("HardwareSerial : %d" % curve.HardwareSerial) - w("SyncDivider : %d" % curve.SyncDivider) - w("CFDZeroCross0 : %d" % curve.CFDZeroCross0) - w("CFDLevel0 : %d" % curve.CFDLevel0 ) - w("CFDZeroCross1 : %d" % curve.CFDZeroCross1) - w("CFDLevel1 : %d" % curve.CFDLevel1) - w("Offset : %d" % curve.Offset) - w("RoutingChannel : %d" % curve.RoutingChannel) - w("ExtDevices : %d" % curve.ExtDevices) - w("Meas. Mode : %d" % curve.MeasMode) - w("Sub-Mode : %d" % curve.SubMode) - w("Par. 1 : %f" % curve.P1) - w("Par. 2 : %.6f" % curve.P2) - w("Par. 3 : %.6f" % curve.P3) - w("Range No : %d" % curve.RangeNo) - w("Resolution : %f" % curve.Resolution) - w("Channels : %d" % curve.Channels) - w("Acq. Time : %d" % curve.Tacq) - w("Stop after : %d" % curve.StopAfter) - w("Stop Reason : %d" % curve.StopReason) - w("InpRate0 : %d" % curve.InpRate0) - w("InpRate1 : %d" % curve.InpRate1) - w("HistCountRate : %d" % curve.HistCountRate) - w("IntegralCount : %d" % curve.IntegralCount) - w("reserved : %d" % curve.reserved) - w("dataoffset : %d" % curve.DataOffset) - - if curve.RouterModelCode: - w("RouterModelCode : %d" % curve.RouterModelCode) - w("RouterEnabled : %d" % curve.RouterEnabled) - w("RtChan_InputType : %d" % curve.RtChan_InputType) - w("RtChan_InputLevel : %d" % curve.RtChan_InputLevel) - w("RtChan_InputEdge : %d" % curve.RtChan_InputEdge) - w("RtChan_CFDPresent : %d" % curve.RtChan_CFDPresent) - w("RtChan_CFDLevel : %d" % curve.RtChan_CFDLevel) - w("RtChan_CFDZeroCross : %d" % curve.RtChan_CFDZeroCross) - - return '\n'.join(r) - -def read_picoharp_phd(datafile): - parser = PicoharpParser(datafile) - return parser \ No newline at end of file diff --git a/src/main/python/Lifetime_analysis/read_ph_phd.py b/src/main/python/Lifetime_analysis/read_ph_phd.py deleted file mode 100644 index 1d35203..0000000 --- a/src/main/python/Lifetime_analysis/read_ph_phd.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Fri Apr 5 15:52:05 2019 - -@author: Sarthak -""" - -try: - from Lifetime_analysis import picoharp_phd -except: - import picoharp_phd -import numpy as np -import matplotlib.pyplot as plt -#import sys - -#datafile = "E:/Python code/APTES_APTMS.phd" - - -def read_picoharp_phd(datafile): - parser = picoharp_phd.PicoharpParser(datafile) - return parser - -#def phd_to_csv(datafile, return_df = False): -# parser = read_picoharp_phd(datafile) -# name, ext = datafile.rsplit('.', 1) -# -# total_curves = parser.no_of_curves() -# y = [] -# for i in range(total_curves): -# res, curve = parser.get_curve(i) -# time_window = int(np.floor(parser.get_time_window_in_ns(curve_no))) -# curve = curve[0:time_window] -# y.append(curve) -# -# df = pd.DataFrame(y) -# df.T.to_csv(name+".csv", index=False, header=False) -# if return_df == True: -# return df.T - -def smooth(curve, boxwidth): - sm_curve = np.convolve(curve, np.ones(boxwidth)/boxwidth, mode="same") - return sm_curve - -def get_x_y(curve_no, parser, smooth_trace = False, boxwidth = 3): - - assert type(parser) == picoharp_phd.PicoharpParser, 'must be picoharp parser' - res, curve = parser.get_curve(curve_no) - time_window = int(np.floor(parser.get_time_window_in_ns(curve_no))) - curve = curve[0:time_window] - size = len(curve) - x = np.arange(0, size*res, res, np.float) - if smooth_trace == True: - curve = smooth(curve, boxwidth=boxwidth) - return x,curve diff --git a/src/main/python/Lifetime_analysis/skip_rows.ui b/src/main/python/Lifetime_analysis/skip_rows.ui deleted file mode 100644 index 35a4e6b..0000000 --- a/src/main/python/Lifetime_analysis/skip_rows.ui +++ /dev/null @@ -1,42 +0,0 @@ - - - Form - - - - 0 - 0 - 413 - 94 - - - - Enter rows to skip - - - - - - Rows to skip - - - - - - - 10 - - - - - - - Done - - - - - - - - diff --git a/src/main/python/OceanOptics_acquire/OO_PZstageScan_acquire_gui.ui b/src/main/python/OceanOptics_acquire/OO_PZstageScan_acquire_gui.ui deleted file mode 100644 index 77dacb8..0000000 --- a/src/main/python/OceanOptics_acquire/OO_PZstageScan_acquire_gui.ui +++ /dev/null @@ -1,750 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 1219 - 901 - - - - MainWindow - - - - - - - - - - 15 - - - - Settings - - - - - - Stage Settings for Scan - - - - - - - 12 - - - - X Scan Size (um) - - - - - - - - 12 - - - - 5.000000000000000 - - - - - - - - 12 - - - - Initialize Piezo Stage - - - - - - - - 12 - - - - 0.100000000000000 - - - - - - - - 12 - - - - 5.000000000000000 - - - - - - - - 12 - - - - X Step (um) - - - - - - - - 12 - - - - 100.000000000000000 - - - 50.000000000000000 - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - 12 - - - - Y Scan Size (um) - - - - - - - - 12 - - - - X Start (um) - - - - - - - - 12 - - - - 1.000000000000000 - - - 0.100000000000000 - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - 12 - - - - Y Start (um) - - - - - - - - 12 - - - - Y Step (um) - - - - - - - - 12 - - - - Start X-Y Scan - - - - - - - - 12 - - - - Estimate Scan Time - - - - - - - - 12 - - - - 100.000000000000000 - - - 50.000000000000000 - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - 15 - - - - OceanOptics Save Settings - - - - - - - 12 - - - - Save Every Spectrum - - - - - - - - 12 - - - - Save Single Spectrum - - - - - - - - - - Spectrometer Settings - - - - - - - 12 - - - - 1 - - - - - - - - 12 - - - - 3 - - - 3000 - - - 100 - - - - - - - - 12 - - - - Scans to Average - - - - - - - - 12 - - - - Intg. Time (ms) - - - - - - - - 12 - - - - Correct Dark Counts - - - true - - - - - - - - 12 - - - - Connect to Spectrometer - - - - - - - - 12 - - - - Live - - - - - - - - - - Scan Save Settings - - - - - - - 12 - - - - - - - - - 12 - - - - Sample Name: - - - - - - - - 12 - - - - Path to Folder - - - - - - - - - - - - - - - - - - 15 - - - - Independent Stage Movements - - - - - - - 12 - - - - Where is the stage? - - - - - - - - 12 - - - - Show Current Position - - - - - - - - 12 - - - - Center the Piezo Stage: - - - - - - - - 12 - - - - Center Stage - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - 12 - - - - Absolute Movements - - - - - - - - 12 - - - - X (um) - - - - - - - - 12 - - - - - - - - - 12 - - - - Y (um) - - - - - - - - 12 - - - - - - - - - 12 - - - - OK - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - 12 - - - - Relative Movements - - - - - - - - 12 - - - - X (um) - - - - - - - - 12 - - - - - - - - - 12 - - - - Y (um) - - - - - - - - 12 - - - - - - - - - 12 - - - - Ok - - - - - - - - - - - - - - 12 - - - - - - - - - 15 - - - - Information: - - - - - - - - 15 - - - - Scan Progress: - - - - - - - - 12 - - - - 0 - - - - - - - - - - - 0 - 0 - 1219 - 21 - - - - - - - - PlotWidget - QGraphicsView -
pyqtgraph
-
-
- - -
diff --git a/src/main/python/OceanOptics_acquire/OO_acquire_gui.ui b/src/main/python/OceanOptics_acquire/OO_acquire_gui.ui deleted file mode 100644 index 33c8b19..0000000 --- a/src/main/python/OceanOptics_acquire/OO_acquire_gui.ui +++ /dev/null @@ -1,240 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 1206 - 668 - - - - MainWindow - - - - - - - - - - 15 - - - - Acquire Settings - - - - - - - 15 - - - - Save Settings - - - - - - - 12 - - - - Configure Folder to Save - - - - - - - - 12 - - - - Save Every Spectrum - - - - - - - - 12 - - - - Save Single Spectrum - - - - - - - - - - - 12 - - - - Correct Dark Counts - - - true - - - - - - - - 15 - - - - Close Connection - - - - - - - - 12 - - - - - - - - - 15 - - - - ALWAYS CLOSE CONNECTION!!! -BEFORE EXITING APP - - - - - - - - 12 - - - - Intg. Time (ms) - - - - - - - - 12 - - - - Scans to Average - - - - - - - - 10 - - - - 3 - - - 3000 - - - - - - - - 10 - - - - 1 - - - - - - - - 15 - - - - Live - - - - - - - - 12 - - - - Connect to Spectrometer - - - - - - - - - - - - - - - - - - - - - 0 - 0 - 1206 - 21 - - - - - - - - PlotWidget - QGraphicsView -
pyqtgraph
-
-
- - -
diff --git a/src/main/python/OceanOptics_acquire/OceanOptics_acquire_plot.py b/src/main/python/OceanOptics_acquire/OceanOptics_acquire_plot.py deleted file mode 100644 index 8de85c0..0000000 --- a/src/main/python/OceanOptics_acquire/OceanOptics_acquire_plot.py +++ /dev/null @@ -1,297 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Mar 27 16:50:26 2019 - -@author: Sarthak -""" - -import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui, QtWidgets#, QColorDialog -import numpy as np -import pickle -import sys -import seabreeze.spectrometers as sb -from pipython import GCSDevice -import time - -pg.mkQApp() -pg.setConfigOption('background', 'w') - -#uiFile = "OO_acquire_gui.ui" "OO_PZstageScan_acquire_gui" -uiFile = "OO_PZstageScan_acquire_gui.ui" - -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.connect_checkBox.stateChanged.connect(self._handle_spectrometer_connection) - self.ui.live_pushButton.clicked.connect(self.live) - - self.ui.path_to_folder_pushButton.clicked.connect(self.save_file_location) - self.ui.save_single_spec_pushButton.clicked.connect(self.save_single_spec) - - self.ui.init_stage_pushButton.clicked.connect(self.init_piezo_stage) - self.ui.show_curr_pos_pushButton.clicked.connect(self.current_piezo_stage_pos) - self.ui.center_stage_pushButton.clicked.connect(self.center_piezo) - self.ui.abs_mov_pushButton.clicked.connect(self.abs_mov) - self.ui.rel_mov_pushButton.clicked.connect(self.rel_mov) - self.ui.estimate_scan_time_pushButton.clicked.connect(self.estimate_scan_time) - self.ui.start_x_y_sacan_pushButton.clicked.connect(self.x_y_scan) - -# self.ui.clear_pushButton.clicked.connect(self.clear_plot) - self.pi_device = None - self.spec = None - - self.ui.status_textBrowser.setText("Welcome!\nGUI Initiated!") - - self.show() - - def _handle_spectrometer_connection(self): - if self.ui.connect_checkBox.isChecked(): - self.connect_spectrometer() - else: - self.close_connection() - - def connect_spectrometer(self): - if self.spec is None: - devices = sb.list_devices() - self.spec = sb.Spectrometer(devices[0]) - self.ui.status_textBrowser.append("Ocean Optics Device Connected!\n\n Device:\n\n"+str(sb.list_devices()[0])) - else: - self.ui.status_textBrowser.append("Already Connected") - - def init_piezo_stage(self): - if self.pi_device is None: - self.pi_device = GCSDevice("E-710") # Creates a Controller instant - self.pi_device.ConnectNIgpib(board=0,device=4) # Connect to GPIB board - self.ui.status_textBrowser.append('Connected: {}'.format(self.pi_device.qIDN().strip())) - # print('connected: {}'.format(self.pi_device.qIDN().strip())) - - self.axes = self.pi_device.axes[0:2] # selecting x and y axis of the stage - - self.pi_device.INI() - self.pi_device.REF(axes=self.axes) - - self.pi_device.SVO(axes=self.axes, values=[True,True]) # Turn on servo control for both axes - self.ui.status_textBrowser.append("Current Stage Position:\n{}".format(self.pi_device.qPOS(axes=self.axes))) - # print(self.pi_device.qPOS(axes=self.axes)) - else: - self.ui.status_textBrowser.append("Piezo Stage Is Already Initialized!") - - def center_piezo(self): - if self.pi_device is None: - self.init_piezo_stage() - self.pi_device.MOV(axes=self.axes, values=[50,50]) - self.ui.status_textBrowser.append("Piezo Stage Centered: [50x,50y]") - - def current_piezo_stage_pos(self): - if self.pi_device is None: - self.init_piezo_stage() - self.ui.status_textBrowser.append("Current Stage Position:\n{}".format(self.pi_device.qPOS(axes=self.axes))) - - def abs_mov(self): - if self.pi_device is None: - self.init_piezo_stage() - x_abs_pos = self.ui.x_abs_doubleSpinBox.value() - y_abs_pos = self.ui.y_abs_doubleSpinBox.value() - self.pi_device.MOV(axes=self.axes, values=[x_abs_pos,y_abs_pos]) - - def rel_mov(self): - if self.pi_device is None: - self.init_piezo_stage() - x_rel_pos = self.ui.x_rel_doubleSpinBox.value() - y_rel_pos = self.ui.y_rel_doubleSpinBox.value() - self.pi_device.MVR(axes=self.axes, values=[x_rel_pos,y_rel_pos]) - - def estimate_scan_time(self): - x_scan_size = self.ui.x_size_doubleSpinBox.value() - y_scan_size = self.ui.y_size_doubleSpinBox.value() - - x_step = self.ui.x_step_doubleSpinBox.value() - y_step = self.ui.y_step_doubleSpinBox.value() - - if y_scan_size == 0: - y_scan_size = 1 - - if x_scan_size == 0: - x_scan_size = 1 - - if y_step == 0: - y_step = 1 - - if x_step == 0: - x_step = 1 - - y_range = int(np.ceil(y_scan_size/y_step)) - x_range = int(np.ceil(x_scan_size/x_step)) - - total_points = x_range*y_range - - intg_time_ms = self.ui.intg_time_spinBox.value() - scans_to_avg = self.ui.scan_to_avg_spinBox.value() - - total_time = total_points*(intg_time_ms*1e-3)*(scans_to_avg) # in seconds - - self.ui.status_textBrowser.append("Estimated scan time: "+str(np.float16(total_time/60))+" mins") - - def x_y_scan(self): - if self.pi_device is None: - self.init_piezo_stage() - - if self.spec is None: - self.ui.status_textBrowser.append("Spectrometer not connected!\nForce connecting the spectrometer...") - self.connect_spectrometer() - - start_time = time.time() - x_start = self.ui.x_start_doubleSpinBox.value() - y_start = self.ui.y_start_doubleSpinBox.value() - - x_scan_size = self.ui.x_size_doubleSpinBox.value() - y_scan_size = self.ui.y_size_doubleSpinBox.value() - - x_step = self.ui.x_step_doubleSpinBox.value() - y_step = self.ui.y_step_doubleSpinBox.value() - - if y_scan_size == 0: - y_scan_size = 1 - - if x_scan_size == 0: - x_scan_size = 1 - - if y_step == 0: - y_step = 1 - - if x_step == 0: - x_step = 1 - - y_range = int(np.ceil(y_scan_size/y_step)) - x_range = int(np.ceil(x_scan_size/x_step)) - - # Define empty array for saving intensities - data_array = np.zeros(shape=(x_range*y_range,2048)) - - self.ui.status_textBrowser.append("Starting Scan...") - # Move to the starting position - self.pi_device.MOV(axes=self.axes, values=[x_start,y_start]) - - self.ui.status_textBrowser.append("Scan in Progress...") - - k = 0 - for i in range(y_range): - for j in range(x_range): -# print(self.pi_device.qPOS(axes=self.axes)) - - self._read_spectrometer() - data_array[k,:] = self.y - self.ui.plot.plot(self.spec.wavelengths(), self.y, pen='r', clear=True) - pg.QtGui.QApplication.processEvents() - - self.pi_device.MVR(axes=self.axes[0], values=[x_step]) - - self.ui.progressBar.setValue(100*((k+1)/(x_range*y_range))) - k+=1 - # TODO - # if statement needs to be modified to keep the stage at the finish y-pos for line scans in x, and same for y - if i == y_range-1: # this if statement is there to keep the stage at the finish position (in x) and not bring it back like we were doing during the scan - self.pi_device.MVR(axes=self.axes[1], values=[y_step]) - else: - self.pi_device.MVR(axes=self.axes, values=[-x_scan_size, y_step]) - - self.ui.status_textBrowser.append("Scan Complete!\nSaving Data...") - - save_dict = {"Wavelengths": self.spec.wavelengths(), "Intensities": data_array, - "Scan Parameters":{"X scan start (um)": x_start, "Y scan start (um)": y_start, - "X scan size (um)": x_scan_size, "Y scan size (um)": y_scan_size, - "X step size (um)": x_step, "Y step size (um)": y_step}, - "OceanOptics Parameters":{"Integration Time (ms)": self.ui.intg_time_spinBox.value(), - "Scans Averages": self.ui.scan_to_avg_spinBox.value(), - "Correct Dark Counts": self.ui.correct_dark_counts_checkBox.isChecked()} - } - - pickle.dump(save_dict, open(self.save_folder+"/"+self.ui.lineEdit.text()+"_raw_PL_spectra_data.pkl", "wb")) - - self.ui.status_textBrowser.append("Data saved!\nTotal time taken:"+str(np.float16((time.time()-start_time)/60))+" mins") - - def save_file_location(self): - self.save_folder = QtWidgets.QFileDialog.getExistingDirectory(self, caption="Select Folder") - - def save_single_spec(self): - save_array = np.zeros(shape=(2048,2)) - self._read_spectrometer() - save_array[:,1] = self.y - save_array[:,0] = self.spec.wavelengths() - - np.savetxt(self.save_folder+"/"+self.ui.lineEdit.text()+".txt", save_array, fmt = '%.5f', - header = 'Wavelength (nm), Intensity (counts)', delimiter = ' ') - - def live(self): - save_array = np.zeros(shape=(2048,2)) - - self.ui.plot.setLabel('left', 'Intensity', units='a.u.') - self.ui.plot.setLabel('bottom', 'Wavelength', units='nm') - j = 0 - while self.spec is not None:#self.ui.connect_checkBox.isChecked(): # this while loop works! - self._read_spectrometer() - save_array[:,1] = self.y - - self.ui.plot.plot(self.spec.wavelengths(), self.y, pen='r', clear=True) - - if self.ui.save_every_spec_checkBox.isChecked(): - save_array[:,0] = self.spec.wavelengths() - np.savetxt(self.save_folder+"/"+self.ui.lineEdit.text()+str(j)+".txt", save_array, fmt = '%.5f', - header = 'Wavelength (nm), Intensity (counts)', delimiter = ' ') - - pg.QtGui.QApplication.processEvents() - j += 1 - - def _read_spectrometer(self): - if self.spec is not None: - - intg_time_ms = self.ui.intg_time_spinBox.value() - self.spec.integration_time_micros(intg_time_ms*1e3) - - scans_to_avg = self.ui.scan_to_avg_spinBox.value() - Int_array = np.zeros(shape=(2048,scans_to_avg)) - - for i in range(scans_to_avg): #software average - data = self.spec.spectrum(correct_dark_counts=self.ui.correct_dark_counts_checkBox.isChecked()) - Int_array[:,i] = data[1] - self.y = np.mean(Int_array, axis=-1) - - else: - self.ui.status_textBrowser.append("Connect to Spectrometer!") - raise Exception("Must connect to spectrometer first!") - - - def close_connection(self): - if self.spec is not None: - self.spec.close() - self.ui.status_textBrowser.append("Ocean Optics Device Disconnected") - del self.spec - self.spec = None - - 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 - - -win = MainWindow() - - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() diff --git a/src/main/python/PLQE_analysis/column_selection_gui.ui b/src/main/python/PLQE_analysis/column_selection_gui.ui deleted file mode 100644 index c20125c..0000000 --- a/src/main/python/PLQE_analysis/column_selection_gui.ui +++ /dev/null @@ -1,107 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 800 - 600 - - - - Select number of columns - - - - - - - Data preview - - - - - - - - Select data columns - - - - - - - - Ref - - - - - - - Inpath - - - - - - - Outpath - - - - - - - 2 - - - - - - - 3 - - - - - - - 4 - - - - - - - Done - - - - - - - - - - - - - - - - - 0 - 0 - 800 - 31 - - - - - - - - diff --git a/src/main/python/PLQE_analysis/plqe_analysis.py b/src/main/python/PLQE_analysis/plqe_analysis.py deleted file mode 100644 index 3e42a4f..0000000 --- a/src/main/python/PLQE_analysis/plqe_analysis.py +++ /dev/null @@ -1,212 +0,0 @@ -# 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 - -# 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) - if ".txt" in self.filename[0]: - self.data = np.loadtxt(self.filename[0], delimiter = '\t', skiprows = 1) - elif ".csv" in self.filename[0]: - self.data = np.loadtxt(self.filename[0], delimiter = ',', skiprows = 1) - elif ".qua" in self.filename[0]:#TODO: Include a Pop-up window for input for skipping header - self.data = np.genfromtxt(self.filename[0], delimiter = '\t', skip_header = 28) - self.cs_window = ColSelectionWindow(self.data) - self.cs_window.col_selection_signal.connect(self.open_with_col_selection) - 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 open_with_col_selection(self): - ref_data_col = self.cs_window.ui.ref_spinBox.value() - 1 #subtract since spinboxes refer to column num and not index - inpath_data_col = self.cs_window.ui.inpath_spinBox.value() - 1 - outpath_data_col = self.cs_window.ui.outpath_spinBox.value() - 1 - self.ref_data = np.copy(self.data[:,ref_data_col]) - self.inpath_data = np.copy(self.data[:,inpath_data_col]) - self.outpath_data = np.copy(self.data[:,outpath_data_col]) - - 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, pen='r') - 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() - -"""Table view GUI""" -ui_file_path = (base_path / "column_selection_gui.ui").resolve() -col_selection_WindowTemplate, col_selection_TemplateBaseClass = pg.Qt.loadUiType(ui_file_path) - -class ColSelectionWindow(col_selection_TemplateBaseClass): - - col_selection_signal = QtCore.pyqtSignal() #signal to help with pass info back to MainWindow - - def __init__(self, data): - col_selection_TemplateBaseClass.__init__(self) - # Create the param window - self.ui = col_selection_WindowTemplate() - self.ui.setupUi(self) - self.ui.done_pushButton.clicked.connect(self.done) - - self.table_widget = pg.TableWidget() - self.ui.data_preview_groupBox.layout().addWidget(self.table_widget) - - self.table_widget.setData(data) - - #self.setWindowFlag(QtCore.Qt.WindowCloseButtonHint, False) - self.show() - - def done(self): - self.col_selection_signal.emit() - self.ui.textBrowser.setText("Data successfully loaded.") - #self.close() - - def closeEvent(self, event): - self.col_selection_signal.emit() - -"""Run the Main Window""" -def run(): - win = MainWindow() - QtGui.QApplication.instance().exec_() - return win - -#run() diff --git a/src/main/python/PLQE_analysis/plqe_analysis_gui.ui b/src/main/python/PLQE_analysis/plqe_analysis_gui.ui deleted file mode 100644 index 7720b49..0000000 --- a/src/main/python/PLQE_analysis/plqe_analysis_gui.ui +++ /dev/null @@ -1,150 +0,0 @@ - - - Form - - - - 0 - 0 - 575 - 524 - - - - PLQE Analysis - - - - - - 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/src/main/python/Spectrum_analysis/Spectra_fit_funcs.py b/src/main/python/Spectrum_analysis/Spectra_fit_funcs.py deleted file mode 100644 index a96f782..0000000 --- a/src/main/python/Spectrum_analysis/Spectra_fit_funcs.py +++ /dev/null @@ -1,241 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Sun Mar 31 15:46:54 2019 - -@author: Sarthak -""" - -# -*- coding: utf-8 -*- -""" -Created on Thu Jan 31 20:48:03 2019 - -@author: Sarthak -""" -import numpy as np -from lmfit.models import GaussianModel, LorentzianModel - -class Spectra_Fit(object): - """ - Fit a spectrum. - - Attributes: - data: spectrum data (x-axis and y-axis) - ref: reference spectrum (both x and y-axis) for background correction - """ - - def __init__(self, data, ref, wlref = None, fit_in_eV = True): - self.data = data - self.ref = ref - self.wlref = wlref - self.fit_in_eV = fit_in_eV - - def background_correction(self): - """Return the background corrected spectrum""" - x = self.data[:, 0] # wavelengths - y = self.data[:, 1] # intensity - yref = self.ref[:, 1] - y = y - yref # background correction - if self.wlref is not None: - wlref = self.wlref[:,1] - y = y/wlref - - if self.fit_in_eV is True: - x = np.sort(1240/self.data[:, 0]) # converting to eV and sorting in ascending order - y = [y[i] for i in np.argsort(1240/x)] # sorting argument of y acc to x sorting index - # need to do this because data is collected in wavelength - - return [x,y] - -class Single_Gaussian(Spectra_Fit): - """Fit a single gaussian to the spectrum - - Attributes: - data: spectrum data (x-axis and y-axis) - ref: reference spectrum (both x and y-axis) for background correction - """ - - def gaussian_model(self): - x,y = self.background_correction() - gmodel = GaussianModel(prefix = 'g1_') # calling gaussian model - pars = gmodel.guess(y, x=x) # parameters - center, width, height - result = gmodel.fit(y, pars, x=x, nan_policy='propagate') - return result - - # 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() - if self.fit_in_eV is True: - peak_pos = 1240/peak_pos - sigma = 1240/sigma - min_max_range = np.sort(1240/np.asarray(min_max_range)) - gmodel = GaussianModel(prefix = 'g1_') # calling gaussian model - pars = gmodel.guess(y, x=x) # parameters - center, width, height - 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 - -class Single_Lorentzian(Spectra_Fit): - """Fit a single Lorentzian to the spectrum - - Attributes: - data: spectrum data (x-axis and y-axis) - ref: reference spectrum (both x and y-axis) for background correction - """ - - def lorentzian_model(self): - x,y = self.background_correction() - lmodel = LorentzianModel(prefix = 'l1_') # calling lorentzian model - pars = lmodel.guess(y, x=x) # parameters - center, width, height - 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() - if self.fit_in_eV is True: - peak_pos = 1240/peak_pos - sigma = 1240/sigma - min_max_range = np.sort(1240/np.asarray(min_max_range)) - lmodel = LorentzianModel(prefix = 'l1_') # calling lorentzian model - pars = lmodel.guess(y, x=x) # parameters - center, width, height - 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 - -class Double_Gaussian(Spectra_Fit): - """Fit two gaussians to the spectrum - - Attributes: - data: spectrum data (x-axis and y-axis) - ref: reference spectrum (both x and y-axis) for background correction - """ - - 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() - if self.fit_in_eV is True: - peak_pos = 1240/np.asarray(peak_pos) - sigma = 1240/np.asarray(sigma) - min_max_range = np.sort(1240/np.asarray(min_max_range)) - gmodel_1 = GaussianModel(prefix='g1_') # calling gaussian model - pars = gmodel_1.guess(y, x=x) # parameters - center, width, height - 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(peak_pos[1], min = min_max_range[1][0], max = min_max_range[1][1]) - pars['g2_sigma'].set(sigma[1], min = pars['g1_sigma'].value) - pars['g2_amplitude'].set(min = 0) - - gmodel = gmodel_1 + gmodel_2 - result = gmodel.fit(y, pars, x=x, nan_policy='propagate') - return result - -class Multi_Gaussian(Spectra_Fit): - - # 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, fit_in_eV = True): - Spectra_Fit.__init__(self, data, ref, wlref, fit_in_eV=True) - 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 - - composite_model = None - composite_pars = None - - x,y = self.background_correction() - - assert self.num_of_gaussians == len(self.peak_pos), ("Number of gaussians must be equal to the number of peak positions") - assert len(self.min_max_range) == len(self.peak_pos), ("Number of bounds on the range must be equal to the number of peak positions") - - if self.fit_in_eV is True: - peak_pos = 1240/np.asarray(peak_pos) - sigma = 1240/np.asarray(sigma) - min_max_range = np.sort(1240/np.asarray(min_max_range)) - - - 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() - 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(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[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) - - - 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 \ No newline at end of file diff --git a/src/main/python/Spectrum_analysis/Spectra_plot_fit.py b/src/main/python/Spectrum_analysis/Spectra_plot_fit.py deleted file mode 100644 index 69f182b..0000000 --- a/src/main/python/Spectrum_analysis/Spectra_plot_fit.py +++ /dev/null @@ -1,1020 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Mar 27 16:50:26 2019 - -@author: Sarthak -""" - -# 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 matplotlib -import pickle -import lmfit -from lmfit.models import GaussianModel, LinearModel -from scipy import interpolate -import customplotting.mscope as cpm - -sys.path.append(os.path.abspath('../H5_Pkl')) -from H5_Pkl import h5_pkl_view -sys.path.append(os.path.abspath('../Export_Windows')) -try: - from Export_window import ExportFigureWindow, ExportPlotWindow -except: - from Export_Windows.Export_window import ExportFigureWindow, ExportPlotWindow -# local modules -try: - from Spectra_fit_funcs import Spectra_Fit, Single_Gaussian, Single_Lorentzian, Double_Gaussian, Multi_Gaussian - from pyqtgraph_MATPLOTLIBWIDGET import MatplotlibWidget -except: - from Spectrum_analysis.Spectra_fit_funcs import Spectra_Fit, Single_Gaussian, Single_Lorentzian, Double_Gaussian, Multi_Gaussian - from Spectrum_analysis.pyqtgraph_MATPLOTLIBWIDGET import MatplotlibWidget - -matplotlib.use('Qt5Agg') - -"""Recylce params for plotting""" -plt.rc('xtick', labelsize = 10) -plt.rc('xtick.major', pad = 1) -plt.rc('ytick', labelsize = 10) -plt.rc('lines', lw = 1.5, markersize = 3.5) -plt.rc('legend', fontsize = 10) -plt.rc('axes', linewidth=1.5) - -pg.mkQApp() -pg.setConfigOption('background', 'w') - - -base_path = Path(__file__).parent -file_path = (base_path / "Spectra_plot_fit_gui.ui").resolve() - -uiFile = file_path - -WindowTemplate, TemplateBaseClass = pg.Qt.loadUiType(uiFile) - -def updateDelay(scale, time): - """ Hack fix for scalebar inaccuracy""" - QtCore.QTimer.singleShot(time, scale.updateBar) - -class MainWindow(TemplateBaseClass): - - def __init__(self): - pg.setConfigOption('imageAxisOrder', 'row-major') - 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) - - ##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.add_to_mem_pushButton.clicked.connect(self.add_trace_to_mem) - self.ui.export_single_figure_pushButton.clicked.connect(self.export_plot_window) - self.ui.export_scan_figure_pushButton.clicked.connect(self.export_window) - self.ui.analyze_spectra_fits_pushButton.clicked.connect(self.analyze_spectra_fits) - - 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) - - self.ui.export_data_pushButton.clicked.connect(self.export_data) - self.ui.clear_export_data_pushButton.clicked.connect(self.clear_export_data) - - # 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.ui.plot = pg.PlotWidget() - self.ui.plot.setTitle(title="Spectrum Plot") - #self.ui.plot.show() - - 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 - - #variables accounting for data received from FLIM analysis - self.opened_from_flim = False #switched to True in FLIM_plot when "analyze lifetime" clicked - self.sum_data_from_flim = [] - - # fit scan file variable set to None for analyze_spectra_fits - self.fit_scan_file = None - - #container for data to append to txt file - self.data_list = [] - - #for adding traces to memory for plotting/exporting all at once - self.x_mem = [] - self.y_mem = [] - self.best_fit_mem = [] - self.legend = [] - self.single_spec_fit_called = False - - self.show() - - """ Open Single Spectrum files """ - def open_file(self): - try: - self.single_spec_filename = QtWidgets.QFileDialog.getOpenFileName(self) - try: - try: - self.file = np.loadtxt(self.single_spec_filename[0], skiprows = 1, delimiter=" ") - except: - self.file = np.loadtxt(self.single_spec_filename[0], skiprows = 16, delimiter='\t') - except: - self.file = np.genfromtxt(self.single_spec_filename[0], skip_header=1, skip_footer=3, delimiter='\t') - self.opened_from_flim = False - except: - pass - - def open_bck_file(self): - try: - filename = QtWidgets.QFileDialog.getOpenFileName(self) - try: - try: - self.bck_file = np.loadtxt(filename[0], skiprows=1, delimiter=" ") - except: - 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: - try: - self.wlref_file = np.loadtxt(filename[0], skiprows=1, delimiter= " ") - except: - 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, filter="Scan files (*.pkl *.h5)") - self.filename_for_viewer_launch = filename[0] - if ".pkl" in filename[0]: - self.spec_scan_file = pickle.load(open(filename[0], 'rb')) - if self.ui.launch_data_viewer_checkBox.isChecked(): - self.launch_h5_pkl_viewer() - self.scan_file_type = "pkl" - elif ".h5" in filename[0]: - self.spec_scan_file = h5py.File(filename[0], 'r') - if self.ui.launch_data_viewer_checkBox.isChecked(): - self.launch_h5_pkl_viewer() - self.scan_file_type = "h5" - self.get_data_params() - 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) - with pg.BusyCursor(): - self.filename_for_viewer_launch = filename[0] - self.fit_scan_file = pickle.load(open(filename[0], 'rb')) - if self.ui.launch_data_viewer_checkBox.isChecked(): - self.launch_h5_pkl_viewer() # TODO Needs to implement reading the fit result datatype in PKL Viewer - 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.getOpenFileNames(self) - files = self.pkl_to_convert[0] - for i in range(len(files)): - self.filename_for_viewer_launch = files[i] - if self.ui.launch_data_viewer_checkBox_2.isChecked(): - self.launch_h5_pkl_viewer() - except: - pass - - def launch_h5_pkl_viewer(self): - """ Launches H5/PKL viewer to give an insight into the data and its structure""" - viewer_window = h5_pkl_view.H5PklView(sys.argv) - viewer_window.settings['data_filename'] = self.filename_for_viewer_launch - - def analyze_spectra_fits(self): - """Analyze fits to the individual spectrum within a spectra scan fit file""" - if self.fit_scan_file is None: - self.open_fit_scan_file() - - #result_no = int(self.ui.result_spinBox.value()) - #self.matplotlibwidget = MatplotlibWidget(size=(12,8), dpi=300) - #self.fit_scan_file['result_'+str(0)].plot(fig=self.matplotlibwidget.getFigure().add_subplot(111)) - #self.matplotlibwidget.draw() - #self.matplotlibwidget.show() - analyze_window = Analyze(scan_fit_file=self.fit_scan_file) - analyze_window.run() - - 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(False) - self.ui.save_all_checkBox.setEnabled(False) - 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.save_all_checkBox.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.save_all_checkBox.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 check_loaded_files(self): - """ - Check if 'subtract background' or 'white light correction' is checked - and if required files have been loaded. - """ - if self.ui.subtract_bck_radioButton.isChecked() and self.bck_file is None: - self.ui.result_textBrowser.setText("You need to load a background file.") - 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!") - elif self.wlref_file is None and self.ui.WLRef_checkBox.isChecked(): - self.ui.result_textBrowser.setText("You need to load a White Light Ref file.") - else: - return True - - def plot(self): - try: - if self.opened_from_flim: - flim_data = self.sum_data_from_flim.T - interp = interpolate.interp1d(flim_data[:,0], flim_data[:,1]) - x_range = [flim_data[:,0][0], flim_data[:,0][-1]] - xnew = np.linspace(x_range[0], x_range[1], 100 ) - ynew = interp(xnew) - self.file = np.zeros((xnew.shape[0], 2)) - self.file[:,0] = xnew - self.file[:,1] = ynew - self.x = xnew - self.y = ynew - - elif self.file is None: #elif - self.ui.result_textBrowser.setText("You need to load a data file.") - else: - self.x = self.file[:,0] - self.y = self.file[:,1] - - self.check_loaded_files() - - if self.check_loaded_files() == True: #check the following conditions if all required files have been provided - if self.ui.subtract_bck_radioButton.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_radioButton.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_radioButton.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.check_eV_state() - self.ui.plot.show() - self.ui.plot.plot(self.x, self.y, clear=self.clear_check(), pen='r') - - except Exception as e: - self.ui.result_textBrowser.append(str(e)) - pass - self.ui.plot.setLabel('left', 'Intensity', units='a.u.') - if self.ui.fit_in_eV.isChecked(): - self.ui.plot.setLabel('bottom', 'Energy (eV)') - else: - self.ui.plot.setLabel('bottom', 'Wavelength (nm)') - - self.single_spec_fit_called = False - - def normalize(self): - self.y = (self.y) / np.amax(self.y) - - def check_eV_state(self): - if self.ui.fit_in_eV.isChecked(): - self.x = np.sort(1240/self.file[:,0]) - self.y = [self.y[i] for i in np.argsort(1240/self.file[:,0])] - else: - self.x = self.file[:,0] - self.y = self.file[:,1] - - 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 - - def fit_and_plot(self): - fit_func = self.ui.fitFunc_comboBox.currentText() - - try: - self.plot() - if self.opened_from_flim: - self.file = np.zeros((self.x.shape[0], 2)) - self.file[:,0] = self.x - self.file[:,1] = self.y - bck = lmfit.models.LinearModel(prefix='line_') - gmodel = GaussianModel(prefix='g1_') - pars = bck.make_params(intercept=self.y.min(), slope=0) - pars += gmodel.guess(self.y, x=self.x) - comp_model = gmodel + bck - self.result = comp_model.fit(self.y, pars, x=self.x, nan_policy='propagate') - 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()) - - - if self.ui.plot_without_bck_radioButton.isChecked(): #if plot w/o bck, create dummy bck_file - self.bck_file = np.zeros(shape=(self.file.shape[0], 2)) - self.bck_file[:,0] = self.file[:,0] - - # if self.ui.subtract_bck_radioButton.isChecked() == False: - # self.ui.result_textBrowser.setText("You need to check the subtract background option!") - if self.check_loaded_files is None: - pass - elif self.opened_from_flim: - pass - else: - self.check_eV_state() - if fit_func == "Single Gaussian": - single_gauss = Single_Gaussian(self.file, self.bck_file, wlref=self.wlref_file, fit_in_eV=self.ui.fit_in_eV.isChecked()) - 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.plot() - 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_radioButton.isChecked() == True: - single_lorentzian = Single_Lorentzian(self.file, self.bck_file, wlref=self.wlref_file, fit_in_eV=self.ui.fit_in_eV.isChecked()) - - 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.plot() - 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_radioButton.isChecked() == True: - double_gauss = Double_Gaussian(self.file, self.bck_file, wlref=self.wlref_file, fit_in_eV=self.ui.fit_in_eV.isChecked()) - 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.plot() - 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 Gaussian": #and self.ui.subtract_bck_radioButton.isChecked() == True: - #currently only works for triple gaussian (n=3) - multiple_gauss = Multi_Gaussian(self.file, self.bck_file, 3, wlref=self.wlref_file, fit_in_eV=self.ui.fit_in_eV.isChecked()) - 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.plot() - 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()) - - self.data_list.append(self.ui.result_textBrowser.toPlainText()) - self.single_spec_fit_called = True - - except Exception as e: - self.ui.result_textBrowser.append(str(e)) - - def export_window(self): - self.export_window = ExportImages() - self.export_window.export_fig_signal.connect(self.pub_ready_plot_export) - - def export_plot_window(self): - self.exportplotwindow = ExportPlotWindow() - self.exportplotwindow.export_fig_signal.connect(self.pub_ready_plot_export) - - def pub_ready_plot_export(self): - filename = QtWidgets.QFileDialog.getSaveFileName(self,caption="Filename with EXTENSION") - """Recylce 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) - try: - try: - try: - data = self.spec_scan_file - except: - data = self.fit_scan_file - if self.export_window.ui.reverse_checkBox.isChecked(): - colormap = str(self.export_window.ui.cmap_comboBox.currentText())+"_r" - else: - colormap = str(self.export_window.ui.cmap_comboBox.currentText()) - if str(self.export_window.ui.dataChannel_comboBox.currentText()) == "Fitted": - 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, FLIM_adjust = False, stepsize = data['Scan Parameters']['X step size (um)'], cmap=colormap, cbar_label=label, - vmin=self.export_window.ui.vmin_spinBox.value(), vmax=self.export_window.ui.vmax_spinBox.value()) - elif str(self.export_window.ui.dataChannel_comboBox.currentText()) == "Raw": - cpm.plot_confocal(self.sums, FLIM_adjust = False, figsize=(10,10), stepsize = data['Scan Parameters']['X step size (um)'], cmap=colormap, - vmin=self.export_window.ui.vmin_spinBox.value(), vmax=self.export_window.ui.vmax_spinBox.value()) - plt.tick_params(direction='out', length=8, width=3.5) - plt.tight_layout() - plt.savefig(filename[0],bbox_inches='tight', dpi=300) - plt.close() - except: - if self.x_mem == []: - self.ui.result_textBrowser.setText("Add traces to memory first!") - else: - plt.figure(figsize=(8,6)) - plt.tick_params(direction='out', length=8, width=3.5) - for i in range(len(self.x_mem)): - plt.plot(self.x_mem[i], self.y_mem[i], label=str(self.legend[i])) - if self.single_spec_fit_called == True: - plt.plot(self.x_mem[i], self.best_fit_mem[i],'k') - - if self.ui.fit_in_eV.isChecked(): - plt.xlabel("Energy (eV)", fontsize=20, fontweight='bold') - else: - plt.xlabel("Wavelength (nm)", fontsize=20, fontweight='bold') - plt.ylabel("Intensity (a.u.)", fontsize=20, fontweight='bold') - plt.xlim([self.exportplotwindow.ui.lowerX_spinBox.value(),self.exportplotwindow.ui.upperX_spinBox.value()]) - plt.ylim([self.exportplotwindow.ui.lowerY_spinBox.value(),self.exportplotwindow.ui.upperY_doubleSpinBox.value()]) - plt.legend() - 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 export_data(self): - """ Save fit params and srv calculations stored in data_list as .txt """ - folder = os.path.dirname(self.single_spec_filename[0]) - filename_ext = os.path.basename(self.single_spec_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 clear_export_data(self): - self.data_list = [] - self.x_mem = [] - self.y_mem = [] - self.legend = [] - self.best_fit_mem = [] - - def add_trace_to_mem(self): - try: - self.x_mem.append(self.x) - self.y_mem.append(self.y) - if self.single_spec_fit_called == True: - self.best_fit_mem.append(self.result.best_fit) - self.legend.append(self.ui.lineEdit.text()) - except Exception as e: - print(e) - - - """ Scan spectra functions """ - def get_data_params(self): - data = self.spec_scan_file - if self.scan_file_type == "pkl": - self.intensities = data['Intensities'] - self.wavelengths = data['Wavelengths'] - # try: - self.x_scan_size = data['Scan Parameters']['X scan size (um)'] - self.y_scan_size = data['Scan Parameters']['Y scan size (um)'] - self.x_step_size = data['Scan Parameters']['X step size (um)'] - self.y_step_size = data['Scan Parameters']['Y step size (um)'] - # except: # TODO test and debug loading pkl file w/o scan parameters - # self.configure_scan_params() - # while not hasattr(self, "scan_params_entered"): - # pass - # self.x_scan_size = self.param_window.ui.x_scan_size_spinBox.value() - # self.y_scan_size = self.param_window.ui.y_scan_size_spinBox.value() - # self.x_step_size = self.param_window.ui.x_step_size_spinBox.value() - # self.y_step_size = self.param_window.ui.y_step_size_spinBox.value() - - else: #run this if scan file is h5 - self.x_scan_size = data['Scan Parameters'].attrs['X scan size (um)'] - self.y_scan_size = data['Scan Parameters'].attrs['Y scan size (um)'] - self.x_step_size = data['Scan Parameters'].attrs['X step size (um)'] - self.y_step_size = data['Scan Parameters'].attrs['Y step size (um)'] - self.intensities = data['Intensities'][()] #get dataset values - self.wavelengths = data['Wavelengths'][()] - - self.numb_x_pixels = int(np.ceil(self.x_scan_size/self.x_step_size)) - self.numb_y_pixels = int(np.ceil(self.y_scan_size/self.y_step_size)) - - """Open param window and get peak center range values and assign it to variables to use later""" - # def configure_scan_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 plot_fit_scan(self): - try: - if self.ui.use_raw_scan_settings.isChecked(): - num_x = self.numb_x_pixels - num_y =self.numb_y_pixels - 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)) -# sigma = np.zeros(shape=(numb_of_points,1)) -# height = np.zeros(shape=(numb_of_points,1)) - - if type(self.fit_scan_file['result_0']) == dict: - for i in range(numb_of_points): - fwhm[i, 0] = 2.3548200*self.fit_scan_file['result_'+str(i)]['g1_sigma'] - pk_pos[i, 0] = self.fit_scan_file['result_'+str(i)]['g1_center'] -# sigma[i, 0] = self.fit_scan_file['result_'+str(i)]['g1_sigma'] -# height[i, 0] = self.fit_scan_file['result_'+str(i)].values['g1_height'] - - elif type(self.fit_scan_file['result_0']) == lmfit.model.ModelResult: - 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 num_y == 1: - x = np.linspace(0, self.x_scan_size, num_x) - self.graph_layout=pg.GraphicsLayoutWidget() - self.plot = self.graph_layout.addPlot(title="Line Scan") - self.plot.plot(x, self.img[:,0], pen="r") - self.graph_layout.show() - elif num_x == 1: - y = np.linspace(0, self.y_scan_size, num_y) - self.graph_layout=pg.GraphicsLayoutWidget() - self.plot = self.graph_layout.addPlot(title="Line Scan") - self.plot.plot(y, self.img[0,:], pen="r") - self.graph_layout.show() - - else: - self.fit_scan_viewbox = pg.ImageView() - if self.ui.use_raw_scan_settings.isChecked(): - self.fit_scan_viewbox.setImage(self.img, scale= - (self.x_step_size, - self.y_step_size)) - scale = pg.ScaleBar(size=2,suffix='um') - scale.setParentItem(self.fit_scan_viewbox.view) - scale.anchor((1, 1), (1, 1), offset=(-30, -30)) - self.fit_scan_viewbox.view.sigRangeChanged.connect(lambda: updateDelay(scale, 10)) - else: - self.fit_scan_viewbox.setImage(self.img) - - self.fit_scan_viewbox.view.invertY(False) - self.fit_scan_viewbox.show() - - except Exception as e: - self.ui.result_textBrowser2.append(str(e)) - pass - - def plot_raw_scan(self): - try: - # TODO test line scan plots - - intensities = self.intensities.T #this is only there because of how we are saving the data in the app - intensities = np.reshape(intensities, newshape=(2048,self.numb_x_pixels, self.numb_y_pixels)) - self.raw_scan_viewbox = pg.ImageView() - self.raw_scan_viewbox.setImage(intensities, scale= - (self.x_step_size, - self.y_step_size), xvals=self.wavelengths) - - #roi_plot = self.ui.raw_scan_viewBox.getRoiPlot() - #roi_plot.plot(data['Wavelengths'], intensities) - self.raw_scan_viewbox.view.invertY(False) - scale = pg.ScaleBar(size=2,suffix='um') - scale.setParentItem(self.raw_scan_viewbox.view) - scale.anchor((1, 1), (1, 1), offset=(-30, -30)) - self.raw_scan_viewbox.view.sigRangeChanged.connect(lambda: updateDelay(scale, 10)) - self.raw_scan_viewbox.show() - - except Exception as e: - self.ui.result_textBrowser2.append(str(e)) - - def plot_intensity_sums(self): - try: - # TODO test line scan plots - - #intensities = np.reshape(intensities, newshape=(2048, numb_pixels_X*numb_pixels_Y)) - - sums = np.sum(self.intensities, axis=-1) - self.sums = np.reshape(sums, newshape=(self.numb_x_pixels, self.numb_y_pixels)) - self.intensity_sums_viewBox = pg.ImageView() - - self.intensity_sums_viewBox.setImage(self.sums, scale= - (self.x_step_size, - self.y_step_size)) - self.intensity_sums_viewBox.view.invertY(False) - - scale = pg.ScaleBar(size=2,suffix='um') - scale.setParentItem(self.intensity_sums_viewBox.view) - scale.anchor((1, 1), (1, 1), offset=(-30, -30)) - self.intensity_sums_viewBox.view.sigRangeChanged.connect(lambda: updateDelay(scale, 10)) - self.intensity_sums_viewBox.show() - - 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") - print("Using Single Gaussian to Fit\nThis is the only fitting functions implemented") - - with pg.BusyCursor(): - 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()) - - if self.bck_file is None: - print("Load Background file!") - ref = self.bck_file - index = (ref[:,0]>start_nm) & (ref[:,0]start_nm) & (x - - MainWindow - - - - 0 - 0 - 2115 - 1483 - - - - - 0 - 0 - - - - Spectral Analysis - - - - - - - - - - 12 - - - - Fit Settings - - - - - - false - - - - 10 - - - - n: - - - - - - - false - - - - 10 - - - - 1 - - - 1 - - - - - - - false - - - - 10 - - - - Plot components (>1 Gaussian) - - - true - - - - - - - false - - - - 10 - - - - Bounds - - - - - - 0 - - - - - - - - - - - - - - max - - - - - - - min - - - - - - - 9999.000000000000000 - - - 780.000000000000000 - - - - - - - 9999.000000000000000 - - - 760.000000000000000 - - - - - - - Peak Center 1 (nm) - - - - - - - - - - - - - - - - - - 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 - - - - - - - Sigma 1 (nm) - - - - - - - Peak Center 1 (nm) - - - - - - - 9999.000000000000000 - - - 770.000000000000000 - - - - - - - 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/Plot in eV - - - true - - - - - - - - 10 - - - - Fit Single Spectrum - - - - - - - false - - - Save Entire Fit Model (File will be large and will take longer) - - - - - - - false - - - - 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 - - - - 0 - - - false - - - - Single Spectrum - - - - - - - 0 - 0 - - - - - 350 - 16777215 - - - - - 12 - - - - Load Settings - - - - - - - 10 - - - - Spectrum File - - - - - - - - 10 - - - - Export Figure - - - - - - - - 10 - - - - Clear Export Memory - - - - - - - - 10 - - - - Correct for White Light - - - - - - - - 10 - - - - White Light Ref File - - - - - - - - 10 - - - - Enter Legend Here... - - - - - - - - 10 - - - - For Single Spectrum - - - - - - - - 10 - - - - Subtract Background - - - true - - - - - - - - 10 - - - - Export Fit Data - - - - - - - - 10 - - - - Add Trace to Memory - - - - - - - - 10 - - - - Plot without Background - - - - - - - - 10 - 50 - false - - - - Plot - - - - - - - - 10 - - - - Background File - - - - - - - - 10 - - - - Clear Plots Everytime - - - true - - - - - - - - 10 - - - - Normalize - - - - - - - - 10 - false - - - - Clear Plot - - - - - - - Export Settings - - - - - - - For Data - - - - - - - For Figure - - - - - - - Plot Control - - - - - - - - - - - 10 - - - - - - - - - Scan Spectra Data - - - - - - - - - 210 - 16777215 - - - - - 12 - - - - Load Settings - - - - - - - 12 - - - - Spectra Scan -File - - - - - - - - 12 - - - - Background -File - - - - - - - - 12 - - - - Load Only - Fit File - - - - - - - - 12 - - - - For Scan Data - - - - - - - - - - - 12 - - - - - - - - - 12 - - - - Export Fitted -Scan - - - - - - - - 12 - - - - Launch Data Viewer - - - - - - - - 12 - - - - After Fitting Scan Data - - - - - - - - 12 - - - - Intensity Sums - - - - - - - - 12 - - - - For Raw Scan Data - - - - - - - - 12 - - - - Plot - - - - - - - - 12 - - - - Plot - - - - - - - - 12 - - - - - pk_pos - - - - - fwhm - - - - - - - - - 12 - - - - Plot - - - - - - - - 12 - - - - Export Settings - - - - - - - - 12 - - - - Analyze Spectra Fits - - - - - - - - 12 - - - - For Fit Scale - - - - - - - - 12 - - - - Use Raw Scan Settings - - - true - - - - - - - - 12 - - - - # X points - - - - - - - - 12 - - - - # Y points - - - - - - - 2000 - - - 100 - - - - - - - 2000 - - - 100 - - - - - - - - - - .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 - - - - .pkl to .h5 - - - - - - - 20 - 20 - 323 - 40 - - - - - 12 - - - - Import .pkl file - - - - - - 390 - 20 - 327 - 43 - - - - - 12 - - - - Launch Data Viewer - - - - - - - - - - - 0 - 0 - 2115 - 38 - - - - - - - - diff --git a/src/main/python/Spectrum_analysis/__init__.py b/src/main/python/Spectrum_analysis/__init__.py deleted file mode 100644 index b4011af..0000000 --- a/src/main/python/Spectrum_analysis/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import absolute_import, division, print_function \ No newline at end of file diff --git a/src/main/python/Spectrum_analysis/analyze_fit_results.ui b/src/main/python/Spectrum_analysis/analyze_fit_results.ui deleted file mode 100644 index 7669c5a..0000000 --- a/src/main/python/Spectrum_analysis/analyze_fit_results.ui +++ /dev/null @@ -1,91 +0,0 @@ - - - Form - - - - 0 - 0 - 664 - 136 - - - - Analze Fit Results - - - - - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - 12 - - - - Check! - - - - - - - - 12 - - - - 1000000000 - - - - - - - - 12 - - - - Result number: - - - Qt::AlignCenter - - - - - - - - 15 - - - - Analyze Fit Results - - - Qt::AlignCenter - - - - - - - - - - - - - diff --git a/src/main/python/Spectrum_analysis/pyqtgraph_MATPLOTLIBWIDGET.py b/src/main/python/Spectrum_analysis/pyqtgraph_MATPLOTLIBWIDGET.py deleted file mode 100644 index ba2f59c..0000000 --- a/src/main/python/Spectrum_analysis/pyqtgraph_MATPLOTLIBWIDGET.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Mon Sep 2 13:02:50 2019 - -@author: Sarthak -""" - -from pyqtgraph.Qt import QtGui, QtCore, USE_PYSIDE, USE_PYQT5 -import matplotlib - -#if not USE_PYQT5: -# if USE_PYSIDE: -# matplotlib.rcParams['backend.qt4']='PySide' -# -# from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas -# from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar -#else: -from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas -from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar - -from matplotlib.figure import Figure - -if matplotlib.get_backend() is not 'Qt5Agg': - matplotlib.use('Qt5Agg') - -class MatplotlibWidget(QtGui.QWidget): - """ - Implements a Matplotlib figure inside a QWidget. - Use getFigure() and redraw() to interact with matplotlib. - - Example:: - - mw = MatplotlibWidget() - subplot = mw.getFigure().add_subplot(111) - subplot.plot(x,y) - mw.draw() - """ - - def __init__(self, size=(5.0, 4.0), dpi=100): - QtGui.QWidget.__init__(self) - self.fig = Figure(size, dpi=dpi) - self.canvas = FigureCanvas(self.fig) - self.canvas.setParent(self) - self.toolbar = NavigationToolbar(self.canvas, self) - - self.vbox = QtGui.QVBoxLayout() - self.vbox.addWidget(self.toolbar) - self.vbox.addWidget(self.canvas) - - self.setLayout(self.vbox) - - def getFigure(self): - return self.fig - - def draw(self): - self.canvas.draw() \ No newline at end of file diff --git a/src/main/python/Spectrum_analysis/scan_params_input.ui b/src/main/python/Spectrum_analysis/scan_params_input.ui deleted file mode 100644 index 4df8d40..0000000 --- a/src/main/python/Spectrum_analysis/scan_params_input.ui +++ /dev/null @@ -1,68 +0,0 @@ - - - Form - - - - 0 - 0 - 542 - 211 - - - - Form - - - - - - X scan size - - - - - - - - - - Y scan size - - - - - - - - - - X step size - - - - - - - - - - Y step size - - - - - - - - - - Done - - - - - - - - diff --git a/src/main/python/Table/Table_widget.py b/src/main/python/Table/Table_widget.py deleted file mode 100644 index 4c7b447..0000000 --- a/src/main/python/Table/Table_widget.py +++ /dev/null @@ -1,145 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Mon Sep 2 17:04:49 2019 - -@author: Sarthak -""" - -from pathlib import Path -import pyqtgraph as pg -from pyqtgraph.python2_3 import asUnicode -from pyqtgraph.Qt import QtCore, QtGui - - -pg.mkQApp() -pg.setConfigOption('background', 'w') - - -base_path = Path(__file__).parent -file_path = (base_path / "Table_widget_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) - - self.clear() - - self.ui.clear_pushButton.clicked.connect(self.clear) - self.ui.add_row_pushButton.clicked.connect(self.add_row) - self.ui.add_column_pushButton.clicked.connect(self.add_column) - - """Saving and Copying --- implemented from pyqtgraph TableWidget""" - self.contextMenu = QtGui.QMenu() - self.contextMenu.addAction('Copy Selection').triggered.connect(self.copySel) - self.contextMenu.addAction('Copy All').triggered.connect(self.copyAll) - self.contextMenu.addAction('Save Selection').triggered.connect(self.saveSel) - self.contextMenu.addAction('Save All').triggered.connect(self.saveAll) - - self.show() - - def clear(self): - self.ui.tableWidget.clear() - self.verticalHeadersSet = False - self.horizontalHeadersSet = False - - def add_row(self): - row_position = self.ui.tableWidget.rowCount() - self.ui.tableWidget.insertRow(row_position) - - def add_column(self): - column_position = self.ui.tableWidget.columnCount() - self.ui.tableWidget.insertColumn(column_position) - - def save_table(self):# Needs to be implemented - print(self.ui.tableWidget.currentItem().text()) - - def serialize(self, useSelection=False): - """Convert entire table (or just selected area) into tab-separated text values""" - if useSelection: - selection = self.ui.tableWidget.selectedRanges()[0] - rows = list(range(selection.topRow(), - selection.bottomRow() + 1)) - columns = list(range(selection.leftColumn(), - selection.rightColumn() + 1)) - else: - rows = list(range(self.ui.tableWidget.rowCount())) - columns = list(range(self.ui.tableWidget.columnCount())) - - data = [] - if self.horizontalHeadersSet: - row = [] - if self.verticalHeadersSet: - row.append(asUnicode('')) - - for c in columns: - row.append(asUnicode(self.ui.tableWidget.horizontalHeaderItem(c).text())) - data.append(row) - - for r in rows: - row = [] - if self.verticalHeadersSet: - row.append(asUnicode(self.ui.tableWidget.verticalHeaderItem(r).text())) - for c in columns: - item = self.ui.tableWidget.item(r, c) - if item is not None: - row.append(asUnicode(item.text())) - else: - row.append(asUnicode('')) - data.append(row) - - s = '' - for row in data: - s += ('\t'.join(row) + '\n') - return s - - - def copySel(self): - """Copy selected data to clipboard.""" - QtGui.QApplication.clipboard().setText(self.serialize(useSelection=True)) - - def copyAll(self): - """Copy all data to clipboard.""" - QtGui.QApplication.clipboard().setText(self.serialize(useSelection=False)) - - - def saveSel(self): - """Save selected data to file.""" - self.save(self.serialize(useSelection=True)) - - - def saveAll(self): - """Save all data to file.""" - self.save(self.serialize(useSelection=False)) - - - def save(self, data): - fileName = QtGui.QFileDialog.getSaveFileName(self, "Save As..", "", "Tab-separated values (*.tsv)") - if fileName == '': - return - open(fileName[0], 'w').write(data) - - def contextMenuEvent(self, ev): - self.contextMenu.popup(ev.globalPos()) - - def keyPressEvent(self, ev): - if ev.key() == QtCore.Qt.Key_C and ev.modifiers() == QtCore.Qt.ControlModifier: - ev.accept() - self.copySel() -# else: -# QtGui.QTableWidget.keyPressEvent(self, ev) -"""Run the Main Window""" -def run(): - win = MainWindow() - QtGui.QApplication.instance().exec_() - return win - -#run() \ No newline at end of file diff --git a/src/main/python/Table/Table_widget_gui.ui b/src/main/python/Table/Table_widget_gui.ui deleted file mode 100644 index d5fae7e..0000000 --- a/src/main/python/Table/Table_widget_gui.ui +++ /dev/null @@ -1,70 +0,0 @@ - - - Form - - - - 0 - 0 - 658 - 438 - - - - TableWidget - - - - - - Add Row - - - - - - - Add Column - - - - - - - Clear - - - - - - - 12 - - - 6 - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/python/Table/__init__.py b/src/main/python/Table/__init__.py deleted file mode 100644 index 1a21e72..0000000 --- a/src/main/python/Table/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Mon Sep 2 17:52:35 2019 - -@author: Sarthak -""" - diff --git a/src/main/python/UV_Vis_analysis/uv_vis_analysis.py b/src/main/python/UV_Vis_analysis/uv_vis_analysis.py deleted file mode 100644 index 6302a40..0000000 --- a/src/main/python/UV_Vis_analysis/uv_vis_analysis.py +++ /dev/null @@ -1,188 +0,0 @@ -# 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.correct_for_scattering_checkBox.stateChanged.connect(self.scattering_checkBox_state) - self.scatter_corrected = False - 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 = 2) - 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 scattering_checkBox_state(self): - if self.ui.correct_for_scattering_checkBox.isChecked(): - self.scatter_corrected = True - self.ui.mean_radioButton.setEnabled(True) - self.ui.fourth_orderpoly_radioButton.setEnabled(True) - else: - self.scatter_corrected = False - self.ui.mean_radioButton.setEnabled(False) - self.ui.fourth_orderpoly_radioButton.setEnabled(False) - - def plot_absorbance(self): - try: - self.plotted_absorbance = self.Absorbance #by default set to original absorbance data - if self.scatter_corrected == True: - if self.ui.fourth_orderpoly_radioButton.isChecked(): - p = (np.polyfit(self.Wavelength[(self.Wavelength>self.correction_region_min) & (self.Wavelengthself.correction_region_min) & (self.Wavelengthself.correction_region_min) & (self.Wavelengthself.correction_region_min) & (self.Wavelengthself.correction_region_min) & (self.Wavelengthself.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/src/main/python/UV_Vis_analysis/uv_vis_analysis_gui.ui b/src/main/python/UV_Vis_analysis/uv_vis_analysis_gui.ui deleted file mode 100644 index 85bd935..0000000 --- a/src/main/python/UV_Vis_analysis/uv_vis_analysis_gui.ui +++ /dev/null @@ -1,268 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 742 - 924 - - - - UV-Vis Analysis - - - - - - - UV-Vis plot - - - - - - - - Scattering Correction - - - - - - - Plot - - - - - - - false - - - 4th Order Polynomial - - - true - - - - - - - Clear - - - - - - - Correct for scattering - - - - - - - false - - - Simple Mean - - - - - - - - - - 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 - 21 - - - - - File - - - - - - - - - Load data - - - - - - diff --git a/src/main/python/__init__.py b/src/main/python/__init__.py deleted file mode 100644 index 8b13789..0000000 --- a/src/main/python/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/main/python/main.py b/src/main/python/main.py deleted file mode 100644 index 63b241d..0000000 --- a/src/main/python/main.py +++ /dev/null @@ -1,111 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Apr 10 14:41:08 2019 - -@author: Sarthak -""" - -# system imports -import sys -from pathlib import Path - -import pyqtgraph as pg -from pyqtgraph.Qt import QtGui, QtCore - -from fbs_runtime.application_context.PyQt5 import ApplicationContext, cached_property -from PyQt5.QtWidgets import QMainWindow -from PyQt5 import uic - -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 -from Image_analysis import Image_analysis -from Table import Table_widget -from Export_Windows import Multi_Trace_Exporter - -class AppContext(ApplicationContext): - def run(self): - self.main_window.show() - return self.app.exec_() - - def get_main_ui(self): - qtCreatorFile = self.get_resource("DataBrowser_GUI.ui") - return qtCreatorFile - - @cached_property - def main_window(self): - return MainWindow(self.get_main_ui()) - -# pg.mkQApp() -# #pg.setConfigOption('background', 'w') - -# base_path = Path(__file__).parent -# file_path = (base_path / "DataBrowser_GUI.ui").resolve() - -# uiFile = file_path - -# WindowTemplate, TemplateBaseClass = pg.Qt.loadUiType(uiFile) - -class MainWindow(QMainWindow): - - def __init__(self, uiFile): - super(MainWindow, self).__init__() - - # Create the main window - self.ui = uic.loadUi(uiFile, self) - # 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", "Image Analysis", "Table View", - "Mulit-Trace Exporter"]) - 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_()) - elif analysis_software == "Image Analysis": - self.image_window = Image_analysis.MainWindow() - self.image_window.show() - elif analysis_software == "Table View": - self.table_widget = Table_widget.MainWindow() - self.table_widget.show() - elif analysis_software == "Mulit-Trace Exporter": - self.trace_exporter = Multi_Trace_Exporter.MainWindow() - self.trace_exporter.show() - - -def run(): - appctxt = AppContext() # 1. Instantiate ApplicationContext - appctxt.app.setStyle("Fusion") - exit_code = appctxt.run() - sys.exit(exit_code) # 2. Invoke appctxt.app.exec_() - -if __name__ == '__main__': - run() \ No newline at end of file diff --git a/src/main/python/main_0.py b/src/main/python/main_0.py deleted file mode 100644 index 4038d57..0000000 --- a/src/main/python/main_0.py +++ /dev/null @@ -1,15 +0,0 @@ -from fbs_runtime.application_context.PyQt5 import ApplicationContext -from PyQt5.QtWidgets import QMainWindow - -import sys - -class AppContext(ApplicationContext): - pass - -if __name__ == '__main__': - appctxt = AppContext() # 1. Instantiate ApplicationContext - window = QMainWindow() - window.resize(250, 150) - window.show() - exit_code = appctxt.app.exec_() # 2. Invoke appctxt.app.exec_() - sys.exit(exit_code) \ No newline at end of file diff --git a/src/main/resources/base/DataBrowser_GUI.ui b/src/main/resources/base/DataBrowser_GUI.ui deleted file mode 100644 index 054b4ef..0000000 --- a/src/main/resources/base/DataBrowser_GUI.ui +++ /dev/null @@ -1,97 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 435 - 221 - - - - GLabViz - DataBrowser - - - - - - - - 30 - 75 - true - - - - GLabViz - - - Qt::AlignCenter - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - 15 - - - - Analysis Tool : - - - - - - - - 15 - - - - - - - - - 15 - - - - Load - - - - - - - - - 0 - 0 - 435 - 21 - - - - - - - - From ee8b496b014ccde2520f921436e72a5efbeb444f Mon Sep 17 00:00:00 2001 From: SarthakJariwala Date: Wed, 3 Mar 2021 13:57:29 -0800 Subject: [PATCH 09/10] new screenshots --- Screenshots/GLabViz_Lifetime_analysis_2.png | Bin 115272 -> 67330 bytes Screenshots/GLabViz_interface_1.png | Bin 8358 -> 5603 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/Screenshots/GLabViz_Lifetime_analysis_2.png b/Screenshots/GLabViz_Lifetime_analysis_2.png index 979384450a4228bd09b3fb71ca0d31b973afde6f..dc09da764b7263d86002fa4bfce9ec899aa52d56 100644 GIT binary patch literal 67330 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 !ef`?~nDd4({_6 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 M6-!94`w0up?G6`gUb6x zWiH7Q_*RPq(cZBOHVJ&N0H$vE9e!yn#c-GCWq1N0qpricO@^6c>K1mz0g7Q_NHuLs z*8cL05nmtM{SK>L6U$G@-!Z?PF3DgQa{bsX58p#DsgIej-Ns<>^(U(G?_?tuLs|w; z58r1vOVq!q$s4HfZml`rH%PLgVtd8$b*uf(n;n(_e`oKD^cv*K6i>lsfs5y#?E$>2 zg~ntzI06Odriacu@6Ptvt8+XJv-s#m?*K?^d=V4>IqKRDKG1cIuUl4jRqqmWIc7BI z)uuq~_BqHQJO1y*B4&r^*RrK5#n#F17SvFH_{}#JxoaZAr39?bF`u+@7oQGZ*Fw#8 z7UW^oy(p>6=WG3Q59x#g{45|DFVa8%SSNmjfQ|0iH%addw1%>r{cd3f zQuew$!8;t$fMy_&yg_d8OGW(Y_vRDd9wVfTC`#$yI@vZUSSI5Z$YRy(`a{Vd^|c(_ zDCC5F3Vqvpf%d-!dhsj6KBbKJYU$~K&IWxELZR09_f>%39QK0hM&DjH@YpzgyVlkpKK!K2hkMQXG`&{z(pe$@Ha zb=2-anLeCT!Sk6az_8S5e+Iow{JUnL*Z3(QMT60M(CpxvL*UZ6t^JKXNSLU!kt|Q{ zwjH=wA?r#~xcLt+F>k{?2pDT7eJq(@ae!~1YC0HKf5jD)tg@BuBs9N~96O2UH%19d zX~~tkX3bSPT;AcFLSsxbo%dsF5U&1E%F5yBJR4%pWjbOx7$%~f=k_*h4uTQ$<`SQb zUO=m5e=z=(hBn@lo$;hJsWl(k-mjaCCMhhj9*7U2H2y+M8G-?LU+HG|%T^A;G;a~mi;H>T>RKtnSdDSrJpCQyN}qjr?47lJ}}8oE3Flw|#l8&Cfa9*rDEb zCAFno;mOLHX;a(XZx(zO005-L7Box&hTWd-*^-^bt#VvxU2Apz*>qE1!|Dk7)1)r> z2tqRXpnhWGpx+x@IHuH)-z>gCKUZk;psX+;`^3`4gyNGz)WFR&}BZ4II|9{0Oa{_v4>P&Jtf+C`37(4{)1BJX~D)*a2??r( z1F6UYX&+*HZ6kV1VbH*dq;V0fFtzR3WG}tha0n5sROUICi)NcO!3x1fI7Tm$1ybPf z3v|#{Mka6$peUINN+ST20=m9kx=Zk6Pi*Y{%KlOlQV?> zWK&6_Cf#`>D1MQ|MDkN)BM;FbR-(&EpX~9()=%sa^G=)}+n+o*A0jSveCn!gf!A&Y zI1-DV3JeMDNyktZp0xi(=j`Rt{zKf?`FAA%jBTO6IBeAMSqBvj${;nE@6pXJTjQS_ zqGJNW`%O_xr$Pe2-9@rKKGbr)+u1mAehf?2hd~Z86M>m`hy|)-4PY~MgA~7`@fF8( zxXbQj%s1a;9h>)gJq=D-B;+f4TDs~I6t0Bm4E3isoCN;bqP6Nkqq7_8np3a-1#-OW zz$;rAbwLTuJRmF!jo9#i)d%nVqz2iLLmGvMh7mH>Mx2u33GwmoHoC3^FW{pW)U)65 zi7m#)>AqMfHn@Sy-6;gqJSq;wz<@U1rqx|s;^~1bL&km|b9Z7v0P5p~)GsxJpT2tO zCp&03>Ba&)Ks_!*Qul%Pd4FEnlU# z7yE?BENrFJW_pZ^RlHYQ5g=ibx7nptbNb9DfCuj9NXE?*Sj;S1{{VmLDwj90yN%;P z)s^*8j_3KfW*anK)4Cu(XZ?4yT^;I^H(0o z9jfBi5<}A!r`h`YZqC5A#0Zhi^fE+SUGyX@<#$ledEiz;f@1$V96i9LUcMu|Bs+js z0=gDnKLi16i?Kwh}Gy0JrI+ZDZF5k6v z;Br#9GHE3|xPDMsFr$68Q6i0Y(feE%csP@%B!uKa}WRaV6gU^_tu zJ5-I|f)$YRTQxTuqx4f>(XhSzw<1@<`OiGm)G%taShAHgKB5-zpPJQfuC1}`J<*0q zew#l`xeW^RVJ~C_0-gEEuprcTLAzQe4|~J6S0n1<6CT{5T^jRn4RoF6H^@noKU^Ph^Ur!?qBF*_g1DNTZ`W&_o|Cm+B8XBa`Vie zlR@%erzj^bEh0exT4m@qUpfqA7Yyv1gZft;{S+(&{QLmnNuU=zA2q|Vt}ME~%_}o= z=FY&hz$Wu51Sl%qUrM z{cvCU@U1$5C%8D{_?ui`&7vpW&On2jsnCh=7RNM)^w{L@&^7ZS?#e7gfC@VX)Iw`c zZ{KAZ)wFp|qV=Zt{)mzvsqNERn(wJs{HhjMD-PDXn=U`sdX%74H-AdRo=x4fH^?bk zcaM0hJ(nkU;CkYOKTBfhCs(0K4XJqtqITi@BBr4y*4kf1g$9RBJtY-hxx^wV&ah2S_vNbJ8N$XsSQEME_Baj8hy5k*iw7eE=Gnw9v1-9RNEKrP1= zP={^8`512^>9k;w{8=IqmGArznmo~PWSHKcRUr$rA1^vb&I7cy00TJW6ol?%g&%Ae zAE?5`b^Ajo{v3jzMH`C8+3tng4Bp zo0?$LG*ccQB0wG~tbZ8xH>y$J0{2xItRBJc;DMEFnirq%p`;MFOYsgNi;PSG2I98D zbnU%4bet#KL`>ZYmb3&L+&(rsdg}!x?J>O(1uM}vOg-Ar4N6a6Z+i~fi@NN7jA;PL zUll{2V74e+cUb8G49B^JhC>9)Zxu{FxhnJ64d^|Q@%1BY zN|`}enPSs9eBo3eH^f$$+`TQn;N~2;dXMS+8@@$Uc0tY#zVMdK$^jZ1FYi%%F&=lx zq`)J%lPu2k1lh3u&dyF%!mEbV0Jz9MhElKe9sl7pXgKm--*7@|Yd3-(5w+A-fA`c( zdzW#qGDHYSw1_%)-IYD(dveA&<5Ii{pIFq_3lehqGDd=oxH=*M*u`I0MyX!4i{s&d z`Wkv0ipY{Wsch5)$S5CT^CS%QL3%MT=H1(63oLYs z#aZIOH2>~k-IO|0awj&6E!8`cXg`Gc!8|#-a&B#bb(5OPPl>3Jt)}|>?zu|w#^kJnL-M=_McLOwDiGE`ect=zB&1zE* zqT!4nSE~AX|8d}JbcEA0*qz0>k-@*Sz%5q2^#|@a%-IU#=jv_l%y0uZ`33c+>%g#w zP)e)@vXaIpFZ*$RZVR9mb%$CEo@fS{lw9Ci429O@RHg~iV{zZXW%F{Y0&)v#{%`ww zdEJ5rP~8QYNU0oRufKXT^W%+vzN1bGg-1o~0BurIM^@JKUOyJ!9No>&M4lhH@+(`P zoGI{N^Nv$0pHKZPIeDqEQOf#0f6m%U+Cw{WnqaQ5fz}83{_s*)qCf%!dQ{?#6GFuW z%lUz(f+2TgmPZ5*7Q|F_1>nTk*Bx0CEJZi{3l6(4-1fehq8L|rW^hJEy?xU-Hota^ zP-9a(oqWplfS&oIKXsL?ei??y~NDQ6ed32(*iJy;1C0ma9vCsPXw&xCm* zK1fW7&_5SchVz}$a)k-q_`WZ}k$O$ud9hn;usEtNcPfw!Sm;g{)5(!W#fPy&pz`u3 za0o_=C?j(rMm&@!IJ&@HbB9I6%iT%b!-j-jN0WSL!~fW{=9_8%{c6M3>2|?!jkH0- z`V5Lc7Q3NPjKmI@qE-AGP%S^|KCX99pXUI36sQhLervHzKp6SOWV>so*;sT zK>%#>*B9fYU(#&n!|4rx!d{omWcY@rA`{~(8HH|D1^opB7(aW_ofT*hCIBb-B^Uig z$_y|?@-zAYpefxb%Cjx~RWg0TdA}`YzJ@>JMS`|#1d3`a9+)MLPrb1rkVgNkuxiPD`Si1{pvZk%v{`TPmW3kl}!Mq=&B1S8YhTS`DI&R$i#{FQs$i_u=?-= z5Qa-wXEiZLhY5Du?fUIlclLzHQV z@V-c@LrSn4Up%2o5Mx{qr}Oa8Q=Be&L2tNZQ9p`zEhq*wN-3#R<_^+wduv}ygXr-A zt|h2`xJg(No6Xa<=Kf4$8Hp93H%6l?!xdlwi_{rZ908eYaCU%fo|vP$L1Ybx^0CBd zW0=3Y0K52j=uljK>;wz_uEeYV!h|V+TNdzG>6KXrlMu z5(o)nou8Z{m=fdjj+az{)DqPo5aDTw;yB{WGtMZA0iuxGZWPNw>7Z|jjM3tdsjm)85FjW;*}MN?u)gvW zn1Ui2@WT6GY7{xH68FFC>i!^B>A~(M`MBc_2X0< zIStn<`FW7hitET^g0d7{?CF#}DA(0#f?~4-;)?NX5Ru2y&(y@FCN=lgw_ya5GyC`d z!+;!PFprDgQ+}R1yMo=J-_t6Tj`}bf_^XJ!=vUzGHR@V-Y=Ix?juf58?yR~c69OsC~{17+O-1+(891n zZQE3S3{M2uY@L~hXJf~}07kZ_cP+)&voyh{>NYZ6 z8Gf#r!VSok_*28Ma0<9PqsJ+}q&p*aEx>e(Wj50% zxNCwab-u*H9nRd`Tv7@UiRX;bv+Glha*FiMujUiklb)IPJZ(yH*;umzcJCzUOPN|f zsyfJ#lYJA3qHe%P4tcm0V9~%W+Tv-vN!f~x1}A;h0>=dumPy)5u+4r>=_o9&0cq46 z)=+Zcn2E!@&KmWkG<8L4L&O%2t3Ld4#L5V#>XRuwSC)a=deI=-*uc)T9Gdh>(M?WY zsq2vC@vAKdkJBR-1jdn|D>pdvfE0>&1tuCl4JtNoJN*PKHZc`g>_USk3txEO?66NX zkmQB@uvaj_193g$j8f4EL(Gc069m~{aNYqY`uPDLE07UhgjCq2`NrQp#KJ2Ew#5lU z>%U)FcOBE9u(v`%LWkm^{-2)^MvAc`>BipY2P`}BCbL|`!YOa zDD-9#mjKKbW}xCwaf+dg>RIr0eI?P}kTwZ)~3u0kG-QpO8uX_{{AyBI> zwwd`G&6yGWCzx?LwS_wKjFA<6#tTL-<35du zo5rL@h^>c4KzVh^w#y*sBO8Yp%u%``^22>|eD_ zk}=wvb{HACg)}0&%I(Ggyt`F=MG`}*D023F;Sbb91*P^`R z)L?xQBb6!vq*HFEwqZXNrDvAbt2 z2S@BGn$aVij{^BSsJr6pI;iJibdnRC-36w6Fw2$gq0IB$nJN0?P<8eDK!|>? zu3o$^-vYmBn3xzCEv&6$2lKfF`|MMlI)`^!u{r*2g(Yfl*V%BH-lZ-a8MNgyIu>_X zaw>|y9*#){^GIgm8Tm*LQ_pWWtSSFgYNnVbuLAR>c_SJ9J2|j}MZhC{$>3fBX^exa z=#8f%S*KQqtt8Ubzl=@Z~GAQThW}^aJlSA zF?%@|)EmvjH?B^_@-!(AB|j`{9H?I2)RrNb#&>iQdhzy92nu?xJq8nz_j3{Y(@(%u zpVwhZ5>TCv6(sviY^6nQwejFSDVTM*H=|2u_I7T92dA#U=qDYxaQ9Ue#PNgbRDt;E zD1s*mSNSQ$lat0su`+Dhdy5;zSrY$^V??AH3nP#!RFbtVQdP2GHZhmGu3DFbB4zQq z@a5-{hPZyB4^x9WB;SIB73d0(*}`j6hi!xEy~o$Cjc zQjR#x95@8Pw$0Jp!cAscl?$<@GyViD++#)7RcqR9p~29Tn#Zji+`P_Q^&I_`UigwSW6bW|n?%;VMrGDs)-P(;a3K&YHSllnbw4HNg{X9Iz@ zrM{A4<6~hxZnD_i(=@t1W* z+htv0pO(Ds#k48nqh7W0TWaY2r2Lk4-I0SXf;M@`##9EKsh`&=q>eHDzrNH-3 z^BkWTmnBIFNM^6~1zOvcHdDVmx5J_>zwk}tekSmMOc zl%|g|&=s2p71on7-nAvk@;Z=A+mhw9Ke=AtJ=rd2B!s5NRJ1A8Gx@H1r8A*{m7LX? zT9eNFihC9jQqDi=!<%WR1xud--)AA0urAVk;*tAKLHbGM)HAN-%=30&x8x(0Ta;I> zN+Q3q=}~bMASN3)Ox@iUgR0eJVBR-=wYc{@dbCST;$9_rd=O`t|y7u+~3iJ8Jr;~zD-RenY zrQ8Gz$xR?~xP51d_XiToeqjbhXOJa*?e;Ide%V3CHm05t>*|hSztGDw*OjATkvgrM zJx9u1luq4i9+$j_=H|)r{33LT0QG|b9=-R@^uE>G=&C&o-|y;`j2qwB>yOif9#nZ( zAsfeVr+P>YYXORSmP&hHSfhaTZ%nKh0y9WxshzT&>(?yZhpn)Z&E;^!&249N3LU2r zKdF5_=-0ZV=u26&e6qr6gL3#`STOJ@;=OgAX}cU1it563FO#FkDO8MLbuj1;^K%=# zF+&Bvs`vac*Kc|>`AJIujJ`yI86XH8$ZjRboGbj2{b2OXB7z1#7FKvMwuAmop)ao5 z3C-rqAwc%w^yhZu(|-2BPRUreRUS&^O__34zfOC zTE)0=vRw=S{TfW35AgWK0vf$-=4z0eB%#azD#HR8#3hm&PBTmN$?(W%d&nM}tlWHg z-0>m}`6TaR0i?c0r{e!xLAVn#A$b(e%&)&|rp;#El=9Eq^=#y}54uQ8A^J}^NZ&20 z9b8`|5T)!*-BkDP0{F6m8}ycKK@mvqG2e`i(QkZmJY6M`+K9P8GoAtADWIraW~374cC0K z&uME3s%?CDQW^iaz=k?=`pL8y4B6xM4U+@QWs;p(|3e=`XYA3c-|GSQ3d1*#C)B;@ zB-; zGzsS!y4&e=i|cUgRYSOMYGk%W9;A1-JI~Qmp9FzwfpBWU;885;w|txH(()|HfUCFd zPenJexqitkW@q$HKBZpkp5|tVJ$12tCE;M@4KyV1U`0o4Ku$PQvKX`b2RD0 zuTjVV#xh9M4nerIl@`LKvDD@LAoX@GktpB|)h?_mB|(Lh1#=(gz#2%e!}u2z9sTp6 z8vh5#OaDRZCd=e*4miw&x55dWEC3(_zh#2Kuu8%BFZC^q^Dub`>;O zJ08;4cL)kE?|cHrK9AL^C(lSX6TopFWhgHrN^B^p4N>wSmd&4|49Zh15K#2yVQpC&P12upW=ol`~=MdPr3Wnb`vvi z^26EGdsTm5YG3)VhN(SI22TsG=r3~LDzy6!bcns~9?zz@!`o_|zFEg*B1vr{`{7T5 z2JR;Tbk_?6Y5le62wdX$<`uE&-L#U{*b~FC2m4OH#xCVY^h)Gcc~>es-mgwglG-&6 zC3?=RzNxW`7zm(pzkHYDOj8IN`%~`p)b=~s@WdS{kr!G|{`r7j;-|q~dbHH_)qw$% z07Zs})NH*PghXJb<=1z0G^|x%HVTI@sk+#52&_yjGd(zM3P5(*YYM&;tXbevt4I?E1v>-$s z?dC)SxbI&0Ghg$=f~AgE0RdL%crdI;_EqI6rPe;{ZpVs3vy=g|+7^Mg<@QRmM5|M_ zjaE@^U)a5>(7iHaI_>Ism5G8P*)!cVyG#6h{6KQq&L_GAx%+>t!)}8c{C4J+m@MLy zlb0ic$e7HpinzEF7r{7so1%NmZmDaYy3>MhH@y00^V$4X!5YGh<{%sUj&L?H`Im^t z*E_eugT=U8&Zyo&0V98BbDabOKcScXiGA`?s1hZ89(4&MOc#5V4ry*IqF z>~Q{grAl79R%*TUQKX{FJ!5Bi^hdRe|@iH8NvpPWkxE~vzU+G(I&|G7U{_fiMQ+p7*a-jG3bnL3$0-*dda zp6sWr1+0XHKntyM7B9-K`DrUECWd->&eq8!}ZqO#&PDNGgUVG3cF-Ww`$B7~l z8E4|P=#{p=N;VMnCsC%|jEhUZCISTXBd8_v4>c3(fSjqt9Pk(EZg8A{zwm;Y{r~6( z!#2a5pC9lAK|hqA@dikFd4h)0A{%p^s9U=<5_su6!@ms@_#RFXaw742qX^-0g$FkI zi2X4_f~HK|!)6z=`^%>(MKVs9F5ky*8W6~R4Le{?rwYt-N)9-nb+IOIadNj5>#nnG#-2b{odD=4h0&>@-{j#lBBXN%8 z)~*ki9;&(E|5$n@9yRWc*q(Ue(@5Qq7dXE;A~~v!V&Z#+ej20JB`-vM)HlgWA{}lO zw=)emaSgs>sE&R3&hmq-LVMbkOSI6K%B9*8xcg@lj#KHeK`4&L`Nz}4#VBotSojS0 z;{Yl?$t@L;v(Jm%(1_KaRo*)yKAY^dhvKPBa~BT)eQ>(|B&^UudshAvc4oMaP7dm(FcTX*VAyTy z?-bTY(ZNdupDeV+J=M?3D&FLQ&g*mbU7x|eHUYe{kgiIEbC(68Og=gLyWU*-p<&ymqA~KWT2p9!Y)lT z_Lkq4=$rqddKVq&T{7ux+Q$T)^`Rb5e}QUSawEx|vM>6UHa-Ax=69`hk;$m(22`7M z=o>1X`x{VaphYZXD7uo9ClhVu)AuLk*bIvlxr2eCGyz$N=(xH!?3rp6`@} z^tCS8fC>d{7ODVu=$Pwdv{;BNj-o+>3{a-B__DrjuvyR<#NXi|?9NY1)iRihu;qR# zXrp;mdQ468MV5~nyctlHimCf!rg1(ixmXMcu%Hk6AtF$}F@>g+f;fUV8YNnW@#KZ& zn0bQlMkvSwl3rt%jQhGZ4(`MnVmE0U4;W;@iUVZK00tz9jvogNrx!?WOxKwLKpz56 zo4>Ob8j6`_xXsY>3dTwZrgy`0iDkMSvi8BKhVF*(l5Qc=rB?1Xa3n4-rOqH}P2H7A zMa|9>kY-nQhc)90NI@+d7e2|kwm2|vXy%>0Z;;phwAzUHEOfTuKhE9(br>J?Lk(^l zjM}`h60j2_C3}!@OE=BBHE_p`6>3{6)*J8X0_{(cCr6&u6t%Imfok>@L6w5nSkZZ3 z;-5*Cm9S=_;{?YpN0MK@hZ-zYS(|0F`c&s;HeXxY4)e2yDqEigpLVzG%4iWiJT^f% zyJ|LdwFTMve6xr>H1})4Y76r?wohhBOd1?6NLXhdx6`KkCxcAUrv*42Cbdi6lPJpb+gx_RjdL^GuH6hcr9kkBrYeP4#EIMU9gZPtv|(I*Rl`16et_vY%zd zotOCYySd$~zhxVqWzz~i@Z;+G0sxm z_9L&nr6HUKzc@5*>AxG=F4o8qvegu8cs~|NX7&A-!b-wI* zzBd-=vxNj|F7HjBWEYJ z$9z!Cr-FXzbF4hHLmC@pCLvpWNYqOX@Iwk4k(HhDf|-bGSx9#Lh*6cmFUzx?UDL)> zE51}F6wTV6UB=T8WTQ@?d6!yWy$Lm|Pc)nu=EwRIXH)&^d=1_@?t`v)y?2l4gKnw( z!x`=akRbE*R+{KK=8?k6S=6&{Ma}P?P*pk@OgAAEN@V6$@UExZaRn0R>d&V}>ygfa+>iU7N$er~uJ_-Z z5GjX6>zIF5AdQo@(l0>XOr;B;wT!AiMuSmQY4?{CVbf9LHBEM;6E=cq8=ehW2@7Yn?!Ux3%R@f{@h=Xitnh#BjW=-$_mb^-Sba}G9{ zf=Z~KxkWPWy=Ly+y+ZQk66|I_z&AIQUHnR=^$j&eoHm+#jFFhkt+q|KNUZB$xJ3 z6T+wd_QnWuR=Hrtd&NzYXcx+Kxm3(OJ1#Z`T^_z@vg=hR{BUea2@(52oh`)w&E9hD zk#ic`^2ikI`+=S=)9(5dEAj~`>v_lOKNUFu2`mK}tYOexv);dB7Jxu3*;sfx^Jo$-%Y$5mFM@$k;H}mIm0rwMw$)kj>bax4+ zv2v;>uk;(w+l8$$Y+T-yy&oV>{`PwXn_v>t_3`q8nulLeLxFyjnf``kqQz;}Z;npm zPMfSlZ0`SXQ!ILzQ}2%4MRpG083?k+&)&;peZN+!#ocj}2xjz$3oFRZ>#*)&B97_S zO+N!E%V^5WE*K`1v2p-|6?b~pbO&rRWrU2qxajd~=5y&s{{(cRx9>R!j{xY!!cK>k z?|cp+7Xe*qz(fOe5*PZv@zDw^hcw_MvCap7Qbf+v`EuU}s%h>9N2>q41q@_{ zW^j}41$0^LW+n3!gQEo8GDwB>Kq_p}=N5Gudgli3;#&WehjbBnB#CjzmV67Gs8vQx z5EN@RF`(9X32*o_W*L|_JA7=L$Oc(d8DSu)B=Ry(`fK)$3vLEZ;Op;qV(jO+bp_0u y4(R`nAUE{)U(Fx>>$myM_-ERQ^ifu+$$5~)zv1@TE-y%e{C5QXwc@j*U;TIajE}7V literal 115272 zcmce;cT`hp*ESwU#YRyP5fKQW*;v0Zys~c}>&HG{W1Z^&IOg0O>*RpTwL8}UfU=ll zn+^wA=SRG6S^5G1r-gU_?YnJ!agp`Q^L{t)`Ds0Ja`1cT1^V6ep{El-PDWN)MdqrC zoc!-H$|`aSDl+m~Vtjf4z)ryJ8`n$&Y!?W9FZo@bP!;@1l zY#de`EpMB;C9-7u(fL%w`1?=K2ELREo+!Omdi*#31B&b~619|-KWC$5wGQ}5waF%Y zksU3M4J-9mIHLx#NP8&|B9G@Q#`e_keX&EEdEFMS%t;u0V`UtwsddL9o(LtE%s(p5 z&Yxfcp_3ud%vDfR2DdtWo~*ICm3r>I|NGKaJ|A+);koLmcy!3e_clKgY%8yu$+ z19kND^ni&FYSM4~>x(>IsWSh13gbo&iP|Nu5tzj*J(d__TQ0qS?_vOe(H+(zhnOQZ zu>RVxR)hT4os+6wlm7K40DWxZBsCqvmgEbW(n6n1FHcm4{%aC0x8Zzo2T(eXjF+CL zBbd)$bk2QJg?Rk>1fa3tk4dUUr@-^oT7e=GB|>9!Bdxx6u_k0?#oL^yu(c^9eM@L3 zRwHNQU$e5HdvQG2vQ;#%-Sfd(50*!}GT3u`Sk&$|FEOBpS{i+B#cfMBCS%0&*E==d zYE!ygPMX50LbnAw<|H=G{`#A}pCboEI*8b}f|C;R_nZB-QUG=rwMpujC3@|{>XHj& zf%DfzjaQ=zNz{~(?HJzf>TYPhmbtR za}KpYidy@qIQmiHXX&tadY$;hmB_@T$R*)ACA>4Y6@>9d^~w4aB;qTV z^ohh|o!^y)Zy&Hx?g>1){;VuKVQ;%wc!rq_@3BEI1uZ7~iV1|&^9QxTa0rwOvW?%4Gk@!QgwoVRgd*1%oW29I5QZ~LU1#XSgNbisYMU2 zy0*ag_F5S;1%egQ_w$&#vz-{4dMHh@>F>_KP<^K^zXxura4v{eOgHYMayp;i1J{Aq zMJs6;_8{W#VV+8}l?w~QwMQPS!aZjh6~!|u_?aczq!~e&a4_s~lIrl|(cc zU;SvRE@d082j6HLZb2_jU1dJV9VT0;d|XrJ=q9{23Z{0@J=p@zq{=)MgS>0tZj zAAqo?%YmAI+KteGvMp9mTa*v@vaRx2HM>OE-@};!g{CetoJanue&YPVl)BcY?yAi4 z6qmrjQMja3yH8w?Gv&Lr*b_Zi&QwWMR8pc?L5hou!IJh7Y|#wv`M67iTi91E*t9!` z`Jzq7J?&gaa8JM&O$#gr87C?yv=k6l-;^QM1WNhW#Y%n4C`_z&vXOMpO`56HAT2E~ zd+5cHRBKP6Z&sT?G}Yu=O<*RJ%>!HI%O1F;s%`=(y`?fC3X!YwR$7l3r}X%$?E2

U+lLuTWEM6TJZY+eq`$G%2A`@4m$ItzrLv)^bbm1DoLaWFRaCJoW@zqjN9z6$YqMEFZ}waoXMeGGIZqr9SfPRJt$Z{leGzLPp0Z>`p0& zsAg{7+&FwmOh6rD_vAVKkQgt!u>-Mtq284A_sN-_f|W`^?q;qlsX+ng*Nzs(Jsq>* zWO=;@%j=g^UkBrBdX@^)&LN&$v)XeyR}$!3`LwvtYUz&u#mE@87A4KGGqdGm>6pN8 zt}+$N*->*r%cu>;MYH#ZwC>^Dgr^4{wodu%$S;hSa{so!QA3F}8Jm-yaUe*P&>;(j zeTC^Z=wW?_{o4n~M6M88?0O8eg*Jywq=#&D zCBT1dFi_K;8(wifv=I8?XM5(VTBIK0hSNT79jjUHK}n!;F4llOB$FbmaLSn)oEk?o zsua^R5RMNGFBiW^c1PM;)Y#k=nbJXvPfyJ=my`i-2_wyfk!0VJ=Tmxf&M^a7qmu$- zTAd)b&UTlJrSGTXiG@8x^|^I6rzeERaHOhy-Obu7^L_tJPFP5@y>G8Ak|6A_=vY4- zXGc3HEZ1DJe!)T!ww?fjtg+V>;X_>BJ_QdX2V9!+Fua`iK(6?H**opg1~C*(h7GXs z>05+Tw39!&cOK2pxXKZETF}GQo)#ss@GfO{uPg>_zY(oXYM5WzdDnhP%#(ZIL0_G% zMez}5DMGX(|4pD^={Uo3KiGNZ=p`7%eb_`=2QyX4a7)Ze&u)UFXBbrh%*JAQE5nz? zD5O;r=noS{Cs@$hyyowR2WkJjg-m|)+o7kA(=(NrFi(p&E<9g91eWOJv-<(f5QMYU zQwAUIfrhyar%mb6UUL4)4>wp_xgZQ*Ca*#YEwDCL;i24`j_*3?ixc-TaM7NvR_zZS z^MvV(iZ*E7TYW_=yCzonHj>pQA_0Sg_3$ddY;sMAT_t% znzOb)U2*av`Gr1}sCV@m>^LUE!Q<*AjI?cVAr3#BH+*1mCgtcfxl;sHdDRNOWz$1e z09VgYar}j_FxYUH!Gsr}agixZvrY9*$TI``?mJbS{Y{~ zobwK^; zl6WH30iMfLuPfl#Sy=?p10r0pRkU=0vT_sy+q2JJlaHZ3ILk-nv^G**vCzi((aj4T zIY+_IuM=RgM<>p|drX#j=XDl#iptTfVFJ1W@epvo2 zKt|vxO7aKDmzfYbL6tg%zfg(3zzqO|^G&P!1^H4LCteE2cb_&ZyaL5)kwLSs8(cE~ zvi15>pq*~?R@pEdF@yC_Gi>K!l2mPtihQYi=%S&>$xu3y&hNBr4FG7%#Cs#} zTrTtwK6z^xGzW5FxJHz^USYF=vU8Pru_D@_8Ro{xmA|Y?a{ScbaaxUgxb}jqx}U`p zPK6%f49*AzjLjG+(`Y{+@8S9+oK_kQc79!^AW&WmtvInd1ANd&CR8T9Y1KUr#*xJn zkBL7x`GWYxDg^j-FOiXX(xMZ8%P**a;_1>!KR+{e5Ma+_TpFi{ocsnR_nR$+)_la?CnG# zpwTZ%Y}|kP>!U|S;m;yL^ps`i>Z22~C2O=$*%AfR2iAsz^j(sApMz0s&y$*)e_A`# zW~Iaf2wU$-cc9KtqT64ar1-y;0Htm=5t6fR!(rq*ZIKW+Ml(je~%aT!nC68wztzF z5~I2hbXOFNDcR52V(TJR`RIbmbCx5i#){(9K?N3e#0K)pzLkkS+ zm;xkf1YvLcPD(h!-eqG_5@(?zV4+6TXbMuxFehd@5Oq)asjCds@r}J3KdN>Im>UGO zR+sQWQ2mGq3FB0n_XD{Js}9wwz*i9Jt;uU$cTAVxrDTi1#>OU*!qu2rNjzr!`yl!p z(ZYf;=g0)|OZ_bXV5DS!JKb5a*n{zEcwsLqYxH|1HXikmV)Jf@3 zPykhy4*<50X<9`)A&>|2(Ck*A5BEFG=T)mNycR1s-cuJD|EVAd-G$yWR2FzwY8&=d zN-inrcP+4$F8;gY((Bnb_n;g4cP-&creY$nh^Vp$eZ|FaXwBPK%579G;gft$N1bRk z912xK+z0Ze_1k@H)sveo1*%fR6H_bR3WU5R_+-tf9Gk9%AU>!=b%f2XFvrL9G33Q0 z+O;;-dRwsy~ zqp-320B3qG(XzOce0d!$(8DQ*zKxu1+M7%Opp@$bl`4z7Ef(L|QD{D03n#b-;>|?! zO=}-OE}B*+Rs1WBV{kVU`0f_R#~J4H#I#p__|R`vNAauADII{zCC26A|8-aC6I9Ie z8+q@cuWk4I4*+Nkxmk^BzwaVdX!Ge(?5#@LR{6FP~BiIlM~a!p=} zsg>3y#vrlHL-7cu-4zZy@dM*(Z>g=V&JBP))u;rN z3l0EC;>xazJjoiAjPt*DWC5y1z+xkaf_cs@x{FGEU07p}aN4t7dyJW{gq7v(4xioB z@%kS>6ZwT>*8pQKx0U5a3&=T}ltDp}TobQw@+BTLFZc*nsLBOTE=)_CXDD()5&Hlw z=YL*kf5@Raxp+(fW`BCDx6oXIwg1o_9_IvX9Ql!g>mX6iBoQ#aCs7hWlJ+ z&>Zk011Y&sP-n5@8!eIc3UD_op%VcW_PN%TlJCqQ(z{bH02V4?OzNnDy;qt>@EDu} z)XVIfo_0c2RiA4&`ac<%;ykHirlCeiEx;ZL9+npJ?~ zW9r68hAn)>GarCRJKFUF8p1D3>Sn?KVj}D5Ni236Bpd3UNeP#236gZo-|%P430{A!`oA% z7Nb~KzF;dU6vKYRGaSfm&vo+LegHV?CrerC!YQa$aKCI80b#3)Jqo^q#c4Zk$PqJg zWYd*h?hm~I;cxo@V?T>v=Jc0eb?a5OD>D3P^X5HuetF9JDK&5p{>JTVKW%Qr#QEwBCJYh+Td4Xt{Nx_JFJexfJWnl2;qdlcw62xZWCpL_b50p| zbT1bQNSTb=3ad(6Lb|UNKWgg}J_C?B{*&fu%pkJ|kkm)$!r0CmZ$r3%cvU5KZ$}r5 zm;;Qyt`0@NaUnf?F!jKQ{zQ{nu}sF?iwI%^EWG;prZU{`_R+=r1 zTXmVe>rB6AJFO43u?EhUPC&zdpXiauD0ej64-k3r>sopJM)&Ik%VXe{^9g#$Vy#tU z&mL4N7MIg)S*b#5`)Xm?*;RMtQxGb<(%}s&BzA_-9>n+S%4*01^v_LFIc-F9-A>^9ADGvNLb|B5ow?cVMjtven+e34Pi;TqwT^n&aSdu7^8@If?2NZT@ih^s9C6VnZm&>PgHlIJL#)H zKs~_-FY~Z(4Kn3;G<6>UDEXbw+l{jWnOOUaBI8jcw9w?1@KJxW4+GEHAT6uoQ5!8z zaYL6lm#Je~3NAZe#Ihe{pW%bH$V`IN4&UKEIFx@ng|l!CeW&0q!gYS49<8Ct_=tuv z{l0uWg5q)btuK~F&LVg~Y-qu2g~X5!5a<24^|tEHZgJCuOPzlji6e< z+kt#Td`T0>|1x@vZYm+yyl6f$nL^3f*R1#<%8DNb-uIlj?<)@eb(s#WS>wIQjy>)K z%^TaeQrrYT@)j4~t6@}KuhisrxtKg@7g{2(wzAF8S0Yws%`Y)c*O#q>H%gE}mi@5y z9!w;3hrn-JcrD6!;_BGvA)k7lg&&mHf|5v_K>Kg3C@wps3~?;Wc`>iI8YRKP3^Ju%e5)%gcFsST0 zVY`-QVOPBH!k7i5iGhb4W+v$|UH)9W#md(CEN4zO#Ninvyn?Q-4hHq%q`uSy5_5hG zZyN&d>{1-Zk{|RsS>zf{3|D!a`C;w=z}uJVhao^uwGYJv zQ(xWIDcwZ3?M?Wd$`swTiZ6xHHRix}iA?kQ@;aoM1Pig*xgP^;9q*U0Tb_SeG^DEw zBXRsTIw}i=c}XNLQyuG1)kx1|1Tagj!^REiRZl#aY0w9#6IyAIPc)#(!YjH~aVPyXYq0KiP^fffR$ z#%bz>4s04Mg6atb%GbtGPW;g}eCh@?W{~LM8&>*Z)bVOob1U_=gRc~BOp@buut6IL zkN4M@O7ajHd7SroppP?Uk zZ|14obX%2ejRr*PTEqz7QJusJrkkm;3+1l{=YTKq@w;^kgGzCv{1R+^gexJ}aej1P zC7&R!f+TR=1&%%ID|HYxVvjG~;r&g2=*ldsM==XC`kM8iMhRu;En zx9eAw+Qz2O0FVEA;U3lt&yIhf&y?2X`e8T=$FumHIp6U(i@dZY^mJId7sW4k<`2AR zzjG$-?4#}R78MnX=aY4nNJ2mZWJEKh;6i(NWxFGvZy;-1`c>o%OTSY{0_l(HT3*xX zdDCAg<@-?)c3)d-lBnly>Vlx&Z^c{e@|5Rejo$GEq{t`>yL|S6wFKIeTCd<$pA(x< z--sOacEE^NM)1njd&(glTcsR*?p0}XM7Dp z#JbXMX(#7H?x_aR*5_2(rBCDa);lH+-kxm{o%m*_8ocr`+9zl=(snNWmGMON4=!WZ z%1g`VFg5&L^H3R$a#LN2pmTHS;((GMba7LIyFxNfD5@ld8i%&#q zZBYr<*SvaJRgSsXGi}Wk<&1lH)$-c0!zXdz7R(9NHU@RrL6xzR-Dk|CMywJ2C&yCL zl5C2i_!#|MF>De_JC^wTJT#)S$DUF$SZMEbh!xwZihE86Sa?u72^UQ$DewqKS!oC+ zTPWl$_~Xlwal^Xe`9=Iu+cq^rX~q&hxq9Gg6Kr;n)lyt?<`9fPJ=SF_dCU%f$ma}Ot06BaaE{Suk&5&{-IlcQp^RWjeSPlpst#ENZBe#VzUei zOa|p}pp?Eb;jt_t_K7=fTjeyd5V9cC#zo&oDfwx#TVxV&Q7e20Yv!%pux6Qo2!Z}A zE$0q$J%P-hTmO3O=PSt6{#T~R(cvHWf@HQr!blb=D}eYd4CD{&!iY=!&VC||cM_in z7OIcdrjq6*UyBv@r@Y0v+e)0Gh!_`{((rw3(Iw$lPLU(9=5b# zIzDk%9-mU1a`U?6p+)l9GK;sqT|BskesN~K?m`ePg$>$PdWn^B=5vc0jFkN4qU5*z zb>l<05acSHS-)vY85mCSX66u?TmgEyUdP<9A(16m`A%72<(Tu#xKll0bm?=`{&ZG4 z1p42lLsiAt*vCM*OrhTzFDHF<=`doEre_`OUH#(xfo3$@fgx+*p+DyA{&?M2=Ii1w zoBC?iQ~`Ytm8cGxd-tJ(ayy4??(ex(r2Be5G@iz5Rcl~CUX%ID;F3x8S0v>j7BE!d z541q9H_NB4*cPpFLZW;sac3mQ32W7T7}ZTCW@OIh-evU`V9V;8t=UZac8_@-bn~0_ zdZgVcKR0WQTCHk#cC$={PFehMDn`7SK)CQd5e{3^u6}8WW3FfclHS}7O*p5p?alkL zFlFGyZ5L)8ash+>tL)ylX?ECiZwT{8A=keC1oi&#(%ShjOPL;T$h_eaS+lgR^OIs!Qif3xuaS+^^7xo_xIc@94zKA@3^%GAu!io}rHa$WEAR;GE9yk_f}XWK5YpW2Xo8WnzoHoaDu`z^^rFG_pr z8PJQB@ej_=7ZH97L;KQ*>L|8_s9jWC`M*QO;FZ7+N0bM)gFMtP9xbw{+kyKTzaY62 zoq;^6o~cL9^wjIuWZmzd7`pPwy{|UUvzJWlGGcex5{&33MT#1vm0Bmv1wxmD)hkl* z=-htf!Sv-}{kP!dtw?C`YROUO_5%xyb9p4t%#073Ih_yP5JvgFY2IL(-=3iMeqJ0~ zPyUKdSK7*Wwpcku3obO&`KE0+WvT7OC8(A(%d-95NqfaN2|D(JN$EyDe1;AUy{^vg$py)rYg>z5UTK@66|TNS`ipmW=z9_ELUTg5<5#&dPf{6Iu2-wN z{a!PnzZ`gBoM1h`4&h7S+p~hp5iSkGh|TC6ufo-b7e^Fqp||siQnM!f3%@n0{UV6E zuME5#>|RPKX2a)^is#JS;`;m6+yM zc`>E4s)ZVRId{Sxs&ZCIzAy0pN-d2EP$ZL_^8O44U+^N)6cEoc=0bcMfEbehRERrf zaGO+`!P;7**B*!0Jf@KEq?1$;n~duj>G+`sSb{idXpJn&XkC&;>u#;1^c(SODK#!l z!9t5rRYa3@EukTcEU>7-X+<-@UJnp@hnyYV;#Z*s8(KN_oWuxt9&=#1%AN-1PgVgx zhNY7Tbk9oBoufy4R~Ff*l>`-<=_(*>@IEl7Mt7OXQgnoCs(Wr-KTexH|AN2vntvPW zz+iZl|2^rgyE;9%NE$g?HrK8JihKGue>ETN9g|8wnM5sf9=;-N9yT zKb_%(_{EhmOkuR%;WzWImxW()3>`ZXO6WdH85CKY1OQn2kOF^VZOoQ zV-92t*;omezqwRk<27U_&3h`%-@C<@`8&9t%>o0}IBbfCXHkJ#+RwAMXkJX%nL>FJ zbF`(#qiwpm^i=QfOjtnvYINowDd(1Qnr)-=kWMa~;G)Hn`U4qv7ivAnj>g^PYDZBMzt!`D`^2s9K(! zQUTm?lgpN-!YGpi#G4hE`~AgYU(*}zF&I!cq#sg}An>;2g52XIq3CsGtKP1@)he~X z0-SgIR?_FPw@ADQjRlA9(7LRJgRQO(oz|`QoA=ym=2`?5^(Sxp&ns&#>e;Fv3|TW@7NKt?96YQdUf8Um|Y6w-`486bk} z&UjDoLVh{XXtXA1g@i@UOsGGEU3UD^?R#!Z<=Bs4y}MVQ2}3P?+DW;p!cFjlIHloA z2ZEJ@uAKR@*4GAFpNj5WeG+W##oN7DWmm=AOgqsYqhD0);f1XaTnJ6yuCZ>BPbI>= zwzLu_lQzChzW#j6G2i`g*ZQ@^G$?qgj&rmo(n}*~#AzF--*WjNitQ(P4$vx~j2!f5 zM6H~??|v;5Z|koSyuc(6l~t-!pO+-A#y0TVea4j|i-1S>gP#j9duzih$in!8VCe`t zddFylyqX2tUSTNvY^=@P_Ws%@R9`WraHA{wzRkqMYjso>#8Y~5$}2hWb;DpH@k{1T zm(1_&Ar=2!E*Vj^fZLS0HxjFZ);<9F$M;G~KFZPA9*XYByqvm=x&Ku}@>I$cweXeD zO?)!>{AF<4VQ_lfmo+Qv8qmh|mip5b7n1f@1>g8u^hn}i{KKD#{t5Y*bi(}AFSK7MyHaxRPi=VEgqAIkQ@tSdcJ*5II6%uL?~1l~-TOxkC+VTc_}1reu7%URa-TwNs0o4Fhar&2A585G{&FOW z*mUhyvRE;d;(v(Ev=E{BB&>!ID013*JctlGCb>F4Kl+GqZN}d zrP)tWkEV0J$PJ}Tb>Yf&*?7+*p?ApqE@n$`_iES(3gae4wIjQF5-gG4N{1yUjOTc1 zvx9mLEc7lJY>~{oWBBRTnKMUv>o2FhI1m`z3^Y7*5~7MN-)I=t+1b3I6Jm5Y8ozXo z$f5)5phU*}5r!se=QVLlghe`OIAIm;1zZh4H7^dN)#lg*kd7EoD51>Et@B~1h!+A8 zMst<>j{ij&2%XsRo&D>%(&^ zzDP(|EhVNEsiLbSZUZkLSZN<#eqB2y(s-p~CZH9Qh6f8QRUb(VS9~Rsw?MysS4$G% zr&g!w(>fqL^yHy_WL+ehyc!Oq#W&NNn#all15A=*Mg3=c&!dt8_3oT?UM{^QjTI6+ z5`jb~_jNdr)`3&rFio>^kll}g7+<-SdXNUYS57`j`HvN>M$kmDs8#zN^`QHujL|OzZhz84_O4g9-KBT)A}N=&fD0elNsiZTBb$#IZm7T?2*+2STUM;b{iuYV=;tJ{Y4@Le!h;Ky)w2;b&N`T#J)y|!S?%X*JO?JRY z;?K|5oKp+vm3FTO!_dAznC02IMWawEUUAZ<`lhNKt2)iiLhMfP*rVHTBrGan3?b^i zFrWF;80|#N+R<(>@=@PEkIH9>Npx>UX=hgk;9D;O!H<2entd~$2CX|u}aI>F8%yo!*_Sh zg;Q5yC2cGt(GQ&9?33+RpACZVh6F-}5KEh2W_Be{oi`Aw>LL()d{QR}P zbQ*oV-7b)ycXjP$%&OJmQO4tv63JcnU#IPCHQ?1L+bf~lSa1nab90glTte7c)7;Tv zIYQCSCa^welq&EujBzE|GpWA9A&k6ep~b39%|X$I)euF|kUXU=0Yb7pGqG~N$`m?( zP4oU1RMD@r-A81zXRlJ9A~>?^Rx+G34GoYF-}#GL2rUD+KMd`^zWNZQSZzeq5&W(h>7htl<@sErn?1AexE5PerjRB*WWf%05IZ?rRURf!ZlrEyd5KPIkb#Uih|0@ zisqcEfN8r!laFmW_7MNjUTP`xPAmc?S@ys@{*lN?!L)w~X|u4r%O5U2tK$a!i$RlI zu%l^Ea#fGGkLoRFW+(@PW!MnGgDFDv+eXrO!bs*j-|1*~%K&U<*VocRoPOlYjr8p+ z9=Bkh52;-3^!dJ-u^5G{h1=C+J=-`+SYbcK^*X6&{%f3WZUswbQk7$@tUTmucgMn@ z6IP;Z<8^zk%JI=rH^Ds2wdz`!#B6!-uF^Vi)(#JMh8gQl7ZHgDEyxM>;DyAcdHz+k z>~jNOq0_#$$*wS@5Pf}kOLPY zstq$WJoh9mVSN-}b#W3V%{>pL_>^7^@mH`jE>V{D(kn4H)(~K7wPn3RWAdTn-C(Ro zs-3qCGG4DWUXk(vRySN=uFK+Y6M&VWf2~~M2-H`|HyvlyVLC^E8VGc59i;m}1Tk6x zMkDqU_DL{X$N?&HB;UsImI~bVt{AR68#K89;$8LrJ+I|ZZWM|VT0NyvK<9YLC8RNK z^Uve~vhgk67B5N!%H3lsPHe?peQlIwV~!@?%|0RJA1{CHjEw})?C+M=xRLA9z z{X=ahU03egqF&;TZ8NJA8iwc7)kS)UC{?x-dma1QqolaEmHaF#x0)2DH*tbxF zXxN4>bTzAb|B3b;NxyEzz33d@QDN4wJ#`?ea{5t|iThcY(&9H0KpsDX?_hz?~M zk7fUvE&Fo}#qr?v5gL6o-r8)aLo88O%qf9YzzonE12>-Q7$Kj_Mbf17zho?E`v%Ov z>93N+v+6MOUoT3tsfTYch@KiQ?SsCjD9(q~r)gqgK(gZdWl&-(bMrekixOY^3av5eS40fKW3cWH^LBO%*^DOTUbzr z|7M~8Zx6GRcNFDFp1knb4=ZoAyUgSDa?R$G*V#55!di&$R5N^^s%$LJmuF3XZa+ND^=6;i!~;uN`cpcKTM=PJxYJEsmDK? z?8m~xM^ktzxXULy<0aO2QMWlSztDXA1?Aw}W9yr(I?x_!+ZX)USU;~_P*&#VS5Ywb z+vYm1fdN}gA<>qnyTG*1Y{5c6TiYsk*vFF28kB=lPWbc8%4ZcU81Fe zmB^IvEZ<~^W!;!blS)_5ZrQG@k`1jNg-^On_(tDzv3squwf3+a7$Hjqee3nFU>FLI z`wQfLamEnR>v5De0czEl!*2vV=s6+banFN{r>OF0-n{6ilhi_!{DllKEBmN$M+Y+r z5LQgw+F&wus#OpEAZTSKSQIl^zZ$lyM>q(8O*MrdGC2leg6_@CEAZdbO)giw{Sp&p zg(xuY7Eivi&-wWM=m#hL#q;Dzn@wS&nmXhgW11q|>4uQQW$&dFNJKP+=H~mY0K$Bw zLGj>}P46|*yn0ARx|XBZ9wOUsfQ%`=BX!`~|qwQaL z;-WVtWfG{VHCz*ZkMU+xt?$5#C6+c7$~6P^4c23~ucHXXw5ZHa1aKEIP#P9PX#+cg z6k~TPp`W1~;^=bf!4eujM?_Df=WnMCB%?Wd_OX9QHakl~JA26H2Qp@@j&tAlf^BlX zbYlMt&8KkD*G9@I3yQEO34RI|^k`R~V&z=(=E>)-x+0HGrLFFH(pM$uV%4(JSA5WtG_H& zEa(1yL+?SiO=bE@rig#p@zti1`2H6<(J}dYMZ!nL{f;gE(X^|QN*h=$5Gcc3yZF*A zPQD7~eJ8g964kffGmcPzp)bVHRht+w;Sid{qCQ)4rGFybS%=1@(#INX%^^CATy&&ebNO3yUoea|wsB8d zVxaHVK-&|DG-O5nH<^v*u)Q@63*7A@tnxSdceP-;lP7qw=@9ss>*zn8-msS1PzGxw zO>9#pR_BWm>)46uu0#s)|G$>-AE>XH)Ui^MVr^8rOZfGft~XRi8#{})uo{bG*>eL} zg^Bd5KFuJ?XO`wcbFE&m-mD+_%mnNrjrF^*11C1D^@19{mk;J^gc?H_zba;N%-%u< zi+8pkXE{tA{s-pQk34ePdZ5nV(m2`%k+v(={HM*&VOt&Lv#WXPU*pFv2u>w~(lq&w zcx#~<`x{qxn*?X9kltYAk8CC>*x^Y;O52szC`-)MY2?Jbb*R{^ zDPn8;+IPJ_AgpFsGL7(0;V4gn)l|_p`@!^s+=YYfA9zAhauGh^?UuYV6M9~u^N|&q z^;>TU7t}Xq%68}_p#jz^bw1?je3GZj)}TqGIS}V6k53%t=$c;UmNJ(v2qX0zEgu*yT?D4qcdLX`FJ@8NHT3 zR?fE*Sb1kiwbZ3H^a5hV0pI;d1HsR1>3jlSeZMmr^?77PsqPJc!OubfjW$-cwi(}k zcrhb31ys@=k4dbnyn82i6)Nw|VH*E3KVG_0w{RxjT@@rF;t{12!xBr!kiK3OYaIwQ zDH$pefTx+dz|`}i{puv^h@)XRs*0|^Eh^usk8=!YyH)Qve+wF8}CQN2M?s&L8 zc{9-ou`6=(j6DY0(t} zLanKdu8QADT+rra^}l5D<5sH_(1tzZb*&E;t&qW44tDO97bYnUREpBdQY~>ipj6Yu zyyY|~;m#@3`iywHK}YBqJoQgS#ann~$Quu%MEyfEzTO&*-nck$A+T}d%3nX}!Nbia z4Y4dkH(hVVtMG`tWqQp9%>=6l73X*z%ijd%k>GQ$H_Yd(M}gqXRbpbMxEzlccB`eD zRp#uzgLr7mA2OLP^L{27R|UC)Uro@RIPItVgCm>)U#hFeYysLztp9eOnWfIKqNhsm zdIjO^_z%tPe^&Fhc&H+*j>NC&@K#!IiiIS*WDDh%_t>d=#Xnv7Y#}Zi%~j8Ego+aC zcZw96R=jd{gAh|@0~-+>gEy_4r8e8OGn>>KT;2#RI3`+oeVwQLpqNw%iGDt}g2 zaV$4S0PG;D?Me&qmjZl2j9W$VEIzR+%vbv3uwOTPJwt;t;uI#0a#*STLe((1idcl4 zcKMR)haX2y-hVO<!5N^lnb9yiFY{9$QM-CK_0`DKkVp^Sq!i1e z|0*;9BJQrB@~P_bLB2>q?4(?TQA}#HZ7UkSH8=GZxmZ>?673{9mn$SlZ_o%@j@t1* zEf7l6)4V)iS)pwma$9pvRg(}u5y%`xOuL{$nXxlAXaGa#CwvF6^`28)>x&q-_w+ia zAwFPO1twEh#~iSQ{hnr90!C&8M*DMvf|7EoEiu1+Vm29=&ZnvN2ltT1<`Ci#2(QO_ zz|I-@D=S+u-8Rkbbcp8pv4afxpZ9MZKN3FNPnoX*lufv!-l!GZyN%Wjr{5yLi{~q? zo7Bn&j(5$!dH5GDI)Ei$foz|)01X*o@U#(vV~r3?Qp1~r(#=0dOWS722PrjvGKajs zq+yaCzJU?*iyo1=>kadbmtBg2ztt1bJ`FtYG*F05R8vqTY$g{;=gRZ3#T#T`$=l_w zA2+()zi>>Wx0K$Y?AD(@!Jzn@x}T!kcMKk&8n?B7N}!W8dopq!wpy51e37nsYw@rF z2No`Td9cBGXqQ!G>1s?~f`dEmTk;LWS>1j}+iX%u`O&g1nZz7cO4vAI!hoJH$WLHm zMpf*_7-Nz)SZMC6oEJ`-L`t>cLj*9EHDuFEYfEs!`pmu#tw_I5mqq1k*Srn0ffI$- zZ+oo~AYS6W%MW@LLzN3-lbL8mHeWVmT@G^V#?{4P&AOhyyBjrh{xx1!dPu*1+&iL~ z9)h44U$!BPlzycu31a5ZFnIO@rB z5N8Lsup-I7X^q~9TkMR__sulmS#@HuwpXVbeFQ>_bEF%Bq(68e(u^kuSbQkf-A>0b zS}0VH79Wy@R2{&VDt%z?6wUnUpgGU<(i7N@A` z>_XFv=d2RH%x;;hQErlYNGQl28J2Kud+HIgdqt-j#cCnk%~^Ky0#vEGAF7k3ZWf`i zw+tB)tS=^+YkkSh7D**1WWB&Om9dbr>#WEUnQIp?9zZ*(}oc z)U#U&%97nAXP{W$z_3g4sy#)`3bR8D(^Z_9tB}Rd*S_aP#SI%w8aFlipB|QOrQOLB z*=wcgt^~D+L!?AT6uNOB1Y|Fz1$6(>OaOYP$C;a(SG*}6Q{UiX0vc_}-)Y+hs2rZq zd2Lv19$pWnNxj4oby7ft7{qHb{-P~zR%Cz*(vb5)OYB!k>E&s14bWehe zTeY<~yu@4<;QFHzAkz;oMuC!i@t$kmg}AQHi%%@v*XSDRirnIZ_!vUfso1IofPN`I zR^pQ%w{1DONQc##%X8&DV`S%vPvNwGRNcA#_}<9S%vd3Ez0DxW(BpUJS6?1GMb!pl zDot}$*P&sfMQFQp>Svy$mRys8D!a$>Y0GPhHYOqBg?45&&ZX|ZkzTy$xMzJvprZI4 zwC49zbw?bF-F$+&O#`woV9chUJg{gDU8}LRUSaCG?|#3`nhkIFx`i?jJw^KmvjPD3 z&d6WAT>g4+w3I~4j(Q#$0{1l&4((ZGlY+X?Bon%e1NG&l0zweHHEl10Gyk`-hwb+V$R36mwmTf)_*cDov(~Xu{a^8t0bRC{>+4Qd!^k@ zYmMaHFYj=3x+$^!##r2fa#A1vs4z|vXT{Q$r@19wIA=Xv?A5#S>XCjdIUXD6lroKO zb@#%|`F4MLQ+w5o21FLwv!z;Y95&x4PF~LvE!b9p?$L~Id@_^IxCs^ z{5x5=HyG!>R}m<h_x7IF~Jo|BKokEkTnWF&<`TVx1f~5$o1 z77y<7r}{98AwAY8iw68^1ip&3m zFT^Nt#BZ8*xCvdN^~;G}sOrneL&pfRDUFoOl0MiK0jcmYNo`wf7=M6dI6soVFilS)vo{qmO$F5$g8M(nMW=SvTssLGHsx=a7GIJng0R0QM~4|SxdDh}LG5gMo(nvN zydIFYV_B%%gwe7QXr9OUQS2DdA+)luz#o_C&1=9WmrMQExZQDSrL+0VMe^ zuVZ)hpjzwr{^dmd1LF8xGxgKc%LcRw#?5q=(b3pr_U;eIj6h`&zY@A zjkj=Rhwq*=vU~K|Bz&O26COD*Vr)0Hhkzs^Y7mS2&4jUUz)!*Vq$HBBn_>!;2dfkN zmDN!^*SYQABycl|U8I7%>whE?J3sf>u7(awE z5iHal*2)S*0#ET|xR|o8Rc2Y78BVL5AJ&Lg^mIfom-TH%D3mAISQNtu@fKEMZ>U~p z0%VG|4+@bBbh~{qo{<{=CJe!pzZ=6JYvM+22|46jJ|CG7%i&>GeR+edZxsk(r7&LZ zeB6zVD~WdITg;u2Oh_2=&MCN31meI~-81d-85$)`r)~-ykM3|AoU@?1M{s_2G;?vO z_XEmK?;8#pKkyE9l!U|fs!8IAWlT`>2}nJyrlC~kwZrW`#3jA)na*9mhS~Da2#3$e zraWS0rfT{mHHEgTpfHzHOIq}WB z#Sk{JRKgjw*CK8WQf?j4v>9SqfV)vg9G|3HXa54Jp}mK*Isg#QC_9}N;9)%VzfsnibO0NyRh1tDF5Yn1mt^} zY8qsWNt+AWwssM`mSZLltpqPL7RulaiOqn`t=cAeX3ZF9{m=}6a)P}krFsa6A_l(^ zn*8Kl^SVP8bUkK0_a->x*2HeN3>WN9w|s7I*-xjd3`Kg&rTQ@C8+c5V8y}~Jk?WBE z6|Lh_d0NnY?g)kUv(gI$ucsplMBW0MO>3Hr_v*!>EY9*X(NPfP$_3t9eH1b}u}?vQ z^;lq<)mj+$OfKY!JxR+&q2NpOSn2pH({@8TnZYtmRZ$IW1%ogqjz_X$n=Ku@ZkJ)n ziYf1kjCebi7+YeNw?!8gyZiQ2&~w~ zk?EBd>Al_L-9Hm&Rb*_E$nm~R2_sH=36>vXSYD@80a=F!4hggo8OaA%Q zudzuOh$K4;a2SPn-?Dl+;-R~#x}B9!u4*{5;UMi&I5SkUDIu^GpR=j&i9p~S?6W;5 z(P7l;F3BX%of55^;>lMij0=%~$@w=>RCq3pN3>kkQEx_eKW@F%`<{!fmO1{O4oOxf zdVYHM?jzWb8mh->q{JF<9dL{wQm+k+`g_5k?~2zyWWU8{9^!@TDdAzV3BnAu(CS3M z7id6tlM-*H=6PI=^AlNJ=>SFL$gyd>QbjP&FC-BuB8WE4Qwyq$TPllr^3hPeWV85# z?NK8YwhjGCA75O;${ai~wNkS*bNn9y@&MI0k z^#y~#XODM)Ql^Nz{H20NAu%yg*TA4$kD>?y!uC&eQX35pN4WL$^tQeS^R9~c5k<%) z2u%HMA~~4e--H7a7FN9Kw}9Q5Np8e$$l;MZC^PgQy-dEvzkViH8GvehgbAf$ne1Su zP(e@{_A+=7xwIu|Veg2uPu4)5jIxfdU3u;GAT#w1qm8J6jlIZg5}6A9()!y89t@0j z+%~@ClEukJU}a@-2l)(2DUKDVXjl#1GQ2bI1X2TwOXECVR83edL(gUcW9rz>l*lW% z>Al0ei)i0BPT*^KJzf|eQQ-4KN(*>f4DDJ^+D&hj`DtG|RyH*mAdSFiH)8p2pYB+r zwi9o{T6|*)QT8D8=_HGKYZq%{fRa?uMtAY`R~otw8j6Oln$CFmPaG5(8TYj9Q{e^V z-z`A>G}n&x_)#Bm$7^^WJ+stn{P>65<;X;&dzNsSVy-q_fKB0`!hwpXbE%(w(p^N9 zkHYx#e*r}ui_48u&}6|BI3HOlo5Wf567{=>lhH+w{?%HD{?%ZHw|@1rOGXq8b>u8F z&eq6pm-LHY;|HvnARF0Z)S4L>bV{(M8z#hWKH6$gU9bOx==@_Ad#mia%Hp_)w)~*x zpi7e>^?5*#c!A;!IYmNp3-S`u8IYoaiw8HJvEMTT4HtI5(+2dzDdO~zPc2<}9zvp( zMRSGwHPnRTFq&^&1RjJwi?g8r6z~89d!t^4?^`|Yw3V);jp!QADd!n(l5jS#$^b|KGG=_{Mbm!*Wx%M?!t3RPl3~6F0|#*Nv2nIY4W*NP~6Z;ftQvNqI}`KQ-K+p z5m8Z8z-Vxa2RL;8H=)HwL%8*n3fZ{eITnpZm)Iggw>(#hTB^xvoJ!v^sv81$*O{l9 zZCx}2pa|!#;VHdCdtKmlin+_27OB;N1@3Cl+ysdRq8Y_=>ab%b&x9&JxdL^(Bu#`2 zZW*u+|1zhoOz8KjF@w$~R2cS=f-mcekSyA5&P8TMX%P~JU|y4d#SWJiP&|(pC|JKo z_V;?uf0=C81G#s|3pKX0U?=1AnvaNxcmHVI0UG-Mzuh2tGZz_EYlw#R>q~iVjMnvx z5d%|!Bv##Eo9vHDevBi?;~oxIDuS+8pOx<5NF>Fi-bhFDLTEg&xoJj2r>B8miBXr8 z{RGbu-ou}vP&z^;qi%P(NO~7tElBQJKI)EoF(FpW=CeLPGF)gLR9h_ae@h=UBDpf8 zeEqb-Wfg?hufRC+K6pDC>WKP&TgqY^{(K{}Uruk&6pk{>TJ8~4{AtH%etyVBUUG`P zmWc1(#Z^0=4F9@6OdBZR3ibrY)B($!f$Hq6CR8~9C5cdw6{vAQY2-8?~^2D7j$Nh!+k%9)` znK6%k6&3puHe+A^{a|DPW##4OD6h*T;*TfcM=6Yll!%CD^O6u?*uWQK-h1Gtq4T8O zB;elsc`aq0N$J4O8(QVCau;U3!j?ITHM+{#X)=2#vXf)G^wnvv?Yg{O)l`XjN%c}H zm$YADy76tIG*{`|tw~BX(LcpaYI55j`1ejnI1hv@Bz9X>9>4-FuKmhB;S1UUrwkuq zCfZ|d+{IV-+vG>KKA|X#i@aXgAXMaoIxn(b5zyEPU z3j6Lfi)KD#Wp3vD_cf|R8@Pr#2IcLqw_j5fL0`ceSb~}(I5gW&m5e8>pfY>$8#yf7 zBMu4Pgrk@jhyBXBP${G}6y6Gs4E7u&W<^YS~xXfK36OUFKTwo{w zER6xvSBHTNt7pAa9cd|nImv$2*q97AKmM^I_ox>UW@Ac%vGXFW;lsWePjNfqJ%9j ze|!2hlXrN(dmW(LEKf_GB=`_TTWN=bJz^GbKHpPnNbD4j?J{0(SQdd3!f)ucxI%$l z>A}OtxGgYBYRdD_&Cumrw8{p?F3jPMtm|Wlyu8hbbuXxA6H}xLw8fRG5$X9I)1{`y zo*JWtUDB6hht+xKObwo$w2xI1FDws?8%)xYDs;8#paha&0V9-no3awXiEOzBcveV7_J~P>Z?7g_~ln0a5?Q@Zg{OUFA2V9hvoTXgaLd(&Q#S-Cra}|+lF8s;UOb>$B=1vN^ z-eflpSu?8ABa@DvW|Lip(#Gj1d#wgEc&_j4mV4JOJo*|7{P@<|QB5mraUqhKuH~El zO(>!S?O@08k*0ueWVQJb5I8%RF_X5|#2%eVI^8&8kB4DO0DJ&{WNajOH@O-ZGGB!4 zeb|cVOBS&lhW4wgO7WVXdcUSoQjn5&6m?mx;yXg~bc=9T_9&NQ%%=#8bq7M*^=gP4 zr-yo!=v-x4aF}6*LCjs8%*%0=Mp0jSj{A%Dt2nx+nu|9NC**lCXNK0faQBk}$i&yU4f(P6y52UlEw3G zo5fGqv3r~FIV9;Run+;QEjz*StHyUVlX<*ES)ij&8mO^eV;mb4H#v8Eorn<_*FbnJt|DmwU@O8a3_wy#f<)hSYAsFwnHVg&}^KvBc~U zz|{#FSzFt5*KmZuYi`ny17xKR-GhT_PR;ljuMvJt!`o%?iC4@oXV(OEF)vy$tnPa! zrA%S$e)dHw$8F1B`FU$Q_5@lkO%$sveD|cmKX)+F?Zy?rO~lTr4f%E%CCrzBmRPs4{N;vlE%-8Av|0D8L_zz z+au1hv0jfJv+Ak!%*&lhL_|&@7&qd0)-#*X52ZKV3nB2f(a3SvuH#Hv5@=$%V+p3W zqoP9qYwW@Bd;HdI`a$j|gJzJ`-}=AbAFm0*?M-oZcE6`Iky_8+XrX+nU|5#+Pzr73 z*6@c|$iAnYB(&TLR-!$;%zbfiz-{QfD;iN~$QpVyvZB5bn-#u$<&oHsxVqg5RRgcr zH5z{N`}!P{;Vmp-lPML+u8AiAKPp1ZR*qS?Nj7kzUR+}_P#!Oy1pjD!3f0BOUz3i| z3cMW#70h=U6wiT{*iOOvE@8atx@zMQzuv^ms|;%mjFnUPo`!D$zAOv+-6m__{4$xt zN=B}VNt8-6TYma5cO5<~vkh{v) zDnHKz!N6w>rRg2SX)*SKFaFgjP4ge?hnA2)fUHQ<5`qAZVUO-q+P6kWDnR!eI=$|_ zRk~{~Xb8n}-FjXqo7!9k`+r7g~3!NkJUV`4JYiYjHZrJsz+!g2IwTBXV37=zW^(tqak;?jVqFLx&*xjcEn;u8Ud-@PC!EavDkx4HCk2&m_ zw>uCz9|z=DGUh@AgjZ&^AIr2$rR`u+TZ?#f_tCql-45Gbj8LRy*2}gc$xijn1-uX??x6RurUNQ)x7aR2 z6uG)ndlgC8J@#{EKji&*aD4fZ{LDs>rsm5VEGkgwOZ}|o=n2X?aKZ%==h-yMSf^wA zM8$+nRQ|(<&-PiCO6rh5CEffGA?c3OJu=r%&nK=v;SFTdHpm`1`r*N1YO-kh`S0$g zHA7k@1w3g!9XC&LFm-S`!hckT2@=1qow`b|a2~fpAz9FsAB(V7shw?ZOEy~ z1(Q6Zx};^pN^u&;YZKA->Lt3Ra5Uf&12q2nfEZG*J4(Y*-IaYhL97bs7y`76KBh6J zSisXhCcXz&6^lMfna1e88?J(+c+>ez$NY5*%e0|Hu7tTzoXdVu%#+2oFY)*cA&9e- z)J?x-!P4KG4}i5H<#?Vcy;6up8H%04*vh{yyw3%4@4$1Ng2Ge=)EX(=g*(?9eS{C| zf`}GJ7^Bw*_a)S4X9kAGXt+JoAaagSXimyQ#lJ>A@diV$VC}S># zex-)v%Brh#1_w0&t&$?(|NS(V?uojR^VT*!U`%_9%|F>xvooXCWq{2q3|n=;%wd}& zQ&I~=Qsk66u5_DbS=U`Sv;_p;`7A;C`!x0szrO06Iogvy z31?H!6dr zLFugW#SaEOcooCxW@<;zmFmjLO^&QQ2(tZjK#nOx`%~jRpAasScNTT-brvyBRm3iU zZiY=iB$&dp0v~u17$(Is8yvOF;=dB_{ItI%FYgA(Wi*I&pjsk6BL$=m^Ec|(9k~7M zGXX1qlj~#;kN4htL+Tbo1Lz}GA3}@R)<$WvTC&M(-0PByqw8aSWBR9=?ijD1K)SEG zl`8drl7Iv|GxwkI8M0XL9Q~&oK*K1X%fMG6+jAiqM%V_T=;aN8{9?W6NJaKRZ~F&1 zNoljn%%)iQsS<3d0dyh31(u)wql)ZUsOqOsZ66JZS&oU)>dNtgxhl-&%R@B!7UCa4 z7H_q>@=S-Kc9^+gh(A}++Z%!~b?JkZ<_{^N`~k>I3n}gv`&Cedy0!NOi)2lP4~F}2 znVH93ggfz0y8ldla1#)10v_Aox%&+-blG96<$G;80O9vjnzE}u5^q~m&Y~q|6f|I@ z#*QeQ&Tb#nw`5f5#^Loz%q2Hyk>SX$DmfKz>igb4!qWd^JgYQqg=Od0#Z^7HSjpi!wx{mzSnlqv*N-{z zPa*|`f>%bl+ufAfkW%5M|FlujrWr44Z_n0m;}}U27Rl5u$yTT^3fI@V4BY-rb?3yf z4nXAepxI&)NW~dy5h-$)KfmixVdb>?s`DU=5ZWBk&Jb+BT|qTRF{R}C26LCeIHE?< zWjDt2C)f%$qxMPg5r`=y^TY4i?$cFp1G`7|yH57J`oOa<1Go!oR1=inFPJdXid{eI zGBXFOXR$fLmV*&QImX}oePTU-9WiXJKlzpnntBn3ei^+NEIBRNL#Sfw9cIHL&;mBxe*4lO75m+F{;3{G7j$Jr#ai{i*Ys zkSUVnQhMmilUi1*IGhRui>T28(;y#A%i);{KGnGJ)zv~Od5rV$j|b;>JZ%*L4ND`D7c{yM%}CmNzi8suhe>en zUq)GWMGNA?E4nmD?rlbMVs@^i3e>F5Mb(;0)Jt_a*806Lg55@5-&bY?v4(YKy7BV+ zhnApWO-WgLMuyg^iT04-;ei-@!rX6Xf^`A+d?c*SA;vlZjt}#bhBF)<83lk`R9pZn zme127YbxfQ$dc=XC?Jzy^3?TKIyuHlv5oy-Xt&L@BxW`*sUDyp7M%wc!1e>(kr0w- z8Cj>7=W}j?OtXwIFe-K5LJCM!$W_}oJeVO1^B#;;5Xvn zzb++a@o~>DS29Tw?g=2gzy-p6a6itzeHE4rPY)h3!|r2*8Hx3$M_qhdD1RNGY9TmggY6q9_}pg*XNQVGUJ7eL7zU#y5!^; zcD1ImeAHC+!dj<_n`oBNF28@AIbSaB%nVv?#J}=8pDVdm|NCbt8hR|MfEie^*%Pu9 zE`QR^eyS!_Gurk9oGqy^J8l)d;7IutI3q>}%}(AsqyF2h{jHs2h z%CJjITVnVhp%pg=`Dxb9*e|r@HMM!#hYtK%CVA8HuSgD1ML?{mZlkQ@*EWBSVxHRN z3_)sef=@=$>yGx!XS%;9tfOt#DDo_(=0jVeoxd_(kwv=^lrWEx{udDi2ZFb**!gEY zvX9DDb)BR)m^)>|*Djh?!}dnMSWhY%>MayLw}#4Zu9hG9!|nH1HQrCyd?q5cx?Jsx zHO&)kCf>D+YX$M-?SZA(ns#@6yslK?mHiNQy=6mcJduR&pzTP^*gw+&Eat-JXN$|P zreQy#(I3@U2y(Kooe?8AFAwI7yi?9YCNNmKH;z-|O<6_+?H_a4vhud*QU^Sn@;UXw zKsOIX1fg3l$>iSC-y`_|`wvPVZ5ODyp~O0S8upexvpo-a6$WUWSho!OqHHA{T5-#Q z8TnbtuudO~i{GnuKD~E-(rF6RuGc=Bqg?137vI|ap(r@!@L=Qx5!;JKm+w9*5Tk!x z!(n3TUQ^dPX8{!BsG^Pv;qV-e$_P#CX=pi7@Hj=J@a*>Zl@@MZUQBuOce#9S!;&whbc;;=RN3_&IWThv3Q{%XYw(T^$uUtk)WUfe@#HpH=uoHGlX zI0?B%qJ8?Mf*@Zd{4hRvAdnG ztXaL6USLnzPj`v?!Th#1IDIA^>+@rIchdGI(@y)dloI+Io=y+30-h_S-a2tE?mfZPN8yv52E?grO3TM2#2t?bL+Tu6$gd zt)g|QlaFq`Qn&G)8b0J;Zr)sIkD7}cx3`*T%4;vLi@$b}JQ~m9%I=9DosX-;tyM_F zuBFV0n%O#VH?4_KNDFpI{!&>1zd5+&E|D+S^8$=F5O;hKX^m{m_3zWD_ z8k=!WPYK=(uZ%88?LUOQHf=6MWQI$vo8I!-8SzldHt7<{`CkOl$jd1$n`eE77#2MB zL!ODa%iitMC1W1TK3I6=&Zbi|06oSZc6Tm0-!7idu;7ai#}7cvOa$d%j8Kn(im<1# zD3OW^FI0+4E*2`%5+QbZ^#0H3yNLYCJ!q3GbLvF@=ZuMvvv(LF=gb~oSe3QCH`-w9 zcnm=!y@%u!;%Q0(+v|THLN{H?C$A>Glx4p+OP2Xv&>4mu+Q#-Oqm1=a1rBbYLB6xU zg-W@w&qWi|`v%|R79u%fn#@Yl%cLRs7>1`c4jQ7w3uepSXI>I&3eql;w!Wf<v)Ft=tI4s|?XKs!w zbhNW45oES^mxuaqNUtiJwZrRwy&T)xN^QZ%_e;qI_|H5c-G^Z|1K6TwZAiCET(DZ= zN5cK1`G-sYA91&q2MfOIFX`}LdV&|y$ab)ksqpQU5E{^V;e0zVXvRPT*n-`nsoz$N z2aBH`(<#Xg+RQ7uXMriEa+J$_tcmEg65l6}@J^v4%0AS5dz4j=vh647?72ymh3n@e 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 diff --git a/Screenshots/GLabViz_interface_1.png b/Screenshots/GLabViz_interface_1.png index c9eb37e9f127001d14b7242f4759cf6c74fcd6d1..c31ac825bcaca0c290a22590294bb409cf1e5365 100644 GIT binary patch literal 5603 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 Z9M7bzJqY4s-m literal 8358 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 Date: Fri, 19 Mar 2021 13:28:16 +0000 Subject: [PATCH 10/10] Bump pillow from 7.1.0 to 8.1.1 Bumps [pillow](https://github.com/python-pillow/Pillow) from 7.1.0 to 8.1.1. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst) - [Commits](https://github.com/python-pillow/Pillow/compare/7.1.0...8.1.1) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f74b5d1..b935d2e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -61,7 +61,7 @@ parso==0.5.1 partd==1.0.0 pefile==2019.4.18 pickleshare==0.7.5 -Pillow==7.1.0 +Pillow==8.1.1 pkginfo==1.4.2 prometheus-client==0.7.1 prompt-toolkit==2.0.9