Source code for pykrait.trace_analysis.peak_analysis

from scipy.signal import find_peaks as scipy_find_peaks
from scipy.signal import peak_widths
from sklearn.preprocessing import normalize
import numpy as np
import pandas as pd
from typing import Tuple

[docs] def find_peaks( peak_min_width: int, peak_max_width: int, peak_prominence: float, peak_min_height: float, detrended_timeseries: np.ndarray, ) -> np.ndarray: """wrapper function for scipy's find_peaks function :param peak_min_width: minimum width at half-maximum of peak in frames :type peak_min_width: int :param peak_max_width: maximum width at half-maxumim of peak in frames :type peak_max_width: int :param peak_prominence: minimum prominence of peak :type peak_prominence: float :param min_peak_height: minimum height of peak :type min_peak_height: float :param detrended_timeseries: the timeseries to find the peaks on :type detrended_timeseries: ndarray :return: returns a (T x n_roi) np.ndarray with 1s at the peak locations and 0s elsewhere :rtype: np.ndarray """ peak_series = np.zeros(detrended_timeseries.shape) for i in range(0, detrended_timeseries.shape[1]): peaks, _ = scipy_find_peaks( detrended_timeseries[:, i], width=(peak_min_width, peak_max_width), height=peak_min_height, prominence=peak_prominence, ) peak_series[peaks, i] = 1 return peak_series
[docs] def calculate_normalized_peaks(peaks: np.ndarray, frame_interval: float) -> float: """ calculates the normalized peaks (peaks per 100 cells per 10 minutes) :param peaks: binary array of shape (T x n_cells) with 1s at the peak locations and 0s elsewhere :type peaks: np.ndarray :param frame_interval: frame interval in seconds :type frame_interval: float :return: normalized peaks per 100 cells per 10 minutes :rtype: float """ n_frames, n_cells = peaks.shape n_peaks = np.sum(peaks) normalized_peaks = (n_peaks / (n_cells / 100)) / ( (n_frames * frame_interval) / 600 ) # normalize to 100 cells and 10 minutes of recording return normalized_peaks
[docs] def create_peak_properties(peak_series:np.ndarray, detrended_timeseries:np.ndarray, frame_interval:float) -> Tuple[pd.DataFrame, np.ndarray]: """ analyzes properties of the identified peaks such as FWHM, rise time, decay time, AUC and creates a numpy array with arranged peak traces for visualization :param peak_series: binary array of shape (T x n_cells) with 1s at the peak locations and 0s elsewhere :type peak_series: np.ndarray :param detrended_timeseries: the detrended timeseries :type detrended_timeseries: np.ndarray :param frame_interval: frame interval in seconds :type frame_interval: float :return: returns a dataframe with peak properties and a numpy array with arranged peak traces :rtype: Tuple[pd.DataFrame, np.ndarray] """ peak_frame_length = 70 peak_counts = int(np.sum(peak_series, axis=None)) peak_frame = np.zeros((peak_frame_length, peak_counts)) # create a numpy array to hold the # 0 = cell index # 1 = peak number # 2 = peak_loc # 3 = peak_dist # 4 = FWHM # 5 = rise2080 # 6 = maxrise # 7 = decay8020 # 8 = maxdecay # 9 = AUC peak_df = np.zeros((peak_counts, 10)) column_names = ["cell_index", "peak_number", "peak_loc", "peak_dist", "FWHM", "rise2080", "maxrise", "decay8020", "maxdecay", "AUC"] peak_counter = 0 # iterate over every cell for cell_index in range(0, peak_series.shape[1]): # if cell has peaks if np.sum(peak_series[:,cell_index]) > 0: # find the peak indeces peak_indices = np.nonzero(peak_series[:,cell_index])[0] # flatten the timeseries time_series = detrended_timeseries[:,cell_index].flatten() # calculate full-width half maximum widths, width_height, left_FWHM,right_FWHM = peak_widths(x = time_series, peaks = peak_indices, rel_height = 0.5) # for the 20%-80% rise time and the 80%-20% decay time, calculate the indices # Quick Note: scipy.signal.peak_widths has weird settings for rel_height: # "1.0 calculates the width of the peak at its lowest contour line while 0.5 # evaluates at half the prominence height" # therefore 0.8 == 20% # and 0.2 == 80% _, _, left_20, right_20 = peak_widths(x = time_series, peaks = peak_indices, rel_height = 0.8) _, _, left_80, right_80 = peak_widths(x = time_series, peaks = peak_indices, rel_height = 0.2) # iterate over every peak of the cell for peak_number in range(0, len(widths)): #set cell index, peak index, peak location peak_df[peak_counter, 0] = cell_index peak_df[peak_counter, 1] = peak_number peak_df[peak_counter, 2] = peak_indices[peak_number] peak_df[peak_counter, 4] = widths[peak_number] * frame_interval # calculate the rise time (time from 20% to 80%) peak_df[peak_counter, 5] = (left_80[peak_number] - left_20[peak_number]) * frame_interval # calculate the decay time (time from 80% back to 20%) peak_df[peak_counter, 7] = (right_20[peak_number] - right_80[peak_number]) * frame_interval # calculate the AUC of the 20%-Interval using the normalized curve left_limit_AUC = int(np.floor(left_20[peak_number])) right_limit_AUC = int(np.ceil(right_20[peak_number])) # some sanity checks if (right_limit_AUC - left_limit_AUC > 0) and (left_limit_AUC > 0) and (right_limit_AUC < detrended_timeseries.shape[1]-1): AUC = np.trapezoid(y = normalize([detrended_timeseries[left_limit_AUC:right_limit_AUC,cell_index]], norm = "max"), dx = frame_interval)[0] peak_df[peak_counter, 9] = AUC # for the plot of arranged peaks: # set boundaries for the plot left_limit = int(np.floor(left_FWHM[peak_number])) - 30 right_limit = int(np.floor(left_FWHM[peak_number])) + 40 # if the boundaries do not clash with the start and end of the timeseries if left_limit > 0 and right_limit < detrended_timeseries.shape[0]-1: # normalize the trace to be between -1 and 1 example_trace = normalize([detrended_timeseries[left_limit:right_limit, cell_index]], norm = "max") peak_frame[:,peak_counter] = example_trace # increase peak counter to go to next row in dataframe peak_counter = peak_counter + 1 # clean up, only keep frames of non-zero cells # Find rows where all elements are zero non_zero_rows = np.any(peak_frame != 0, axis=0) # Filter the array to remove rows with all zeros peak_frame_nonzero = peak_frame[:,non_zero_rows] # turn the numpy array of peak properties into a pandas dataframe peak_properties_df = pd.DataFrame(data = peak_df, columns = column_names) return peak_properties_df, peak_frame_nonzero