From 134b3ad1087bed98a157d8ce98ad786bfcf850e9 Mon Sep 17 00:00:00 2001 From: Sofia Kapsiani <77961877+SofiaKapsiani@users.noreply.github.com> Date: Fri, 11 Oct 2024 13:33:57 +0100 Subject: [PATCH] request bin width for tiff and csv files --- main.py | 2 +- utils/lifetime_cal.py | 30 +++++++++----- utils/toolbar.py | 95 +++++++++++++++++++++++++++++++++++++++---- 3 files changed, 107 insertions(+), 20 deletions(-) diff --git a/main.py b/main.py index afc684b..7bb89fd 100644 --- a/main.py +++ b/main.py @@ -21,7 +21,7 @@ app.setWindowIcon(icon) window = MainWindow(app) -window.setWindowTitle("FLIMPA (v1.0)") +window.setWindowTitle("FLIMPA (v1.1)") window.setWindowIcon(icon) # Set the window icon here window.showMaximized() diff --git a/utils/lifetime_cal.py b/utils/lifetime_cal.py index 88f4b8e..131fac3 100644 --- a/utils/lifetime_cal.py +++ b/utils/lifetime_cal.py @@ -39,7 +39,7 @@ def __init__(self, main_window, app): # --- Image loading and lifetime calculation --- # - def load_raw_data(self, file_name): + def load_raw_data(self, file_name, bin_width): """Function for loading raw data files.""" try: if file_name.endswith('.sdt'): @@ -62,11 +62,7 @@ def load_raw_data(self, file_name): items, 0, False) self.shared_info.ptu_channel = items.index(item) if not ok: - raise DataProcessingError("Channel selection cancelled by user.") - - - - + raise DataProcessingError("Channel selection cancelled by user.") data = ptu[:, ..., self.shared_info.ptu_channel, :].sum(0) # re-arrange channels to time-channels (change of intensity with time) and x-coordinate, y-coordinate (matches FLIMFit img) @@ -77,9 +73,13 @@ def load_raw_data(self, file_name): elif file_name.endswith('.tiff') or file_name.endswith('.tif'): data = imread(file_name) data = data.squeeze() - t_series = [] - #t_resolution = 1 / (self.frequency * data.shape[0]) - #t_series = np.asarray([i * t_resolution for i in range(data.shape[0])], dtype =np.float32) + if bin_width != None: + if bin_width == "estimate": + t_series = [] + else: + t_series = np.asarray([i*10**(-9) * bin_width for i in range(data.shape[0])], dtype =np.float32) + else: + raise UnsupportedFileFormatError() else: raise UnsupportedFileFormatError() @@ -103,7 +103,7 @@ def load_raw_data(self, file_name): show_error_message(self.main_window, "Loading Error", f"An unexpected error occurred while loading the file: {e}") raise FileLoadingError(f"Error loading file '{file_name}': {e}") - def load_irf(self, file_name): + def load_irf(self, file_name, bin_width): try: if file_name.endswith('.sdt'): sdt_file = sdt.SdtFile(file_name) @@ -147,7 +147,13 @@ def load_irf(self, file_name): data = data.reshape((data.shape[0], 1, 1)).astype(np.float32) # Calculate time series - t_series = [] + if bin_width != None: + if bin_width == "estimate": + t_series = [] + else: + t_series = np.asarray([i*10**(-9) * bin_width for i in range(data.shape[0])], dtype =np.float32) + else: + raise UnsupportedFileFormatError() else: raise UnsupportedFileFormatError("Only .sdt and .csv file formats are supported for IRF data.") @@ -265,6 +271,7 @@ def ref_correction(self): t_resolution = 1 / ( freq* ref_data.shape[0]) t_series = np.asarray([i * t_resolution for i in range(ref_data.shape[0])], dtype =np.float32) self.shared_info.ref_files_dict[self.ref_filename]['t_series'] = t_series + print("bind width estimated as:", t_resolution*10**9, "ns") # calculate reference g and s coordinates ref_g, ref_s, _, _ = self.calc_Coordinates( data=ref_data, t_series=t_series, bins =bins_ref, min_photons=0, max_photons_t = False, mode_same = False) @@ -324,6 +331,7 @@ def lifetime_parameters(self, filename, M_ref, phi_ref): t_resolution = 1 / ( freq* raw_data.shape[0]) t_series = np.asarray([i * t_resolution for i in range(raw_data.shape[0])], dtype =np.float32) self.shared_info.raw_data_dict[filename]['t_series'] = t_series + print("bind width estimated as:", t_resolution*10**9, "ns") # analyse manually masked data if availabe if mask_data is not None: diff --git a/utils/toolbar.py b/utils/toolbar.py index 11c3f32..c1a826d 100644 --- a/utils/toolbar.py +++ b/utils/toolbar.py @@ -1,5 +1,6 @@ -from PySide6.QtWidgets import (QStatusBar, QMenuBar, QFileDialog, QInputDialog, QFileDialog, QLineEdit, +from PySide6.QtWidgets import (QStatusBar, QMenuBar, QFileDialog, QInputDialog, QFileDialog, QLineEdit, QLabel,QPushButton, QProgressDialog, QApplication, QMessageBox, QComboBox, QVBoxLayout, QDialogButtonBox, QDialog) +from PySide6.QtGui import QDoubleValidator from PySide6.QtCore import Qt, QTimer import numpy as np @@ -98,7 +99,55 @@ def enter_files_by_cond(self): if condition: self.data_condition = condition fnames = self.file_manager() + + def get_float_input(self): + dialog = QDialog(self.main_window) + dialog.setWindowTitle("Input Required") + + layout = QVBoxLayout(dialog) + + label = QLabel("Enter the bin width (in ns):", dialog) + layout.addWidget(label) + + # Create a QLineEdit and set a QDoubleValidator for non-negative values and many decimal places + line_edit = QLineEdit(dialog) + validator = QDoubleValidator(0.0, 2.0, 50) # Non-negative range, 10 decimal places + validator.setNotation(QDoubleValidator.StandardNotation) + line_edit.setValidator(validator) + layout.addWidget(line_edit) + + # Create an "Estimate" button + estimate_button = QPushButton("Estimate", dialog) + layout.addWidget(estimate_button) + + # Create dialog buttons (OK and Cancel) + buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, dialog) + layout.addWidget(buttons) + + # Connect signals + buttons.accepted.connect(dialog.accept) + buttons.rejected.connect(dialog.reject) + + def estimate_bin_width(): + # Set the bin width to "estimate" + line_edit.setText("estimate") + # Show a warning message + QMessageBox.warning(dialog, "Warning", + "You have selected 'estimate' for the bin width. This will be calculated as:\n" + "bin width = 1 / (laser repetition rate × bin number).\n\n" + "Please note that this estimation may lead to inaccurate results depending on your data acquisition settings. " + "Please ensure that your laser settings and detection conditions are suitable for this calculation.") + estimate_button.clicked.connect(estimate_bin_width) + + if dialog.exec() == QDialog.Accepted: + # Check if user selected "estimate" + if line_edit.text() == "estimate": + return "estimate", True + else: + return float(line_edit.text()), True + else: + return None, False def file_manager(self): @@ -106,7 +155,7 @@ def file_manager(self): fnames, _ = QFileDialog.getOpenFileNames(self.main_window, "Select one or more files to open") # Adjust the title as needed if not fnames: return # Cancel if no files are selected - + # channel used when loading .ptu files self.shared_info.ptu_channel = None @@ -119,6 +168,8 @@ def file_manager(self): progress_dialog.setCancelButton(None) #progress_dialog.setWindowFlag(Qt.WindowCloseButtonHint, False) + bin_width = None # Initialize bin_width variable to store the user input + try: for i, fname in enumerate(fnames): # Iterate through the selected files if fname: @@ -127,8 +178,15 @@ def file_manager(self): progress_dialog.setLabelText(f"Loading file {i+1} of {len(fnames)}") QApplication.processEvents() # Process events to keep the UI responsive + # Check if the file is a .tif or .tiff and if bin_width is already set + if fname.lower().endswith(('.tif', '.tiff')) and bin_width is None: + # Prompt the user for bin_width only once + bin_width, ok = self.get_float_input() + if not ok or bin_width is None: + break # If the user cancels or no valid input, exit + # Assuming you have a mechanism to process and display each file - data, t_series = LifetimeData(self.main_window, self.app).load_raw_data(fname) + data, t_series = LifetimeData(self.main_window, self.app).load_raw_data(fname, bin_width) filename = fname.split('/')[-1].split('\\')[-1].split(".")[0] # check if entry is duplicate and if so rename it @@ -154,6 +212,7 @@ def file_manager(self): self.data_condition = "None" return fnames + def load_masks(self): # Select one or more files to open fnames, _ = QFileDialog.getOpenFileNames(self.main_window, "Select one or more files to open") @@ -175,6 +234,7 @@ def load_masks(self): # Disable the close button and remove the cancel button progress_dialog.setCancelButton(None) #progress_dialog.setWindowFlag(Qt.WindowCloseButtonHint, False) + bin_width = None # Initialize bin_width variable to store the user input try: for i, fname in enumerate(fnames): # Iterate through the selected files @@ -184,8 +244,15 @@ def load_masks(self): progress_dialog.setLabelText(f"Loading file {i+1} of {len(fnames)}") QApplication.processEvents() # Process events to keep the UI responsive + # Check if the file is a .tif or .tiff and if bin_width is already set + if fname.lower().endswith(('.tif', '.tiff')) and bin_width is None: + # Prompt the user for bin_width only once + bin_width, ok = self.get_float_input() + if not ok or bin_width is None: + break # If the user cancels or no valid input, exit + # Assuming you have a mechanism to process and display each file - data, t_series = LifetimeData(self.main_window, self.app).load_raw_data(fname) + data, t_series = LifetimeData(self.main_window, self.app).load_raw_data(fname, bin_width) filename_original = fname.split('/')[-1].split('\\')[-1].split(".")[0] masked_data, mask_arr = LifetimeData(self.main_window, self.app).mask_data(masks_dir, filename_original, data) @@ -225,8 +292,6 @@ def load_masks_cond(self): self.data_condition = condition fnames = self.load_masks() - - def handle_duplicates(self, filename): """Function to rename duplicate entries""" base_filename = filename @@ -238,8 +303,15 @@ def handle_duplicates(self, filename): def load_ref_file(self): fname, _ = QFileDialog.getOpenFileName(self.main_window," Selet a reference file to open") + + bin_width = None + if fname.lower().endswith(('.tif', '.tiff')) and bin_width is None: + # Prompt the user for bin_width only once + bin_width, ok = self.get_float_input() + if not ok or bin_width is None: + return # If the user cancels or no val - ref_data,t_series = LifetimeData(self.main_window, self.app).load_raw_data(fname) + ref_data,t_series = LifetimeData(self.main_window, self.app).load_raw_data(fname, bin_width) filename = fname.split('/')[-1].split('\\')[-1].split(".")[0] # updated reference bins based on time channels of reference file self.shared_info.ref_files_dict[filename] = {"ref_data":ref_data, "t_series":t_series, "bins_ref": ref_data.shape[0] } # Assuming you want to store the full path @@ -248,8 +320,15 @@ def load_ref_file(self): def load_irf_file(self): fname, _ = QFileDialog.getOpenFileName(self.main_window," Selet a reference file to open") + + bin_width = None + if fname.lower().endswith('.csv') and bin_width is None: + # Prompt the user for bin_width only once + bin_width, ok = self.get_float_input() + if not ok or bin_width is None: + return # If the user cancels or no val - ref_data,t_series = LifetimeData(self.main_window, self.app).load_irf(fname) + ref_data,t_series = LifetimeData(self.main_window, self.app).load_irf(fname, bin_width) filename = fname.split('/')[-1].split('\\')[-1].split(".")[0] self.shared_info.ref_files_dict[filename] = {"ref_data":ref_data, "t_series":t_series, "bins_ref" : 1} # Assuming you want to store the full path