Source code for piel.analysis.signals.time.core.off_state

import numpy as np
from typing import Callable, Optional, Dict
from piel.types import (
    TimeSignalData,
)  # Ensure this import matches your project structure


# Existing create_off_state_generator function
[docs] def create_off_state_generator( noise_std: float = 0.01, sampling_rate: float = 1000.0, baseline: float = 0.0, data_name: str = "off_state", data_time_signal_kwargs: Optional[Dict] = None, ) -> Callable[[float, Optional[int]], TimeSignalData]: """ Creates a generator function for the equivalent off state signal with noise. Parameters: noise_std (float): Standard deviation of the Gaussian noise. sampling_rate (float): Sampling rate in Hz. baseline (float): Baseline signal level for the off state. data_name (str): Name of the data signal. data_time_signal_kwargs (dict, optional): Additional keyword arguments for TimeSignalData. Returns: Callable[[float, Optional[int]], TimeSignalData]: A function that takes duration_s (in seconds) and returns TimeSignalData. """ if data_time_signal_kwargs is None: data_time_signal_kwargs = {} def generate_off_state( duration_s: float, num_samples: Optional[int] = None ) -> TimeSignalData: """ Generates the off state signal data with noise for a given duration_s. Parameters: duration_s (float): Duration of the signal in seconds. num_samples (float): Number of samples to generate. Returns: TimeSignalData: The generated signal data. """ if num_samples is None: num_samples = int(duration_s * sampling_rate) time_s = np.linspace(0, duration_s, num_samples, endpoint=False) noise = np.random.normal(loc=0.0, scale=noise_std, size=num_samples) data = baseline + noise return TimeSignalData( time_s=time_s.tolist(), data=data.tolist(), data_name=data_name, **data_time_signal_kwargs, ) return generate_off_state
# New function to extract parameters and create generator
[docs] def extract_off_state_generator_from_off_state_section( off_state_data: TimeSignalData, data_name: Optional[str] = None, data_time_signal_kwargs: Optional[Dict] = None, ) -> Callable[[float], TimeSignalData]: """ Extracts parameters from an existing off state DataTimeSignalData and creates a generator function. Parameters: off_state_data (TimeSignalData): The existing off state signal data. data_name (str, optional): Name for the new data signal. Defaults to the original data_name. data_time_signal_kwargs (dict, optional): Additional keyword arguments for DataTimeSignalData. Returns: Callable[[float], DataTimeSignalData]: A generator function configured with extracted parameters. """ if data_time_signal_kwargs is None: data_time_signal_kwargs = {} # Extract baseline as the mean of the data baseline = np.mean(off_state_data.data) # Extract noise standard deviation as the standard deviation of the data noise_std = np.std(off_state_data.data) # Extract sampling rate from time_s array if len(off_state_data.time_s) < 2: raise ValueError( "time_s array must contain at least two elements to calculate sampling rate." ) # Calculate the sampling interval (assuming uniform sampling) sampling_intervals = np.diff(off_state_data.time_s) mean_sampling_interval = np.mean(sampling_intervals) sampling_rate = 1.0 / mean_sampling_interval # Use the provided data_name or default to the original new_data_name = data_name if data_name is not None else off_state_data.data_name # Optionally, pass through units or other kwargs # For example, override units if provided in data_time_signal_kwargs return create_off_state_generator( noise_std=noise_std, sampling_rate=sampling_rate, baseline=baseline, data_name=new_data_name, data_time_signal_kwargs=data_time_signal_kwargs, )
[docs] def extract_off_state_generator_from_full_state_data( full_time_signal_data: TimeSignalData, baseline: Optional[float] = None, threshold: Optional[float] = None, min_duration_s: Optional[float] = None, sampling_rate: Optional[float] = None, data_name: Optional[str] = None, data_time_signal_kwargs: Optional[Dict] = None, ) -> Callable[[float, Optional[int]], TimeSignalData]: """ Extracts parameters from an existing off state DataTimeSignalData and creates a generator function. Parameters: full_time_signal_data (TimeSignalData): The input signal data containing multiple states. baseline (float, optional): The baseline value representing the off state. If not provided, it is computed as the mean of the data. threshold (float, optional): The maximum deviation from the baseline to consider as off state. If not provided, it is computed as 2 * standard deviation of the data. min_duration_s (float, optional): The minimum duration_s (in seconds) for a segment to be considered. Segments shorter than this duration_s will be ignored. sampling_rate (float, optional): The sampling rate in Hz. If not provided, it is calculated from time_s. data_time_signal_kwargs (dict, optional): Additional keyword arguments for DataTimeSignalData. data_name (str, optional): Name for the new data signal. Defaults to the original data_name. data_time_signal_kwargs (dict, optional): Additional keyword arguments for DataTimeSignalData. Returns: Callable[[float], DataTimeSignalData]: A generator function configured with extracted parameters. """ off_state_data = extract_off_state_section( full_time_signal_data=full_time_signal_data, baseline=baseline, threshold=threshold, min_duration_s=min_duration_s, sampling_rate=sampling_rate, ) if data_time_signal_kwargs is None: data_time_signal_kwargs = {} # Extract baseline as the mean of the data baseline = np.mean(off_state_data.data) # Extract noise standard deviation as the standard deviation of the data noise_std = np.std(off_state_data.data) # Extract sampling rate from time_s array if len(off_state_data.time_s) < 2: raise ValueError( "time_s array must contain at least two elements to calculate sampling rate." ) # Calculate the sampling interval (assuming uniform sampling) sampling_intervals = np.diff(off_state_data.time_s) mean_sampling_interval = np.mean(sampling_intervals) sampling_rate = 1.0 / mean_sampling_interval # Use the provided data_name or default to the original new_data_name = data_name if data_name is not None else off_state_data.data_name # Optionally, pass through units or other kwargs # For example, override units if provided in data_time_signal_kwargs return create_off_state_generator( noise_std=noise_std, sampling_rate=sampling_rate, baseline=baseline, data_name=new_data_name, data_time_signal_kwargs=data_time_signal_kwargs, )
[docs] def extract_off_state_section( full_time_signal_data: TimeSignalData, baseline: Optional[float] = None, threshold: Optional[float] = None, min_duration_s: Optional[float] = None, sampling_rate: Optional[float] = None, data_time_signal_kwargs: Optional[Dict] = None, ) -> TimeSignalData: """ Extracts the off state segments from a DataTimeSignalData instance containing multiple on and off states. Parameters: full_time_signal_data (TimeSignalData): The input signal data containing multiple states. baseline (float, optional): The baseline value representing the off state. If not provided, it is computed as the mean of the data. threshold (float, optional): The maximum deviation from the baseline to consider as off state. If not provided, it is computed as 2 * standard deviation of the data. min_duration_s (float, optional): The minimum duration_s (in seconds) for a segment to be considered. Segments shorter than this duration_s will be ignored. sampling_rate (float, optional): The sampling rate in Hz. If not provided, it is calculated from time_s. data_time_signal_kwargs (dict, optional): Additional keyword arguments for DataTimeSignalData. Returns: TimeSignalData: A new DataTimeSignalData instance containing only the off state segments. """ if data_time_signal_kwargs is None: data_time_signal_kwargs = {} data = np.array(full_time_signal_data.data) time_s = np.array(full_time_signal_data.time_s) # Compute baseline if not provided if baseline is None: baseline = np.mean(data) # Compute threshold if not provided if threshold is None: threshold = 2 * np.std(data) # Determine sampling rate if not provided if sampling_rate is None: if len(time_s) < 2: raise ValueError( "time_s array must contain at least two elements to calculate sampling rate." ) sampling_intervals = np.diff(time_s) mean_sampling_interval = np.mean(sampling_intervals) sampling_rate = 1.0 / mean_sampling_interval # Identify points within the threshold around the baseline off_state_mask = np.abs(data - baseline) <= threshold # Find continuous segments where off_state_mask is True off_state_indices = np.where(off_state_mask)[0] if off_state_indices.size == 0: raise ValueError("No off state segments found based on the provided criteria.") # Group consecutive indices segments: list[list[int]] = [] current_segment = [off_state_indices[0]] for idx in off_state_indices[1:]: if idx == current_segment[-1] + 1: current_segment.append(idx) else: segments.append(current_segment) current_segment = [idx] segments.append(current_segment) # Add the last segment # Filter segments by min_duration_s if specified if min_duration_s is not None: min_samples = int(min_duration_s * sampling_rate) segments = [seg for seg in segments if len(seg) >= min_samples] if not segments: raise ValueError( "No off state segments meet the minimum duration_s requirement." ) # Concatenate all segments extracted_time = [] extracted_data = [] for seg in segments: extracted_time.extend(time_s[seg]) extracted_data.extend(data[seg]) # Optionally, sort the extracted data by time sorted_indices = np.argsort(extracted_time) extracted_time = np.array(extracted_time)[sorted_indices].tolist() extracted_data = np.array(extracted_data)[sorted_indices].tolist() # Create a new TimeSignalData instance extracted_off_state = TimeSignalData( time_s=extracted_time, data=extracted_data, data_name=full_time_signal_data.data_name + "_off_state", **data_time_signal_kwargs, ) return extracted_off_state