CoolFace
Apppublic

JFoz/test_path_analysis

sourceHugging Facebsd-3-clauseupdated 3y agoView on Hugging Face
0likes
data_preprocess.py396 linesDownload Raw Back to path_analysis
1 2 3from dataclasses import dataclass4import numpy as np5import scipy.linalg as la6from scipy.signal import find_peaks7from math import ceil8 9 10 11 12def thin_peaks(peak_list, dmin=10, voxel_size=(1,1,1), return_larger_peaks=False):13    """14    Remove peaks within a specified distance of each other, retaining the peak with the highest intensity.15 16    Args:17    - peak_list (list of PeakData): Each element contains:18        - pos (list of float): 3D coordinates of the peak.19        - intensity (float): The intensity value of the peak.20        - key (tuple): A unique identifier or index for the peak (#trace, #peak)21    - dmin (float, optional): Minimum distance between peaks. peaks closer than this threshold will be thinned. Defaults to 10.22    - return_larger_peaks (bool, optional): Indicate larger peak for each thinned peak23 24    Returns:25    - list of tuples: A list containing keys of the removed peaks.26    if return_larger_peaks27    - list of tuples: A list containing the keys of the larger peak causing the peak to be removed28 29    Notes:30    - The function uses the L2 norm (Euclidean distance) to compute the distance between peaks.31    - When two peaks are within `dmin` distance, the peak with the lower intensity is removed.32    """33    removed_peaks = []34    removed_larger_peaks = []35    for i in range(len(peak_list)):36        if peak_list[i].key in removed_peaks:37            continue38        for j in range(len(peak_list)):39            if i==j:40                continue41            if peak_list[j].key in removed_peaks:42                continue43            d = (np.array(peak_list[i].pos) - np.array(peak_list[j].pos))*np.array(voxel_size)44            d = la.norm(d)45            if d<dmin:46                hi = peak_list[i].intensity47                hj = peak_list[j].intensity48                if hi<hj:49                    removed_peaks.append(peak_list[i].key)50                    removed_larger_peaks.append(peak_list[j].key)51                    break52                else:53                    removed_peaks.append(peak_list[j].key)54                    removed_larger_peaks.append(peak_list[i].key)55 56    if return_larger_peaks:57        return removed_peaks, removed_larger_peaks58    else:59        return removed_peaks60 61 62@dataclass63class CellData(object):64    """Represents data related to a single cell.65 66    Attributes:67        pathdata_list (list): A list of PathData objects representing the various paths associated with the cell.68    """69    pathdata_list: list70 71@dataclass72class RemovedPeakData(object):73    """Represents data related to a removed peak74 75    Attributes:76        idx (int): Index of peak along path77        screening_peak (tuple): (path_idx, position along path) for screening peak78    """79    idx: int 80    screening_peak: tuple 81 82@dataclass83class PathData(object):84    """Represents data related to a specific path in the cell.85 86    This dataclass encapsulates information about the peaks, 87    the defining points, the fluorescence values, and the path length of a specific path.88 89    Attributes: peaks (list): List of peaks in the path (indicies of positions in points, o_intensity).90        removed_peaks (list): List of peaks in the path which have been removed because of a nearby larger peak91        points (list): List of points defining the path.92        o_intensity (list): List of (unnormalized) fluorescence intensity values along the path93        SC_length (float): Length of the path.94 95    """96    peaks: list97    removed_peaks: list98    points: list99    o_intensity: list100    SC_length: float101 102@dataclass103class PeakData(object):104    pos: tuple105    intensity: float106    key: tuple107 108 109def find_peaks2(v, distance=5,  prominence=0.5):110    """111    Find peaks in a 1D array with extended boundary handling.112 113    The function pads the input array at both ends to handle boundary peaks. It then identifies peaks in the extended array114    and maps them back to the original input array.115 116    Args:117    - v (numpy.ndarray): 1D input array in which to find peaks.118    - distance (int, optional): Minimum number of array elements that separate two peaks. Defaults to 5.119    - prominence (float, optional): Minimum prominence required for a peak to be identified. Defaults to 0.5.120 121    Returns:122    - list of int: List containing the indices of the identified peaks in the original input array.123    - dict: Information about the properties of the identified peaks (as returned by scipy.signal.find_peaks).124 125    """126    pad = int(ceil(distance))+1127    v_ext = np.concatenate([np.ones((pad,), dtype=v.dtype)*np.min(v), v, np.ones((pad,), dtype=v.dtype)*np.min(v)])128 129    assert(len(v_ext) == len(v)+2*pad)130    peaks, _ = find_peaks(v_ext, distance=distance, prominence=prominence)131    peaks = peaks - pad132    n_peaks = []133    for i in peaks:134        if 0<=i<len(v):135            n_peaks.append(i)136        else:137            raise Exception138    return n_peaks, _139          140 141def process_cell_traces(all_paths, path_lengths, measured_trace_fluorescence, dmin=10):142    """143    Process traces of cells to extract peak information and organize the data.144 145    The function normalizes fluorescence data, finds peaks, refines peak information, 146    removes unwanted peaks that might be due to close proximity of bright peaks from 147    other paths, and organizes all the information into a structured data format.148 149    Args:150        all_paths (list of list of tuples): A list containing paths, where each path is 151                                            represented as a list of 3D coordinate tuples.152        path_lengths (list of float): List of path lengths corresponding to the provided paths.153        measured_trace_fluorescence (list of list of float): A list containing fluorescence 154                                                            data corresponding to each path point.155        dmin (float): Distance below which brighter peaks screen less bright ones.156 157    Returns:158        CellData: An object containing organized peak and path data for a given cell.159 160    Note:161        - The function assumes that each path and its corresponding length and fluorescence data 162          are positioned at the same index in their respective lists.163    """164    165    cell_peaks = []166 167    for points, o_intensity in zip(all_paths, measured_trace_fluorescence):168                     169        # For peak determination normalize each trace to have mean zero and s.d. 1170        intensity_normalized = (o_intensity - np.mean(o_intensity))/np.std(o_intensity)171        172        # Find peaks - these will be further refined later173        p,_ = find_peaks2(intensity_normalized, distance=5,  prominence=0.5*np.std(intensity_normalized))174        peaks = np.array(p, dtype=np.int32)175 176        # Store peak data - using original values, not normalized ones177        peak_mean_heights = [ o_intensity[u] for u in peaks ]178        peak_points = [ points[u] for u in peaks ]179        180        cell_peaks.append((peaks, peak_points, peak_mean_heights))181        182    # Eliminate peaks which have another larger peak nearby (in 3D space, on any chromosome).183    # This aims to remove small peaks in the mean intensity generated when an  SC passes close184    # to a bright peak on another SC - this is nearby in space, but brighter.185 186    to_thin = []187    for k in range(len(cell_peaks)):188        for u in range(len(cell_peaks[k][0])):189            to_thin.append(PeakData(pos=cell_peaks[k][1][u], intensity=cell_peaks[k][2][u], key=(k, u)))190    191    # Exclude any peak with a nearby brighter peak (on any SC)192    removed_peaks, removed_larger_peaks = thin_peaks(to_thin, return_larger_peaks=True, dmin=dmin)193 194    # Clean up and remove these peaks195    new_cell_peaks = []196    removed_cell_peaks = []197    removed_cell_peaks_larger = []198    for path_idx in range(len(cell_peaks)):199        path_retained_peaks = []200        path_removed_peaks = []201        path_peaks = cell_peaks[path_idx][0]202 203        for peak_idx in range(len(path_peaks)):204            if (path_idx, peak_idx) not in removed_peaks:205                path_retained_peaks.append(path_peaks[peak_idx])206            else:207                # What's the larger point?208                idx = removed_peaks.index((path_idx, peak_idx))209                larger_path, larger_idx = removed_larger_peaks[idx]        210                path_removed_peaks.append(RemovedPeakData(idx=path_peaks[peak_idx], screening_peak=(larger_path, cell_peaks[larger_path][0][larger_idx])))211                ###212                 213        new_cell_peaks.append(path_retained_peaks)214        removed_cell_peaks.append(path_removed_peaks)215        216    cell_peaks = new_cell_peaks217    pd_list = []218    219    # Save peak positions, absolute intensity intensities, and length for each SC220    for k in range(len(all_paths)):221        222        points, o_intensity = all_paths[k], measured_trace_fluorescence[k]223 224        peaks = cell_peaks[k]225        removed_peaks = removed_cell_peaks[k]226        227        pd = PathData(peaks=peaks, removed_peaks=removed_peaks, points=points, o_intensity=o_intensity, SC_length=path_lengths[k])228        pd_list.append(pd)229 230    cd = CellData(pathdata_list=pd_list)231 232    return cd233 234 235alpha_max = 0.4236 237 238# Criterion used for identifying peak as a focus - normalized (with mean and s.d.)239# intensity levels being above 0.4 time maximum peak level240def focus_criterion(pos, v, alpha=alpha_max):241    """242    Identify and return positions where values in the array `v` exceed a certain threshold.243 244    The threshold is computed as `alpha` times the maximum value in `v`.245 246    Args:247    - pos (numpy.ndarray): Array of positions.248    - v (numpy.ndarray): 1D array of values, e.g., intensities.249    - alpha (float, optional): A scaling factor for the threshold. Defaults to `alpha_max`.250 251    Returns:252    - numpy.ndarray: Array of positions where corresponding values in `v` exceed the threshold.253    """254    if len(v):255        idx = (v>=alpha*np.max(v))256        return np.array(pos[idx])257    else:258        return np.array([], dtype=np.int32)259 260def analyse_celldata(cell_data, config):261    """262    Analyse the provided cell data to extract focus-related information.263 264    Args:265        cd (CellData): An instance of the CellData class containing path data information.266        config (dictionary): Configuration dictionary containing 'peak_threshold' and 'threshold_type'267                             'peak_threshold' (float) - threshold for calling peaks as foci268                             'threshold_type' (str) = 'per-trace', 'per-foci'269 270    Returns:271        tuple: A tuple containing:272            - foci_rel_intensity (list): List of relative intensities for the detected foci.273            - foci_pos (list): List of absolute positions of the detected foci.274            - foci_pos_index (list): List of indices of the detected foci.275            - screened_foci_data (list): List of RemovedPeakData indicating positions of removed peaks and the index of the larger peak276            - trace_median_intensities (list): Per-trace median intensity277            - trace_thresholds (list): Per-trace absolute threshold for calling peaks as foci278    """279    foci_abs_intensity = []280    foci_pos = []281    foci_pos_index = []282    screened_foci_data = []283    trace_median_intensities = []284    trace_thresholds = []285    286    peak_threshold = config['peak_threshold']287 288    threshold_type = config['threshold_type']289 290    if threshold_type == 'per-trace':291        """292        Call extracted peaks as foci if intensity - trace_mean > peak_threshold * (trace_max_foci_intensity - trace_mean)293        """294        295        for path_data in cell_data.pathdata_list:296            peaks = np.array(path_data.peaks, dtype=np.int32)297 298            # Normalize extracted fluorescent intensities by subtracting mean (and dividing299            # by standard deviation - note that the latter should have no effect on the results).300            h = np.array(path_data.o_intensity)301            h = h - np.mean(h)302            h = h/np.std(h)303            # Extract foci according to criterion304            foci_idx = focus_criterion(peaks, h[peaks], peak_threshold) 305            306            #307            removed_peaks = path_data.removed_peaks308            removed_peaks_idx = np.array([u.idx for u in removed_peaks], dtype=np.int32)309 310            311            if len(peaks):312                trace_thresholds.append((1-peak_threshold)*np.mean(path_data.o_intensity) + peak_threshold*np.max(np.array(path_data.o_intensity)[peaks]))313            else:314                trace_thresholds.append(None)315 316            if len(removed_peaks):317                if len(peaks):318                    threshold = (1-peak_threshold)*np.mean(path_data.o_intensity) + peak_threshold*np.max(np.array(path_data.o_intensity)[peaks])319                else:320                    threshold = float('-inf')321 322                323                removed_peak_heights = np.array(path_data.o_intensity)[removed_peaks_idx]324                screened_foci_idx = np.where(removed_peak_heights>threshold)[0]325                326                screened_foci_data.append([removed_peaks[i] for i in screened_foci_idx])327            else:328                screened_foci_data.append([])329                330            pos_abs = (foci_idx/len(path_data.points))*path_data.SC_length331            foci_pos.append(pos_abs)332            foci_abs_intensity.append(np.array(path_data.o_intensity)[foci_idx])333            334            foci_pos_index.append(foci_idx)335            trace_median_intensities.append(np.median(path_data.o_intensity))336            337    elif threshold_type == 'per-cell':338        """339        Call extracted peaks as foci if intensity - trace_mean > peak_threshold * max(intensity - trace_mean)340        """341        max_cell_intensity = float("-inf")342        for path_data in cell_data.pathdata_list:343 344            # Normalize extracted fluorescent intensities by subtracting mean (and dividing345            # by standard deviation - note that the latter should have no effect on the results).346            h = np.array(path_data.o_intensity)347            h = h - np.mean(h)348            max_cell_intensity = max(max_cell_intensity, np.max(h))349 350        for path_data in cell_data.pathdata_list:351            peaks = np.array(path_data.peaks, dtype=np.int32)352 353            # Normalize extracted fluorescent intensities by subtracting mean (and dividing354            # by standard deviation - note that the latter should have no effect on the results).355            h = np.array(path_data.o_intensity)356            h = h - np.mean(h)357 358            foci_idx = peaks[h[peaks]>peak_threshold*max_cell_intensity]359 360            removed_peaks = path_data.removed_peaks361            removed_peaks_idx = np.array([u.idx for u in removed_peaks], dtype=np.int32)362 363            trace_thresholds.append(np.mean(path_data.o_intensity) + peak_threshold*max_cell_intensity)364 365            if len(removed_peaks):366                threshold = np.mean(path_data.o_intensity) + peak_threshold*max_cell_intensity367 368                removed_peak_heights = np.array(path_data.o_intensity)[removed_peaks_idx]369                screened_foci_idx = np.where(removed_peak_heights>threshold)[0]370                371                screened_foci_data.append([removed_peaks[i] for i in screened_foci_idx])372            else:373                screened_foci_data.append([])374 375            pos_abs = (foci_idx/len(path_data.points))*path_data.SC_length376            foci_pos.append(pos_abs)377            foci_abs_intensity.append(np.array(path_data.o_intensity)[foci_idx])378            379            foci_pos_index.append(foci_idx)380            trace_median_intensities.append(np.median(path_data.o_intensity))            381            382    else:383        raise NotImplementedError384    385    return foci_abs_intensity, foci_pos, foci_pos_index, screened_foci_data, trace_median_intensities, trace_thresholds386 387def analyse_traces(all_paths, path_lengths, measured_trace_fluorescence, config):388    389    cd = process_cell_traces(all_paths, path_lengths, measured_trace_fluorescence, dmin=config['screening_distance'])390 391    return analyse_celldata(cd, config)392 393    394 395 396