Source code for pycat.toolbox.correlation_func_analysis_tools

"""
Correlation Function Analysis Module for PyCAT

This module provides functions for computing and analyzing cross-correlation functions (CCF) 
between two arrays or images. The CCF is a measure of similarity between two signals or images
as a function of a shift applied to one of them. The module includes functions for computing 1D and 2D
CCFs, fitting Gaussian models to the CCFs, and visualizing the results. It also provides utilities
for preparing the results in a structured format for further analysis and reporting.

This module alos provides functions for computing the auto-correlation function (ACF) of a single image
and fitting Gaussian models to the ACF. The ACF is a measure of the similarity of an image with itself
as a function of a shift applied to the image. The ACF can be used to estimate the size of features in
the image. 

Author
------
    Christian Neureuter, GitHub: https://github.com/cneureuter

Date
----
    4-20-2024
"""

# Third party imports
import numpy as np
import scipy.ndimage as ndi
import scipy.stats as stats
from scipy.optimize import curve_fit
import matplotlib.pyplot as plt
import pandas as pd
import skimage as sk
from napari.utils.notifications import show_warning as napari_show_warning

# Local application imports
from pycat.ui.ui_utils import show_dataframes_dialog
from pycat.utils.general_utils import crop_bounding_box



# Cross-correlation function (CCF) analysis tools

[docs] def smooth_array(array, window_size=3): """ Applies a moving average filter to smooth the input array. This is commonly used to reduce noise in a signal. Parameters ---------- array : numpy.ndarray The input array to be smoothed. Can be any one-dimensional array of numerical values. window_size : int, optional The size of the moving average window, specifying how many elements are averaged to compute the smoothed value. Defaults to 3. Returns ------- numpy.ndarray The smoothed array, which will have the same shape as the input array. Edges of the array will be less affected by the averaging due to boundary effects in the convolution. Example ------- >>> smooth_array(np.array([1, 2, 3, 4, 5]), window_size=3) array([1.33333333, 2. , 3. , 4. , 3.66666667]) """ window = np.ones(window_size) / window_size # Create a uniform window for averaging return np.convolve(array, window, mode='same') # Apply convolution to smooth the array
[docs] def gaussian_1d(x, amplitude, mean, stddev): """ Computes a one-dimensional Gaussian function, often used to model the distribution of data. Parameters ---------- x : numpy.ndarray The input values over which the Gaussian function is computed. amplitude : float The peak amplitude of the Gaussian function. mean : float The mean (center) of the Gaussian function, where the peak occurs. stddev : float The standard deviation of the Gaussian, determining the width of the bell curve. Returns ------- numpy.ndarray The values of the Gaussian function evaluated at 'x'. Example ------- >>> gaussian_1d(np.array([1, 2, 3]), amplitude=1, mean=2, stddev=1) array([0.60653066, 1. , 0.60653066]) """ # Precompute constant part of the expression exp_component = -1 / (2 * stddev ** 2) return amplitude * np.exp(exp_component * (x - mean) ** 2)
[docs] def gaussian_2d(xy, amplitude, x0, y0, sigma_x, sigma_y): """ Computes a two-dimensional Gaussian function, often used in image processing to generate Gaussian blurs. Parameters ---------- xy : tuple of numpy.ndarray A pair of arrays (x, y) representing the meshgrid coordinates over which the Gaussian function is computed. amplitude : float The peak amplitude of the Gaussian function. x0, y0 : float The center of the Gaussian function in x and y dimensions. sigma_x, sigma_y : float The standard deviations (widths) of the Gaussian function in x and y dimensions. Returns ------- numpy.ndarray The values of the Gaussian function evaluated over the 2D space defined by 'xy'. Example ------- >>> x = np.linspace(-1, 1, 3) >>> y = np.linspace(-1, 1, 3) >>> xx, yy = np.meshgrid(x, y) >>> gaussian_2d((xx, yy), amplitude=1, x0=0, y0=0, sigma_x=1, sigma_y=1) array([[0.36787944, 0.60653066, 0.36787944], [0.60653066, 1. , 0.60653066], [0.36787944, 0.60653066, 0.36787944]]) """ x, y = xy exp_component_x = -1 / (2 * sigma_x ** 2) exp_component_y = -1 / (2 * sigma_y ** 2) return amplitude * np.exp(exp_component_x * (x - x0) ** 2 + exp_component_y * (y - y0) ** 2)
def _compute_1d_correlation(array1, array2, max_shift, roi_mask): """ Computes the 1D cross-correlation function (CCF) between two arrays, with an option to focus the computation within a specified region of interest (ROI). This function shifts one array over the other within a defined range and calculates the Pearson correlation coefficient for each shift. Parameters ---------- array1 : numpy.ndarray The reference array for which the correlation is computed. array2 : numpy.ndarray The array that is shifted and correlated against array1. max_shift : int The maximum number of elements by which array2 is shifted relative to array1 for correlation computation. roi_mask : numpy.ndarray, optional A binary mask specifying the Region Of Interest (ROI) within which the correlation is computed. Must be of the same size as array1 and array2. Returns ------- ccf_values : numpy.ndarray An array containing the correlation coefficients corresponding to each shift. shifts : numpy.ndarray An array of integer shift values ranging from -max_shift to max_shift. Notes ----- The correlation at each shift is calculated using the Pearson correlation coefficient formula. If an ROI mask is provided, only the data points where the mask is True are considered in the computation. """ ccf_values = np.zeros(2 * max_shift + 1) # Initialize array to store CCF values shifts = np.arange(-max_shift, max_shift + 1) # Array of shift values for i, shift_val in enumerate(shifts): shifted_array = ndi.shift(array2, shift_val, mode='nearest') # Shift array2 # Apply ROI mask if provided if roi_mask is not None: masked_array1 = array1[roi_mask] masked_shifted_array = shifted_array[roi_mask] else: masked_array1 = array1.flatten() # Use entire array if no mask masked_shifted_array = shifted_array.flatten() # Calculate correlation for the current shift, considering the mask if masked_array1.size > 0 and masked_shifted_array.size > 0: ccf = stats.pearsonr(masked_array1, masked_shifted_array)[0] ccf_values[i] = ccf else: ccf_values[i] = 0 # or np.nan # Handle cases with no overlap return ccf_values, shifts
[docs] def fit_gaussian_1d(ccf_values, shifts): """ Fits a Gaussian function to 1D cross-correlation function values to determine the peak and width of the correlation. Parameters ---------- ccf_values : numpy.ndarray The cross-correlation function values obtained from shifting one array over another. shifts : numpy.ndarray The array of shift values corresponding to each CCF value. Returns ------- results : dict or None A dictionary containing the results of the Gaussian fitting (parameters and diagnostics) or None if the fitting fails. Notes ----- The Gaussian fitting process assumes a bell-shaped curve typical for cross-correlation functions in signal processing. This function uses the `curve_fit` method from `scipy.optimize` to perform the fitting. The initial guesses for the Gaussian parameters are determined based on the properties of the CCF values. """ shifts_bottom_quarter = len(shifts)//4 shifts_top_quarter = 3*len(shifts)//4 smoothed_ccf = smooth_array(ccf_values) # Smooth CCF values for better fitting max_ccf = max(smoothed_ccf[shifts_bottom_quarter:shifts_top_quarter], key=abs) # Find maximum CCF value for initial guess # Determine initial guess parameters for Gaussian fitting if max_ccf > 0: p0 = [1, 0, 1] # Assume positive peak else: p0 = [-1, 0, 1] # Assume negative peak for inverted Gaussian try: # Perform Gaussian fitting popt, pcov = curve_fit(gaussian_1d, shifts, ccf_values, p0=p0) results = _extract_fit_results_1d(ccf_values, shifts, popt, pcov) except RuntimeError: results = None # Fitting failed return results
def _extract_fit_results_1d(ccf_values, shifts, popt, pcov): """ Extracts and organizes the results from the 1D Gaussian fitting process into a structured dictionary. This function provides a comprehensive summary of the Gaussian fit to the cross-correlation function (CCF) values, including fit parameters and statistical measures of the fit quality. Parameters ---------- ccf_values : numpy.ndarray The cross-correlation function values, which represent the correlation between two arrays at varying shifts. shifts : numpy.ndarray The array of shift values corresponding to each CCF value. These are the values at which the correlation was calculated. popt : numpy.ndarray The optimal values for the parameters of the Gaussian function, typically obtained from a curve fitting procedure. Expected to contain [amplitude, mean (peak position), standard deviation (width of the peak)]. pcov : numpy.ndarray The covariance matrix associated with the parameter estimates from the curve fit. This matrix provides a measure of the parameter uncertainty, which can be used to calculate the standard error of the fit parameters. Returns ------- results : dict A dictionary containing detailed results of the Gaussian fitting, which includes: - 'ccf_values': The original cross-correlation function values used for the fitting. - 'shifts': The shift values associated with the CCF values. - 'gaussian_params': The optimized parameters of the Gaussian fit. - 'gaussian_peak': The peak position of the Gaussian fit on the shift axis. - 'amplitude': The maximum amplitude observed in the CCF values. - 'peak_location': The shift position at which the maximum CCF value occurs. - 'ccf_sigma': The standard deviation of the CCF values, providing a measure of variability. - 'goodness_of_fit': The standard errors of the estimated Gaussian parameters, derived from the diagonal of the covariance matrix. - 'max_smoothed_ccf': The maximum value of the CCF after applying a smoothing filter. Notes ----- The function assumes that the input CCF values have already been smoothed if necessary and that the Gaussian fitting has been successfully completed with parameters conducive to generating meaningful results. The returned dictionary is useful for analyzing the quality and characteristics of the fit, as well as for reporting and visualization purposes. """ ccf_sigma = np.std(ccf_values) # Standard deviation of CCF values # Compile results into a dictionary results = { 'ccf_values': ccf_values, 'shifts': shifts, 'gaussian_params': popt, 'gaussian_peak': popt[1], # Peak position of the Gaussian 'amplitude': max(ccf_values.ravel(), key=abs), # Max amplitude of the CCF values 'peak_location': shifts[np.argmax(np.abs(ccf_values))], # Location of peak CCF value 'ccf_sigma': ccf_sigma, # Standard deviation of the CCF 'goodness_of_fit': np.sqrt(np.diag(pcov)), # Standard error of the estimated parameters 'max_smoothed_ccf': max(smooth_array(ccf_values), key=abs) # Max value after smoothing } return results def _compute_2d_correlation(array1, array2, max_shift, roi_mask): """ Computes the 2D cross-correlation function between two arrays, potentially restricted to a specified region of interest (ROI). This function measures how similar array2 is to array1 when array2 is shifted by various amounts in the x and y directions. Parameters ---------- array1 : numpy.ndarray The first input array for which the cross-correlation is computed. array2 : numpy.ndarray The second input array that is shifted and correlated against array1. max_shift : int The maximum number of elements by which array2 is shifted in both x and y directions. roi_mask : numpy.ndarray, optional A binary mask specifying the ROI within the arrays. Only the areas where roi_mask is True will be considered. Returns ------- ccf_values : numpy.ndarray A 2D array containing the cross-correlation function values. x, y : numpy.ndarray, numpy.ndarray Meshgrid arrays representing the shifts in the x and y directions. Notes ----- The cross-correlation function is computed by shifting array2 across array1 within the range defined by [-max_shift, max_shift] in both dimensions. If an ROI mask is provided, the correlation is only computed within the specified region. """ ccf_values = np.zeros((2 * max_shift + 1, 2 * max_shift + 1)) x, y = np.meshgrid(np.arange(-max_shift, max_shift + 1), np.arange(-max_shift, max_shift + 1)) for i in range(-max_shift, max_shift + 1): for j in range(-max_shift, max_shift + 1): shifted_array = ndi.shift(array2, [i, j], mode='nearest') # Conditionally apply the ROI mask if roi_mask is not None: # Function crop_bounding_box should crop the array to the bounding box defined by the mask masked_array1, cropped_mask, _ = crop_bounding_box(array1, roi_mask) masked_shifted_array, _, _ = crop_bounding_box(shifted_array, roi_mask) else: masked_array1 = array1.copy() masked_shifted_array = shifted_array.copy() cropped_mask = None # Calculate correlation for masked regions if masked_array1.size > 0 and masked_shifted_array.size > 0: ccf, _ = sk.measure.pearson_corr_coeff(masked_array1, masked_shifted_array, cropped_mask) ccf_values[i + max_shift, j + max_shift] = ccf else: ccf_values[i + max_shift, j + max_shift] = 0 # or np.nan # Handle cases with no overlap return ccf_values, x, y
[docs] def fit_gaussian_2d(ccf_values, x, y): """ Fits a 2D Gaussian function to the computed cross-correlation function values to determine the peak correlation and its distribution in two dimensions. Parameters ---------- ccf_values : numpy.ndarray The 2D cross-correlation function values obtained from comparing two arrays. x : numpy.ndarray The x-coordinates of the shifts applied during the cross-correlation calculation. y : numpy.ndarray The y-coordinates of the shifts applied during the cross-correlation calculation. Returns ------- results : dict or None A dictionary containing the results of the Gaussian fitting, including parameters and diagnostics, or None if the fitting fails. Notes ----- The fitting process assumes a symmetric Gaussian distribution, typical for peak correlation distributions. This method uses curve fitting techniques from scipy.optimize to find the best fit parameters. """ shifts_bottom_quarter = len(x)//4 shifts_top_quarter = 3*len(x)//4 ccf_data = ccf_values.ravel() smoothed_ccf = smooth_array(ccf_data).reshape(ccf_values.shape) region_of_interest = smoothed_ccf[shifts_bottom_quarter:shifts_top_quarter, shifts_bottom_quarter:shifts_top_quarter] index_flat = np.argmax(np.abs(region_of_interest)) index_2d = np.unravel_index(index_flat, region_of_interest.shape) max_smoothed_ccf = region_of_interest[index_2d] # Initial guess for the Gaussian parameters if max_smoothed_ccf > 0: p0 = [1, 0, 0, 1, 1] # Parameters for a regular Gaussian else: p0 = [-1, 0, 0, 1, 1] # Parameters for an inverted Gaussian try: popt, pcov = curve_fit(gaussian_2d, (x.ravel(), y.ravel()), ccf_data, p0=p0) results = _extract_fit_results_2d(ccf_values, x, y, popt, pcov, max_smoothed_ccf) except RuntimeError: results = None return results
def _extract_fit_results_2d(ccf_values, x, y, popt, pcov, max_smoothed_ccf): """ Extracts and organizes the results from the 2D Gaussian fitting of the cross-correlation function values. This function compiles key information including the fitted parameters, the peak location, the standard deviation of the cross-correlation values, and goodness of fit measures. Parameters ---------- ccf_values : numpy.ndarray The 2D array of cross-correlation function values derived from comparing two datasets. x : numpy.ndarray The x-coordinates of the meshgrid over which the correlation was computed, representing shifts in the x direction. y : numpy.ndarray The y-coordinates of the meshgrid over which the correlation was computed, representing shifts in the y direction. popt : numpy.ndarray The optimal values for the parameters of the Gaussian function determined by the fitting process. Typically, this will include the amplitude, x and y position of the center, and the standard deviations in x and y. pcov : numpy.ndarray The covariance matrix associated with the fitted parameters, providing a statistical measure of the fitting accuracy. max_smoothed_ccf : float The maximum value obtained from a smoothed version of the cross-correlation function, indicating the peak correlation observed after reducing noise. Returns ------- results : dict A dictionary containing comprehensive details of the Gaussian fitting results, including: - 'ccf_values': The original matrix of cross-correlation function values. - 'shifts': A tuple of arrays (x, y) representing the shift values in the x and y directions. - 'gaussian_params': The fitted parameters of the Gaussian model. - 'gaussian_peak': The coordinates (x, y) of the peak of the Gaussian function. - 'amplitude': The maximum amplitude of the cross-correlation function. - 'peak_location': The coordinates (x, y) of the highest peak in the cross-correlation matrix. - 'ccf_sigma': A tuple containing the standard deviations of the cross-correlation function along the peak row and column. - 'goodness_of_fit': An array of standard errors for the fitted Gaussian parameters, derived from the covariance matrix. - 'max_smoothed_ccf': The maximum value of the smoothed cross-correlation function, used for assessing peak correlation quality. Notes ----- The function is intended to provide a detailed summary of the Gaussian fitting to aid in further analysis or reporting. It assumes the input parameters are correctly computed and formatted. Errors in input data or fitting parameters can significantly affect the reliability of the results. """ peak_x, peak_y = x.ravel()[np.argmax(np.abs(ccf_values))], y.ravel()[np.argmax(np.abs(ccf_values))] # Find the index of the peak in the flattened array peak_index = np.argmax(np.abs(ccf_values)) # Convert the flattened index to 2D indices peak_row, peak_col = np.unravel_index(peak_index, ccf_values.shape) # Calculate sigma_x and sigma_y from the ccf values ccf_sigma_x = np.std(ccf_values[peak_row, :]) ccf_sigma_y = np.std(ccf_values[:, peak_col]) results = { 'ccf_values': ccf_values, 'shifts': (x, y), 'gaussian_params': popt, 'gaussian_peak': (popt[1], popt[2]), # Peak position of the Gaussian 'amplitude': max(ccf_values.ravel(), key=abs), # Max amplitude of the CCF values 'peak_location': (peak_x, peak_y), # Location of peak CCF value 'ccf_sigma': (ccf_sigma_x, ccf_sigma_y), # Standard deviation of the central slice of the CCF 'goodness_of_fit': np.sqrt(np.diag(pcov)), # Standard error of the estimated parameters 'max_smoothed_ccf': max_smoothed_ccf # Max value after smoothing } return results
[docs] def process_ccf(array1, array2, roi_mask): """ Performs cross-correlation analysis on 1D or 2D arrays with optional region of interest (ROI) masking. This function calculates the cross-correlation function (CCF) and fits a Gaussian model to it. Parameters ---------- array1 : numpy.array The first input array for correlation analysis. Must be of the same dimension as array2. array2 : numpy.array The second input array for correlation analysis. Must be of the same dimension as array1. roi_mask : numpy.array, optional An optional mask to specify the region of interest within the arrays. If not provided, the entire array is considered. Returns ------- results : dict A dictionary containing the results of the CCF analysis and Gaussian fitting. This includes CCF values, Gaussian parameters, and other relevant metrics depending on whether the analysis was 1D or 2D. Raises ------ ValueError If the dimensions of the input arrays are neither 1D nor 2D. """ max_shift = 20 # Maximum shift for correlation analysis dimension = len(array1.shape) # Determine the dimensionality of the input arrays results = {} if dimension == 1: # Perform 1D correlation analysis and Gaussian fitting ccf_values, shifts = _compute_1d_correlation(array1, array2, max_shift, roi_mask) results = fit_gaussian_1d(ccf_values, shifts) elif dimension == 2: # Perform 2D correlation analysis and Gaussian fitting ccf_values, x, y = _compute_2d_correlation(array1, array2, max_shift, roi_mask) results = fit_gaussian_2d(ccf_values, x, y) else: raise ValueError("Array dimensions not supported. Only 1D or 2D arrays are allowed.") return results
[docs] def plot_1d_results(results): """ Plots the 1D cross-correlation function (CCF) and its Gaussian fit. This visualization includes the original CCF values and the Gaussian model overlaid to show the fitting quality. Parameters ---------- results : dict A dictionary containing the results of the 1D CCF analysis. This must include 'shifts', 'ccf_values', 'gaussian_params', and 'r_squared' among other possible keys. """ # Create a figure with specified size plt.figure(figsize=(10, 6)) # Plot the original CCF values plt.plot(results['shifts'], results['ccf_values'], label='CCF') # Plot the fitted Gaussian curve over the CCF values plt.plot(results['shifts'], gaussian_1d(results['shifts'], *results['gaussian_params']), label='Fitted Gaussian') # Labeling the axes and setting the title with R-squared value plt.xlabel('Shift') plt.ylabel('CCF Value') plt.title(f'1D CCF with Fitted Gaussian') # Display legend and show the plot plt.legend() plt.show()
[docs] def plot_2d_results(results): """ Plots the 2D cross-correlation function (CCF) and its Gaussian fit. The function generates two subplots: one for the original CCF and another for the fitted Gaussian function, both with colorbars to assist in interpretation. Parameters ---------- results : dict A dictionary containing the results of the 2D CCF analysis. This includes 'shifts', 'ccf_values', and 'gaussian_params' among other possible keys. """ # Extract shifts and CCF values from the results x, y = results['shifts'] ccf_values = results['ccf_values'] # Calculate the fitted Gaussian values to match the shape of CCF values fitted_gaussian = gaussian_2d((x, y), *results['gaussian_params']).reshape(ccf_values.shape) # Create subplots for both CCF and fitted Gaussian fig, ax = plt.subplots(1, 2, figsize=(12, 6)) # Plot the original 2D CCF with colorbar ccf_img = ax[0].imshow(ccf_values, origin='lower', extent=[x.min(), x.max(), y.min(), y.max()]) ax[0].set_title('2D CCF') fig.colorbar(ccf_img, ax=ax[0]) # Plot the fitted Gaussian function with colorbar gaussian_img = ax[1].imshow(fitted_gaussian, origin='lower', extent=[x.min(), x.max(), y.min(), y.max()]) ax[1].set_title('Fitted Gaussian Function') fig.colorbar(gaussian_img, ax=ax[1]) plt.tight_layout() # Adjust layout to prevent overlap plt.show()
[docs] def prepare_1d_result_dfs(results): """ Prepares pandas DataFrames for both the raw CCF values and the fitted Gaussian parameters from the results of 1D cross-correlation analysis. This facilitates easier analysis and visualization of the CCF fitting results. Parameters ---------- results : dict A dictionary containing the results of the 1D CCF analysis, including Gaussian fitting parameters and their goodness of fit, among other metrics. Returns ------- fitted_params_df : pandas.DataFrame DataFrame containing the names and values of the fitted Gaussian parameters along with their covariance. raw_params_df : pandas.DataFrame DataFrame containing the names and values of raw CCF metrics, including peak amplitude and location. Notes ----- Ensure that all expected keys are present in the `results` dictionary to avoid `KeyError`. """ # Create DataFrame for fitted Gaussian parameters fitted_params_df = pd.DataFrame({ 'Parameter': ['Amplitude', 'mu', 'sigma'], 'Value': list(results['gaussian_params']), 'Covariance': list(results['goodness_of_fit']) }) # Create Dataframe for raw CCF parameters raw_params_df = pd.DataFrame({ 'Parameter': ['Amplitude', 'Smoothed CCF Amplitude', 'mu', 'sigma'], 'Value': [ results['amplitude'], results['max_smoothed_ccf'], results['peak_location'], results['ccf_sigma'] ] }) return fitted_params_df, raw_params_df
[docs] def prepare_2d_result_dfs(results): """ Prepares pandas DataFrames for both the raw CCF values and the fitted Gaussian parameters from the results of 2D cross-correlation analysis. These DataFrames organize the CCF and Gaussian fitting results, making them more accessible for further analysis and reporting. Parameters ---------- results : dict A dictionary containing the results of the 2D CCF analysis, including Gaussian fitting parameters and their goodness of fit, among other metrics. Returns ------- fitted_params_df : pandas.DataFrame DataFrame containing the names and values of the fitted Gaussian parameters along with their covariance. raw_params_df : pandas.DataFrame DataFrame containing the names and values of raw CCF metrics, such as peak amplitude and locations along both axes. Notes ----- This function assumes that the `results` dictionary contains all necessary keys and values. Missing keys will result in `KeyError`. """ # Creating DataFrame for fitted Gaussian parameters fitted_params_df = pd.DataFrame({ 'Parameter': ['Amplitude', 'mu_x', 'mu_y', 'sigma_x', 'sigma_y'], 'Value': list(results['gaussian_params']), 'Covariance': list(results['goodness_of_fit']) }) # Creating DataFrame for raw CCF parameters raw_params_df = pd.DataFrame({ 'Parameter': ['Amplitude', 'Smoothed CCF Amplitude', 'mu_x', 'mu_y', 'sigma_x', 'sigma_y'], 'Value': [ results['amplitude'], results['max_smoothed_ccf'], results['peak_location'][0], results['peak_location'][1], results['ccf_sigma'][0], results['ccf_sigma'][1] ] }) return fitted_params_df, raw_params_df
[docs] def ccf_analysis(image1, image2, roi_mask, label_flag=False): """ Performs cross-correlation function (CCF) analysis on two images and processes the results depending on the dimensionality (1D or 2D) of the images. It optionally skips the plotting of results based on the label_flag, which is useful for batch processing or when visual output is unnecessary. Parameters ---------- image1 : numpy.ndarray The first input image for CCF analysis. Must be of the same dimension as image2. image2 : numpy.ndarray The second input image for CCF analysis. Must be of the same dimension as image1. roi_mask : numpy.ndarray A binary or labeled array indicating the region of interest (ROI) within the images. label_flag : bool, optional If True, skips the plotting of results. This is particularly useful for automated batch processing where visual output is not required. Default is False. Returns ------- fitted_params_df : pandas.DataFrame DataFrame containing the fitted Gaussian parameters and their covariance, structured for further analysis. raw_params_df : pandas.DataFrame DataFrame containing the raw CCF parameters and values, providing the basic outputs of the CCF analysis. Notes ----- The function handles different dimensionalities by checking the shape of `image1`. If `process_ccf` returns None, it indicates an unsuccessful fit, possibly due to inadequate correlation, and results in empty DataFrames being returned. Ensure that `process_ccf` and other utility functions are correctly implemented and that the data passed to them conforms to expected structures. """ # Perform CCF analysis and Gaussian fitting result = process_ccf(image1, image2, roi_mask) # Prepare DataFrames for the results and plot the CCF analysis if result is None: napari_show_warning("Fit was not successful. Likely no correlation between the images.") fitted_params_df = pd.DataFrame() raw_params_df = pd.DataFrame() else: if len(image1.shape) == 1: # Prepare DataFrames for 1D analysis results fitted_params_df, raw_params_df = prepare_1d_result_dfs(result) if not label_flag: # Plot the 1D results plot_1d_results(result) elif len(image1.shape) == 2: # Prepare DataFrames for 2D analysis results fitted_params_df, raw_params_df = prepare_2d_result_dfs(result) if not label_flag: # Plot the 2D results and a central slice plot_2d_results(result) # Central slice of CCF fig, ax2 = plt.subplots(1, 1, figsize=(10, 6)) ax2.plot(result['shifts'][0][0, :], result['ccf_values'][:, 20]) ax2.set_title('Central Slice of 2D CCF') ax2.set_xlabel('Shift') ax2.set_ylabel('CCF Value') plt.show() return fitted_params_df, raw_params_df
[docs] def run_ccf_analysis(image_layer1, image_layer2, roi_mask_layer, data_instance): """ Orchestrates the cross-correlation function (CCF) analysis for pairs of image layers, handling different types of ROI masks (binary or labeled) and integrating results into a specified data management object. It supports handling multiple ROI labels and aggregates results in a consistent format for analysis and visualization. Parameters ---------- image_layer1 : napari.layers.Image The first image layer containing data for CCF analysis. image_layer2 : napari.layers.Image The second image layer containing data for CCF analysis. roi_mask_layer : napari.layers.Labels or None The image layer containing the ROI mask data. Can be None if no ROI is to be applied. data_instance : object An object that stores and manages the results of the CCF analysis, typically having a 'data_repository' attribute for storing results data. Raises ------ ValueError If the input images do not have the same shape, or if the ROI mask does not match the dimensions of the images. Notes ----- The function processes labeled masks by isolating each label into its own binary mask and performs CCF analysis separately for each. The results are then aggregated. For binary masks or the absence of a mask, analysis is done directly. Results are displayed in a custom dialog and stored in the provided data instance. The function updates the data_instance.data_repository with two key DataFrames: - CCF_fitted_params_df: Contains the fitted Gaussian parameters - CCF_raw_params_df: Contains the raw CCF parameters """ # Get the image data image1 = image_layer1.data image2 = image_layer2.data roi_mask = roi_mask_layer.data if roi_mask_layer is not None else None # Validate input shapes and initiate analysis if image1.shape != image2.shape: raise ValueError("Input images must have the same shape.") if roi_mask is not None and roi_mask.shape != image1.shape: raise ValueError("ROI mask must have the same shape as the input images.") label_flag = False # Initialize flag for labeled ROI processing # Initialize empty DataFrames to concatenate results concatenated_fitted_params_df = pd.DataFrame() concatenated_raw_params_df = pd.DataFrame() # Determine if the roi_mask is binary or labeled if roi_mask is not None and np.unique(roi_mask).size > 2: # Labeled mask unique_labels = np.unique(roi_mask)[1:] # Exclude 0 (background) for label in unique_labels: # Iterate over each label specific_roi_mask = (roi_mask == label).astype(bool) # Check if the label is greater than 1, so we only create one plot if label > 1: label_flag = True # Run the analysis for each label fitted_params_df, raw_params_df = ccf_analysis(image1, image2, specific_roi_mask, label_flag=label_flag) # If fitted_params_df is empty, skip the concatenation if fitted_params_df.empty: continue # Merge the results for each label after the first iteration if label_flag: concatenated_fitted_params_df = pd.merge(concatenated_fitted_params_df, fitted_params_df, on='Parameter', how='outer', suffixes=('', f'_{label}')) concatenated_raw_params_df = pd.merge(concatenated_raw_params_df, raw_params_df, on='Parameter', how='outer', suffixes=('', f'_{label}')) # Concatenate the results for the first label else: # Concatenate the results concatenated_fitted_params_df = pd.concat([concatenated_fitted_params_df, fitted_params_df], ignore_index=True) concatenated_raw_params_df = pd.concat([concatenated_raw_params_df, raw_params_df], ignore_index=True) # Binary mask or no mask, run the analysis as before else: label = 1 # Default label for binary mask roi_mask = roi_mask.astype(bool) if roi_mask is not None else None concatenated_fitted_params_df, concatenated_raw_params_df = ccf_analysis(image1, image2, roi_mask, label_flag=label_flag) # Prepare the DataFrame for display and storage if not concatenated_fitted_params_df.empty: concatenated_fitted_params_df.set_index('Parameter', inplace=True) concatenated_fitted_params_df = concatenated_fitted_params_df.round(4) else: concatenated_fitted_params_df = None # Prepare the DataFrame for display and storage if not concatenated_raw_params_df.empty: concatenated_raw_params_df.set_index('Parameter', inplace=True) concatenated_raw_params_df = concatenated_raw_params_df.round(4) else: concatenated_raw_params_df = None # Display the results in a dialog tables_info = [ ("Fitted Gaussian Parameters", concatenated_fitted_params_df), ("Raw CCF Parameters", concatenated_raw_params_df) ] window_title = "Cross-Correlation Function Analysis" show_dataframes_dialog(window_title, tables_info) # Add the results to the data instance data_instance.data_repository["CCF_fitted_params_df"] = concatenated_fitted_params_df data_instance.data_repository["CCF_raw_params_df"] = concatenated_raw_params_df
# Auto-correlation function (ACF) analysis tools
[docs] def calculate_indices_and_plot_limits(acf_shape, lower_limit, upper_limit): """ Calculate the indices for accessing array elements based on specified limits and determine the plot limits for visualization within an autocorrelation function (ACF) array. This function translates a coordinate system centered around zero (used for specifying limits) to an array index system, ensuring the indices and plot limits do not exceed the dimensions of the ACF or a default plotting range. It effectively manages the translation from a potentially negative data range to positive-only index space used in array slicing. Parameters ---------- acf_shape : tuple of int Shape of the ACF array, expected as (height, width). lower_limit : int or None The lower bound for both x and y indices in the coordinate system centered around zero. If None, defaults to half the negative shape dimension to center the view. upper_limit : int or None The upper bound for both x and y indices in the coordinate system centered around zero. If None, defaults to half the positive shape dimension to center the view. Returns ------- tuple - (tuple of int): Lower and upper x indices (inclusive) for ACF array slicing. - (tuple of int): Lower and upper y indices (inclusive) for ACF array slicing. - (tuple of int): Lower and upper x limits for plotting. - (tuple of int): Lower and upper y limits for plotting. Notes ----- This function is designed to adapt the indices for practical use in Python arrays, where indices must be non-negative. The plot limits are set with a default range but adapt based on the provided limits to enhance visualization flexibility. """ # Determine the midpoints of the ACF array (effectively the center of the array) mid_x, mid_y = acf_shape[0] // 2, acf_shape[1] // 2 # Set a default plotting limit of 50 units in either direction from the center default_plot_limit = 50 # Compute the lower indices and plotting limits for x and y if lower_limit is not None: lower_x = max(lower_limit, -mid_x) lower_y = max(lower_limit, -mid_y) plot_lower_x = max(lower_limit, -default_plot_limit) plot_lower_y = max(lower_limit, -default_plot_limit) else: lower_x, lower_y = -mid_x, -mid_y plot_lower_x, plot_lower_y = -default_plot_limit, -default_plot_limit # Compute the upper indices and plotting limits for x and y if upper_limit is not None: upper_x = min(upper_limit, mid_x) upper_y = min(upper_limit, mid_y) plot_upper_x = min(upper_limit, default_plot_limit) plot_upper_y = min(upper_limit, default_plot_limit) else: upper_x, upper_y = mid_x, mid_y plot_upper_x, plot_upper_y = default_plot_limit, default_plot_limit # Convert plot indices from coordinate space (centered on zero) to array index space lower_x_idx = int(lower_x + mid_x) upper_x_idx = int(upper_x + mid_x) lower_y_idx = int(lower_y + mid_y) upper_y_idx = int(upper_y + mid_y) return (lower_x_idx, upper_x_idx), (lower_y_idx, upper_y_idx), (plot_lower_x, plot_upper_x), (plot_lower_y, plot_upper_y)
[docs] def calculate_autocorrelation(image): """ Calculate the autocorrelation of an image using Fourier transforms. This method exploits the Wiener-Khinchin theorem, which states that the autocorrelation function of a signal can be obtained by taking the inverse Fourier transform of the power spectrum (magnitude squared of the Fourier transform) of the signal. Parameters ---------- image : numpy.ndarray The input image for which the autocorrelation is to be computed. This should be a 2D numpy array representing pixel intensities. Returns ------- shifted_autocorrelation : numpy.ndarray The shifted autocorrelation function of the input image, normalized to [0, 1]. The zero-frequency component is shifted to the center of the array, providing a more intuitive visual representation of the autocorrelation function. Notes ----- The autocorrelation function computed here provides a measure of how the image correlates with itself as it is translated over itself. High values in the autocorrelation function indicate high similarity or redundancy at the corresponding shift values. """ # Calculate the Fourier transform of the image and extract the real part fourier_transform = np.real(np.fft.fft2(image)) # Compute the power spectrum as the square of the magnitude of the Fourier transform power_spectrum = fourier_transform**2 # Normalize the power spectrum to its maximum value normalized_power_spectrum = power_spectrum / np.max(power_spectrum) # Perform an inverse Fourier transform to get the autocorrelation function autocorrelation_function = np.fft.ifft2(normalized_power_spectrum) # Normalize the autocorrelation function to range from 0 to 1 acf_min, acf_max = np.min(autocorrelation_function), np.max(autocorrelation_function) normalized_autocorrelation_function = (autocorrelation_function - acf_min) / (acf_max - acf_min) # Shift the zero-frequency component to the center of the spectrum shifted_autocorrelation = np.real(np.fft.fftshift(normalized_autocorrelation_function)) return shifted_autocorrelation
[docs] def plot_2d_autocorrelation(autocorrelation, x_lims, y_lims, title="2D Autocorrelation Function"): """ Plot a 2D visualization of the autocorrelation function, highlighting spatial relationships and symmetries. Parameters ---------- autocorrelation : numpy.ndarray The autocorrelation array to be plotted, expected to be a 2D numpy array representing autocorrelation values. x_lims : tuple The x-axis limits for the plot, specified as a tuple (min, max) to define the visible range. y_lims : tuple The y-axis limits for the plot, specified as a tuple (min, max) to define the visible range. title : str, optional The title of the plot. Default is "2D Autocorrelation Function". Notes ----- The plot is configured with a color map and boundaries defined by the shape of the autocorrelation array, centered around the middle of the array. It uses matplotlib for plotting. """ # Define the extents for the plot considering the shape of the autocorrelation acf_range = autocorrelation.shape plot_extents = [-acf_range[0]/2 - 0.5, acf_range[0]/2 + 0.5, -acf_range[1]/2 - 0.5, acf_range[1]/2 + 0.5] # Create a figure with specified size plt.figure(figsize=(7, 7)) # Plot the 2D autocorrelation function plt.imshow(autocorrelation, extent=plot_extents, cmap='CMRmap') # Set the axis limits and labels plt.xlim(x_lims[0], x_lims[1]) plt.ylim(y_lims[0], y_lims[1]) plt.xticks(fontsize=8) plt.minorticks_on() plt.yticks(fontsize=8) plt.minorticks_on() plt.xlabel('X (px)',fontsize=14) plt.ylabel('Y (px)',fontsize=14) # Add a colorbar to the plot cbar=plt.colorbar(shrink=0.75) cbar.minorticks_on() cbar.ax.tick_params(labelsize=7) plt.title(title, fontsize=16) # Display the plot with a tight layout plt.tight_layout() plt.show()
[docs] def plot_1d_auto_correlation(autocorrelation, x_lims, gaussian_params_df, title="Central Slice of ACF"): """ Plot a 1D slice of the autocorrelation function along with a fitted Gaussian curve to analyze the distribution along a single dimension. Parameters ---------- autocorrelation : ndarray The autocorrelation array, expected to be 2D, from which a central slice will be extracted and plotted. x_lims : tuple The x-axis limits for the plot, specified as a tuple (min, max) to define the visible range. gaussian_params_df : DataFrame DataFrame containing the parameters for the Gaussian fit, specifically values for amplitude, mean (mu), and standard deviation (sigma) of the Gaussian. title : str, optional The title of the plot. Default is "Central Slice of ACF". Notes ----- This function plots both the actual data from the central slice of the autocorrelation function and the Gaussian fit, facilitating comparisons between the model and empirical data. """ # Extract the range of the autocorrelation function and define the x-axis data acf_range = autocorrelation.shape x_data = np.linspace(-acf_range[1]/2 - 0.5, acf_range[1]/2 + 0.5, acf_range[1]) # Extract Gaussian parameters from DataFrame amp = gaussian_params_df['Value'].values[0] mu = gaussian_params_df['Value'].values[1] sigma = gaussian_params_df['Value'].values[2] offset = gaussian_params_df['Value'].values[3] # Compute the Gaussian function values fitted_gaussian = amp * np.exp(-((x_data - mu) ** 2) / (2 * sigma ** 2)) + offset # Create a figure with specified size plt.figure(figsize=(8, 4)) # Plot the fitted Gaussian curve plt.plot(x_data, fitted_gaussian, 'r', ls='--', label='Fitted Gaussian') # Plot the central slice of the autocorrelation function plt.plot(x_data, np.real(autocorrelation[acf_range[0]//2, :]), 'k', label='ACF', lw=3) # Set the axis limits and labels plt.xlim(x_lims[0], x_lims[1]) plt.ylim(0, 1.1) plt.xticks(fontsize=8) plt.minorticks_on() plt.yticks(fontsize=8) plt.minorticks_on() plt.xlabel('X (px)',fontsize=10) plt.ylabel('Spatial Autocorrelation',fontsize=10) plt.title(title) plt.legend() # Display the plot with a tight layout plt.tight_layout() plt.show()
[docs] def plot_acf_image(image, title="Image", cmap='CMRmap'): """ Plot an image using a specified colormap, providing visual insights into the spatial structures of the image. Parameters ---------- image : ndarray The image to plot, typically a 2D numpy array of pixel intensities. title : str, optional The title for the plot. Default is "Image". cmap : str, optional The colormap to use for the plot. Default is 'CMRmap', which is suitable for highlighting features in autocorrelation data. Notes ----- This plot function is designed for general-purpose image visualization with an emphasis on flexibility in colormap choice. It employs matplotlib's imshow function for rendering. """ plt.figure(figsize=(7, 7)) plt.imshow(image, cmap=cmap) plt.title(title) plt.xticks(fontsize=8) plt.minorticks_on() plt.yticks(fontsize=8) plt.minorticks_on() plt.xlabel('X (px)',fontsize=14) plt.ylabel('Y (px)',fontsize=14) cbar=plt.colorbar() cbar.ax.tick_params(labelsize=7) plt.show()
[docs] def gaussian_1d_offset(x, amplitude, mean, stddev, dc_offset): """ Computes a one-dimensional Gaussian function with an offset, which is commonly used to model distributions of data where a non-zero baseline might be present due to background noise or other baseline shifts. Parameters ---------- x : numpy.ndarray The input values over which the Gaussian function is computed. amplitude : float The peak amplitude of the Gaussian function. This is the height of the peak above the dc_offset. mean : float The position (mean or center) of the peak of the Gaussian curve. stddev : float The standard deviation of the Gaussian function, describing the width of the bell-shaped curve. dc_offset : float A constant value added to the entire function, representing a baseline offset from zero. Returns ------- numpy.ndarray The computed Gaussian function values for each element in x, adjusted by the dc_offset. """ # Precompute constant part of the expression exp_component = -1 / (2 * stddev ** 2) return amplitude * np.exp(exp_component * (x - mean) ** 2) + dc_offset
[docs] def fit_gaussian_1d_acf(acf_values, shifts): """ Fits a Gaussian function to 1D autocorrelation function (ACF) values. This method is typically used to determine 'cluster' sizes in the image data, where the ACF values are indicative of the spatial distribution of pixel intensities. In reality it is a noisy piecewise gaussian function, but this offset allows us to look at different parts of the ACF without such a complicated model. Parameters ---------- Parameters ---------- acf_values : numpy.ndarray The autocorrelation function values obtained by correlating a signal with itself at varying shifts. shifts : numpy.ndarray The corresponding values of pixels at which the autocorrelation was computed. These 'shifts' are used as the x-values for Gaussian fitting. Returns ------- results : dict or None A dictionary containing the results of the Gaussian fitting (parameters and diagnostics) or None if the fitting fails. """ shifts_bottom_quarter = len(shifts)//4 shifts_top_quarter = 3*len(shifts)//4 smoothed_ccf = smooth_array(acf_values) # Smooth CCF values for better fitting max_ccf = max(smoothed_ccf[shifts_bottom_quarter:shifts_top_quarter], key=abs) # Find maximum CCF value for initial guess # Determine initial guess parameters for Gaussian fitting if max_ccf > 0: p0 = [1, 0, 1, 0] # Assume positive peak else: p0 = [-1, 0, 1, 0] # Assume negative peak for inverted Gaussian try: # Perform Gaussian fitting popt, pcov = curve_fit(gaussian_1d_offset, shifts, acf_values, p0=p0) # Compile results into a dictionary results = { 'gaussian_params': popt, 'goodness_of_fit': np.sqrt(np.diag(pcov)), # Standard error of the estimated parameters } except RuntimeError: results = None # Fitting failed return results
[docs] def autocorrelation_analysis(image, roi_mask, lower_limit, upper_limit, micron_resolution, label_flag=False): """ Performs detailed autocorrelation analysis on an image, optionally utilizing a region of interest (ROI), and provides outputs including Gaussian fit parameters for both 1D and 2D analyses. This function calculates the 2D autocorrelation of the image or its ROI, extracts specific regions based on given limits for further analysis, fits Gaussian models to these extracted regions in both 1D (central horizontal slice) and 2D, and plots these results. Parameters ---------- image : numpy.ndarray The image data on which autocorrelation analysis is performed. Should be a 2D numpy array. roi_mask : numpy.ndarray or None A binary mask defining the ROI within the image. If None, the entire image is analyzed. lower_limit : int The lower limit for calculating indices and plot limits in the autocorrelation function. This affects the range of autocorrelation data to be analyzed and visualized. upper_limit : int The upper limit for the same purposes as the lower limit. micron_resolution : float The resolution of the image in microns per pixel. This is used to scale the dimensions of the results in the output, converting pixel measurements to microns. label_flag : bool If False, results of the analysis (such as the 2D autocorrelation function and Gaussian fits) are plotted. If True, plotting is skipped; useful for batch processing or when visualization is not required. Returns ------- acf_values_df : pandas.DataFrame DataFrame of the autocorrelation values, suitable for further analysis or export. fitted_params_df_2d : pandas.DataFrame DataFrame containing the parameters from the 2D Gaussian fit, along with their covariances, and scaled object dimensions in microns. fitted_params_df_1d : pandas.DataFrame DataFrame containing the parameters from the 1D Gaussian fit, along with their covariances, and the scaled object diameter in microns. Notes ----- The function is designed to handle various types of image data and can accommodate different analysis scenarios through its parameters. It integrates several computational steps such as ROI application, autocorrelation computation, Gaussian fitting, and optional visualization, providing a comprehensive tool for image analysis. This method is typically used to determine 'cluster' or (blob like) object sizes in the image data, where the ACF values are indicative of the spatial distribution of pixel intensities. It is useful in quantifying the size of objects which are not well defined in terms of their boundary. For condensates, many kinds of aggregates, clumps, clusters, and other objects, especially in in-cellulo data, are not well defined spherical droplets and therefore this becomes an invaluable analysis tool; particularly useful for 'pre-determining' the size of objects before using other PyCAT methods to segment them. """ # Deprecated analysis step for enhancing image contrast #image -= np.mean(image) # Apply the ROI mask to the image if provided, else use the full image if roi_mask is not None: # Crop the image to the bounding box defined by the mask masked_array, _, _ = crop_bounding_box(image, roi_mask) else: masked_array = image.copy() # Calculate the 2D autocorrelation of the masked or full image acf_values = calculate_autocorrelation(masked_array) acf_range = acf_values.shape # Calculate indices and plot limits for visualizing the autocorrelation x_indices, y_indices, x_plot_limits, y_plot_limits = calculate_indices_and_plot_limits(acf_range, lower_limit, upper_limit) # Extract a specific region from the autocorrelation array based on the calculated indices limited_acf_values = acf_values[x_indices[0]:x_indices[1], y_indices[0]:y_indices[1]] # Get the central horizontal slice of the limited ACF for 1D analysis central_acf_slice = limited_acf_values[limited_acf_values.shape[0] // 2, :] central_x_data = np.linspace(x_indices[0] - acf_range[1] // 2, x_indices[1] - acf_range[1] // 2, limited_acf_values.shape[1]) # Create mesh grids for x and y coordinates in the 2D limited ACF analysis x = np.linspace(x_indices[0] - acf_range[0] // 2, x_indices[1] - acf_range[0] // 2, limited_acf_values.shape[0]) y = np.linspace(y_indices[0] - acf_range[1] // 2, y_indices[1] - acf_range[1] // 2, limited_acf_values.shape[1]) xx, yy = np.meshgrid(x, y) # Define the full width at half maximum (FWHM) conversion factor for Gaussian fits fwhm = 2 * np.sqrt(2 * np.log(2)) scale_factor = micron_resolution * fwhm # Perform Gaussian fitting to the 1D and 2D autocorrelation data acf_1d_results = fit_gaussian_1d_acf(central_acf_slice, central_x_data) acf_2d_results = fit_gaussian_2d(limited_acf_values, xx, yy) # Handle results of 2D Gaussian fitting if acf_2d_results is None: napari_show_warning("2D ACF fit was not successful.") fitted_params_df_2d = pd.DataFrame() else: fitted_params_df_2d = pd.DataFrame({ 'Parameter': ['Amplitude', 'mu_x', 'mu_y', 'sigma_x', 'sigma_y', 'Object Diameter x (um)', 'Object Diameter y (um)'], 'Value': list(acf_2d_results['gaussian_params']) + [scale_factor * acf_2d_results['gaussian_params'][3], scale_factor * acf_2d_results['gaussian_params'][4]], 'Covariance': list(acf_2d_results['goodness_of_fit']) + [np.nan, np.nan] }) if not label_flag: plot_2d_autocorrelation(acf_values, x_plot_limits, y_plot_limits) # Handle results of 1D Gaussian fitting if acf_1d_results is None: napari_show_warning("1D ACF fit was not successful.") fitted_params_df_1d = pd.DataFrame() else: fitted_params_df_1d = pd.DataFrame({ 'Parameter': ['Amplitude', 'mu', 'sigma', 'DC Offset', 'Object Diameter (um)'], 'Value': list(acf_1d_results['gaussian_params']) + [scale_factor * acf_1d_results['gaussian_params'][2]], 'Covariance': list(acf_1d_results['goodness_of_fit']) + [np.nan] }) if not label_flag: plot_1d_auto_correlation(acf_values, x_plot_limits, fitted_params_df_1d) # Create a DataFrame from the autocorrelation values for easy export and analysis acf_values_df = pd.DataFrame(acf_values) return acf_values_df, fitted_params_df_2d, fitted_params_df_1d
[docs] def run_autocorrelation_analysis(image_layer, roi_mask_layer, lower_lim_input, upper_lim_input, data_instance): """ Coordinates the autocorrelation analysis of an image using optional ROI masks and manages the presentation and storage of results within a specified data framework. This function extracts image and ROI mask data, handles GUI inputs for analysis bounds, and performs autocorrelation analysis with proper Gaussian fitting. The results are displayed in a custom dialog and stored for later retrieval. Parameters ---------- image_layer : napari.layers.Image An object containing the image data to be analyzed. Expected to provide access to 2D numpy array data. roi_mask_layer : napari.layers.Labels or None An object containing the ROI mask data, if applicable. If None, no ROI mask is applied. lower_lim_input : QLineEdit (textbox) GUI input element for specifying the lower limit of index calculations, affecting the range of autocorrelation analysis. upper_lim_input : QLineEdit (textbox) GUI input element for specifying the upper limit of index calculations, similar to lower_lim_input. data_instance : object A custom object for managing the storage and retrieval of analysis data, including results of the autocorrelation analysis. Raises ------ ValueError If the image and ROI mask do not have the same shape, preventing proper analysis. Notes ----- This function is capable of handling both labeled and binary ROI masks, adjusting its processing logic accordingly to provide detailed and scalable analysis results. The results are displayed in a dialog for easy access and interpretation.This method is typically used to determine 'cluster' or (blob like) object sizes in the image data. Since there are often objects of various size domains, the function is really a piecewise gaussian, here we recommend using pre-processing and ROI masking to narrow down the analysis to specific objects of interest. However, in the event that is not feasible, we provide the limit inputs to allow the user to focus on specific size regimes of interest. You can run the function, look at the domains in the 1D ACF and then re-run with different limits to focus on specific size regimes. """ # Extract the image and ROI mask data image = image_layer.data roi_mask = roi_mask_layer.data if roi_mask_layer is not None else None # Ensure the image and ROI mask have the same dimensions if roi_mask is not None and image.shape != roi_mask.shape: raise ValueError("Image and ROI mask must have the same shape.") # Convert GUI inputs to integer limits, handling empty inputs as None lower_lim = int(lower_lim_input.text()) if lower_lim_input.text() else None upper_lim = int(upper_lim_input.text()) if upper_lim_input.text() else None label_flag = False # Initialize flag to control the plotting based on label presence # Initialize empty DataFrames to concatenate results for labeled analysis concatenated_fitted_params_df_2d = pd.DataFrame() concatenated_fitted_params_df_1d = pd.DataFrame() # Calculate the physical pixel size from stored data microns_per_pixel = np.sqrt(data_instance.data_repository["microns_per_pixel_sq"]) # Process differently based on the presence and type of ROI mask if roi_mask is not None and np.unique(roi_mask).size > 2: # Labeled mask scenario unique_labels = np.unique(roi_mask)[1:] # Exclude background label for label in unique_labels: specific_roi_mask = (roi_mask == label).astype(bool) label_flag = label > 1 # Run the analysis for each labeled region _, fitted_params_df_2d, fitted_params_df_1d = autocorrelation_analysis(image, specific_roi_mask, lower_lim, upper_lim, microns_per_pixel, label_flag=label_flag) # Concatenate the DataFrame results appropriately if not fitted_params_df_2d.empty: # if fitted_params_df is empty, skip the concatenation if label_flag: concatenated_fitted_params_df_2d = pd.merge(concatenated_fitted_params_df_2d, fitted_params_df_2d, on='Parameter', how='outer', suffixes=('', f'_{label}')) else: concatenated_fitted_params_df_2d = pd.concat([concatenated_fitted_params_df_2d, fitted_params_df_2d], ignore_index=True) if not fitted_params_df_1d.empty: # if fitted_params_df is empty, skip the concatenation if label_flag: concatenated_fitted_params_df_1d = pd.merge(concatenated_fitted_params_df_1d, fitted_params_df_1d, on='Parameter', how='outer', suffixes=('', f'_{label}')) else: concatenated_fitted_params_df_1d = pd.concat([concatenated_fitted_params_df_1d, fitted_params_df_1d], ignore_index=True) else: # Binary mask or no mask scenario label = 1 roi_mask = roi_mask.astype(bool) if roi_mask is not None else None acf_values_df, concatenated_fitted_params_df_2d, concatenated_fitted_params_df_1d = autocorrelation_analysis(image, roi_mask, lower_lim, upper_lim, microns_per_pixel, label_flag=label_flag) # Format and set DataFrame results if not concatenated_fitted_params_df_2d.empty: concatenated_fitted_params_df_2d.set_index('Parameter', inplace=True) concatenated_fitted_params_df_2d = concatenated_fitted_params_df_2d.round(4) else: concatenated_fitted_params_df_2d = None if not concatenated_fitted_params_df_1d.empty: concatenated_fitted_params_df_1d.set_index('Parameter', inplace=True) concatenated_fitted_params_df_1d = concatenated_fitted_params_df_1d.round(4) else: concatenated_fitted_params_df_1d = None # Display the results in a dialog tables_info = [("2D ACF Fitted Gaussian Parameters", concatenated_fitted_params_df_2d), ("1D ACF Fitted Gaussian Parameters", concatenated_fitted_params_df_1d)] window_title = "Auto-Correlation Function Analysis" show_dataframes_dialog(window_title, tables_info) # Add the results to the data instance for storage and retrieval data_instance.data_repository["ACF_2d_fitted_params_df"] = concatenated_fitted_params_df_2d data_instance.data_repository["ACF_1d_fitted_params_df"] = concatenated_fitted_params_df_1d data_instance.data_repository["ACF_values_df"] = acf_values_df