Beispiel #1
0
def get_sim_funcs(module_name):
    """Get the available sim functions from a specified sub-module.

    Parameters
    ----------
    module_name : {'periodic', 'aperiodic', 'cycles', 'transients', 'combined'}
        Simulation sub-module to get sim functions from.

    Returns
    -------
    funcs : dictionary
        A dictionary containing the available sim functions from the requested sub-module.
    """

    check_param_options(module_name, 'module_name', SIM_MODULES)

    # Note: imports done within function to avoid circular import
    from neurodsp.sim import periodic, aperiodic, transients, combined, cycles

    module = eval(module_name)

    funcs = {name : func for name, func in getmembers(module, isfunction) \
        if name[0:4] == 'sim_' and func.__module__.split('.')[-1] == module.__name__.split('.')[-1]}

    return funcs
Beispiel #2
0
def compute_spectrum(sig, fs, method='welch', avg_type='mean', **kwargs):
    """Compute the power spectral density of a time series.

    Parameters
    ----------
    sig : 1d or 2d array
        Time series.
    fs : float
        Sampling rate, in Hz.
    method : {'welch', 'wavelet', 'medfilt'}, optional
        Method to use to estimate the power spectrum.
    avg_type : {'mean', 'median'}, optional
        If relevant, the method to average across windows to create the spectrum.
    **kwargs
        Keyword arguments to pass through to the function that calculates the spectrum.

    Returns
    -------
    freqs : 1d array
        Frequencies at which the measure was calculated.
    spectrum : 1d or 2d array
        Power spectral density.

    Examples
    --------
    Compute the power spectrum of a simulated time series:

    >>> from neurodsp.sim import sim_combined
    >>> sig = sim_combined(n_seconds=10, fs=500,
    ...                    components={'sim_powerlaw': {}, 'sim_oscillation' : {'freq': 10}})
    >>> freqs, spectrum = compute_spectrum(sig, fs=500)
    """

    check_param_options(method, 'method', ['welch', 'wavelet', 'medfilt'])

    if method == 'welch':
        return compute_spectrum_welch(sig, fs, avg_type=avg_type, **kwargs)

    elif method == 'wavelet':
        return compute_spectrum_wavelet(sig, fs, avg_type=avg_type, **kwargs)

    elif method == 'medfilt':
        return compute_spectrum_medfilt(sig, fs, **kwargs)
Beispiel #3
0
def phase_shift_cycle(cycle, shift):
    """Phase shift a simulated cycle time series.

    Parameters
    ----------
    cycle : 1d array
        Cycle values to apply a rotation shift to.
    shift : float or {'min', 'max'}
        If non-zero, applies a phase shift by rotating the cycle.
        If a float, the shift is defined as a relative proportion of cycle, between [0, 1].
        If 'min' or 'max', the cycle is shifted to start at it's minima or maxima.

    Returns
    -------
    cycle : 1d array
        Rotated cycle.

    Examples
    --------
    Phase shift a simulated sine wave cycle:

    >>> cycle = sim_cycle(n_seconds=0.5, fs=500, cycle_type='sine')
    >>> shifted_cycle = phase_shift_cycle(cycle, shift=0.5)
    """

    if isinstance(shift, (float, int)):
        check_param_range(shift, 'shift', [0., 1.])
    else:
        check_param_options(shift, 'shift', ['min', 'max'])

    if shift == 'min':
        shift = np.argmin(cycle)
    elif shift == 'max':
        shift = np.argmax(cycle)
    else:
        shift = int(np.round(shift * len(cycle)))

    indices = range(shift, shift + len(cycle))
    cycle = cycle.take(indices, mode='wrap')

    return cycle
Beispiel #4
0
def get_avg_func(avg_type):
    """Select a function to use for averaging.

    Parameters
    ----------
    avg_type : {'mean', 'median'}
        The type of averaging function to use.

    Returns
    -------
    avg_func : callable
        Requested averaging function.
    """

    check_param_options(avg_type, 'avg_type', ['mean', 'median'])

    if avg_type == 'mean':
        avg_func = np.mean
    elif avg_type == 'median':
        avg_func = np.median

    return avg_func
Beispiel #5
0
def detect_bursts_dual_threshold(sig,
                                 fs,
                                 dual_thresh,
                                 f_range=None,
                                 min_n_cycles=3,
                                 min_burst_duration=None,
                                 avg_type='median',
                                 magnitude_type='amplitude',
                                 **filter_kwargs):
    """Detect bursts in a signal using the dual threshold algorithm.

    Parameters
    ----------
    sig : 1d array
        Time series.
    fs : float
        Sampling rate, in Hz.
    dual_thresh : tuple of (float, float)
        Low and high threshold values for burst detection.
        Units are normalized by the average signal magnitude.
    f_range : tuple of (float, float), optional
        Frequency range, to filter signal to, before running burst detection.
        If f_range is None, then no filtering is applied prior to running burst detection.
    min_n_cycles : float, optional, default: 3
        Minimum burst duration in to keep.
        Only used if `f_range` is defined, and is used as the number of cycles at f_range[0].
    min_burst_duration : float, optional, default: None
        Minimum length of a burst, in seconds. Must be defined if not filtering.
        Only used if `f_range` is not defined, or if `min_n_cycles` is set to None.
    avg_type : {'median', 'mean'}, optional
        Averaging method to use to normalize the magnitude that is used for thresholding.
    magnitude_type : {'amplitude', 'power'}, optional
        Metric of magnitude used for thresholding.
    **filter_kwargs
        Keyword parameters to pass to `filter_signal`.

    Returns
    -------
    is_burst : 1d array
        Boolean indication of where bursts are present in the input signal.
        True indicates that a burst was detected at that sample, otherwise False.

    Notes
    -----
    The dual-threshold burst detection algorithm was originally proposed in [1]_.

    References
    ----------
    .. [1] Feingold, J., Gibson, D. J., DePasquale, B., & Graybiel, A. M. (2015).
           Bursts of beta oscillation differentiate postperformance activity in
           the striatum and motor cortex of monkeys performing movement tasks.
           Proceedings of the National Academy of Sciences, 112(44), 13687–13692.
           DOI: https://doi.org/10.1073/pnas.1517629112

    Examples
    --------
    Detect bursts using the dual threshold algorithm:

    >>> from neurodsp.sim import sim_combined
    >>> sig = sim_combined(n_seconds=10, fs=500,
    ...                    components={'sim_synaptic_current': {},
    ...                                'sim_bursty_oscillation' : {'freq': 10}},
    ...                    component_variances=[0.1, 0.9])
    >>> is_burst = detect_bursts_dual_threshold(sig, fs=500, dual_thresh=(1, 2), f_range=(8, 12))
    """

    if len(dual_thresh) != 2:
        raise ValueError(
            "Invalid number of elements in 'dual_thresh' parameter")

    # Compute amplitude time series
    sig_magnitude = amp_by_time(sig,
                                fs,
                                f_range,
                                remove_edges=False,
                                **filter_kwargs)

    # Set magnitude as power or amplitude: square if power, leave as is if amplitude
    check_param_options(magnitude_type, 'magnitude_type',
                        ['amplitude', 'power'])
    if magnitude_type == 'power':
        sig_magnitude = sig_magnitude**2

    # Calculate normalized magnitude
    sig_magnitude = sig_magnitude / get_avg_func(avg_type)(sig_magnitude)

    # Identify time periods of bursting using the 2 thresholds
    is_burst = _dual_threshold_split(sig_magnitude, dual_thresh[1],
                                     dual_thresh[0])

    # Remove bursts detected that are too short
    # Use a number of cycles defined on the frequency range, if available
    if f_range is not None and min_n_cycles is not None:
        min_burst_samples = int(np.ceil(min_n_cycles * fs / f_range[0]))
    # Otherwise, make sure minimum duration is set, and use that
    else:
        if min_burst_duration is None:
            raise ValueError(
                "Minimum burst duration must be defined if not filtering "
                "and using a number of cycles threshold.")
        min_burst_samples = int(np.ceil(min_burst_duration * fs))

    is_burst = _rmv_short_periods(is_burst, min_burst_samples)

    return is_burst.astype(bool)
Beispiel #6
0
def compute_fluctuations(sig,
                         fs,
                         n_scales=10,
                         min_scale=0.01,
                         max_scale=1.0,
                         deg=1,
                         method='dfa'):
    """Compute a fluctuation analysis on a signal.

    Parameters
    ----------
    sig : 1d array
        Time series.
    fs : float
        Sampling rate, in Hz.
    n_scales : int, optional, default=10
        Number of scales to estimate fluctuations over.
    min_scale : float, optional, default=0.01
        Shortest scale to compute over, in seconds.
    max_scale : float, optional, default=1.0
        Longest scale to compute over, in seconds.
    deg : int, optional, default=1
        Polynomial degree for detrending. Only used for DFA.

        - 1 for regular DFA
        - 2 or higher for generalized DFA
    method : {'dfa', 'rs'}
        Method to use to compute fluctuations:

        - 'dfa' : detrended fluctuation
        - 'rs' : rescaled range

    Returns
    -------
    t_scales : 1d array
        Time-scales over which fluctuation measures were computed.
    fluctuations : 1d array
        Average fluctuation at each scale.
    exp : float
        Slope of line in log-log when plotting time scales against fluctuations.
        This is the alpha value for DFA, or the Hurst exponent for rescaled range.

    Notes
    -----
    These analyses compute fractal properties by analyzing fluctuations across windows.

    Overall, these approaches involve dividing the time-series into non-overlapping
    windows at log-spaced scales and computing a fluctuation measure across windows.
    The relationship of this fluctuation measure across window sizes provides
    information on the fractal properties of a signal.

    Available measures are:

    - DFA: detrended fluctuation analysis
        - computes ordinary least squares fits across signal windows
    - RS: rescaled range
        - computes the range of signal windows, divided by the standard deviation
    """

    check_param_options(method, 'method', ['dfa', 'rs'])

    # Get log10 equi-spaced scales and translate that into window lengths
    t_scales = np.logspace(np.log10(min_scale), np.log10(max_scale), n_scales)
    win_lens = np.round(t_scales * fs).astype('int')

    # Check that all window sizes are fit-able
    if np.any(win_lens <= 1):
        raise ValueError("Some of window sizes are too small to run. "
                         "Try updating `min_scale` to a value that works "
                         "better for the current sampling rate.")

    # Step through each scale and measure fluctuations
    fluctuations = np.zeros_like(t_scales)
    for idx, win_len in enumerate(win_lens):

        if method == 'dfa':
            fluctuations[idx] = compute_detrended_fluctuation(sig,
                                                              win_len=win_len,
                                                              deg=deg)
        elif method == 'rs':
            fluctuations[idx] = compute_rescaled_range(sig, win_len=win_len)

    # Calculate the relationship between between fluctuations & time scales
    exp = np.polyfit(np.log10(t_scales), np.log10(fluctuations), deg=1)[0]

    return t_scales, fluctuations, exp
Beispiel #7
0
def convolve_wavelet(sig,
                     fs,
                     freq,
                     n_cycles=7,
                     scaling=0.5,
                     wavelet_len=None,
                     norm='sss'):
    """Convolve a signal with a complex wavelet.

    Parameters
    ----------
    sig : 1d array
        Time series to filter.
    fs : float
        Sampling rate, in Hz.
    freq : float
        Center frequency of bandpass filter.
    n_cycles : float, optional, default: 7
        Length of the filter, as the number of cycles of the oscillation with specified frequency.
    scaling : float, optional, default: 0.5
        Scaling factor for the morlet wavelet.
    wavelet_len : int, optional
        Length of the wavelet. If defined, this overrides the freq and n_cycles inputs.
    norm : {'sss', 'amp'}, optional
        Normalization method:

        * 'sss' - divide by the square root of the sum of squares
        * 'amp' - divide by the sum of amplitudes

    Returns
    -------
    array
        Complex time series.

    Notes
    -----

    * The real part of the returned array is the filtered signal.
    * Taking np.abs() of output gives the analytic amplitude.
    * Taking np.angle() of output gives the analytic phase.

    Examples
    --------
    Convolve a complex wavelet with a simulated signal:

    >>> from neurodsp.sim import sim_combined
    >>> sig = sim_combined(n_seconds=10, fs=500,
    ...                    components={'sim_powerlaw': {}, 'sim_oscillation' : {'freq': 10}})
    >>> cts = convolve_wavelet(sig, fs=500, freq=10)
    """

    check_param_options(norm, 'norm', ['sss', 'amp'])

    if wavelet_len is None:
        wavelet_len = int(n_cycles * fs / freq)

    if wavelet_len > sig.shape[-1]:
        raise ValueError(
            'The length of the wavelet is greater than the signal. Can not proceed.'
        )

    morlet_f = morlet(wavelet_len, w=n_cycles, s=scaling)

    if norm == 'sss':
        morlet_f = morlet_f / np.sqrt(np.sum(np.abs(morlet_f)**2))
    elif norm == 'amp':
        morlet_f = morlet_f / np.sum(np.abs(morlet_f))

    mwt_real = np.convolve(sig, np.real(morlet_f), mode='same')
    mwt_imag = np.convolve(sig, np.imag(morlet_f), mode='same')

    return mwt_real + 1j * mwt_imag
Beispiel #8
0
def filter_signal(sig,
                  fs,
                  pass_type,
                  f_range,
                  filter_type='fir',
                  n_cycles=3,
                  n_seconds=None,
                  remove_edges=True,
                  butterworth_order=None,
                  print_transitions=False,
                  plot_properties=False,
                  return_filter=False):
    """Apply a bandpass, bandstop, highpass, or lowpass filter to a neural signal.

    Parameters
    ----------
    sig : 1d or 2d array
        Time series to be filtered.
    fs : float
        Sampling rate, in Hz.
    pass_type : {'bandpass', 'bandstop', 'lowpass', 'highpass'}
        Which kind of filter to apply:

        * 'bandpass': apply a bandpass filter
        * 'bandstop': apply a bandstop (notch) filter
        * 'lowpass': apply a lowpass filter
        * 'highpass' : apply a highpass filter
    f_range : tuple of (float, float) or float
        Cutoff frequency(ies) used for filter, specified as f_lo & f_hi.
        For 'bandpass' & 'bandstop', must be a tuple.
        For 'lowpass' or 'highpass', can be a float that specifies pass frequency, or can be
        a tuple and is assumed to be (None, f_hi) for 'lowpass', and (f_lo, None) for 'highpass'.
    n_cycles : float, optional, default: 3
        Length of filter, in number of cycles, at the 'f_lo' frequency, if using an FIR filter.
        This parameter is overwritten by `n_seconds`, if provided.
    n_seconds : float, optional
        Length of filter, in seconds, if using an FIR filter.
        This parameter overwrites `n_cycles`.
    filter_type : {'fir', 'iir'}, optional
        Whether to use an FIR or IIR filter.
        The only IIR filter offered is a butterworth filter.
    remove_edges : bool, optional, default: True
        If True, replace samples within half the kernel length to be np.nan.
        Only used for FIR filters.
    butterworth_order : int, optional
        Order of the butterworth filter, if using an IIR filter.
        See input 'N' in scipy.signal.butter.
    print_transitions : bool, optional, default: True
        If True, print out the transition and pass bandwidths.
    plot_properties : bool, optional, default: False
        If True, plot the properties of the filter, including frequency response and/or kernel.
    return_filter : bool, optional, default: False
        If True, return the filter coefficients.

    Returns
    -------
    sig_filt : 1d array
        Filtered time series.
    kernel : 1d array or tuple of (1d array, 1d array)
        Filter coefficients. Only returned if `return_filter` is True.

    Examples
    --------
    Apply an FIR band pass filter to a signal, for the range of 1 to 25 Hz:

    >>> from neurodsp.sim import sim_combined
    >>> sig = sim_combined(n_seconds=10, fs=500,
    ...                    components={'sim_powerlaw': {}, 'sim_oscillation' : {'freq': 10}})
    >>> filt_sig = filter_signal(sig, fs=500, pass_type='bandpass',
    ...                          filter_type='fir', f_range=(1, 25))
    """

    check_param_options(filter_type, 'filter_type', ['fir', 'iir'])

    if filter_type.lower() == 'fir':
        return filter_signal_fir(sig, fs, pass_type, f_range, n_cycles,
                                 n_seconds, remove_edges, print_transitions,
                                 plot_properties, return_filter)

    elif filter_type.lower() == 'iir':
        _iir_checks(n_seconds, butterworth_order, remove_edges)
        return filter_signal_iir(sig, fs, pass_type, f_range,
                                 butterworth_order, print_transitions,
                                 plot_properties, return_filter)
Beispiel #9
0
def sim_bursty_oscillation(n_seconds,
                           fs,
                           freq,
                           burst_def='prob',
                           burst_params=None,
                           cycle='sine',
                           phase=0,
                           **cycle_params):
    """Simulate a bursty oscillation.

    Parameters
    ----------
    n_seconds : float
        Simulation time, in seconds.
    fs : float
        Sampling rate of simulated signal, in Hz.
    freq : float
        Oscillation frequency, in Hz.
    burst_def : {'prob', 'durations'} or 1d array
        Which approach to take to define the bursts:

        - 'prob' : simulate bursts based on probabilities of entering and leaving bursts states.
        - 'durations' : simulate bursts based on lengths of bursts and inter-burst periods.
        - 1d array: use the given array as a definition of the bursts

    burst_params : dict
        Parameters for the burst definition approach.

        For the `prob` approach:

            enter_burst : float, optional, default: 0.2
                Probability of a cycle being oscillating given the last cycle is not oscillating.
            leave_burst : float, optional, default: 0.2
                Probability of a cycle not being oscillating given the last cycle is oscillating.

        For the `durations` approach:

            n_cycles_burst : int
                The number of cycles within each burst.
            n_cycles_off
                The number of non-bursting cycles, between bursts.
    cycle : {'sine', 'asine', 'sawtooth', 'gaussian', 'exp', '2exp', 'exp_cos', 'asym_harmonic'}
        What type of oscillation cycle to simulate.
        See `sim_cycle` for details on cycle types and parameters.
    phase : float or {'min', 'max'}, optional, default: 0
        If non-zero, applies a phase shift to the oscillation by rotating the cycle.
        If a float, the shift is defined as a relative proportion of cycle, between [0, 1].
        If 'min' or 'max', the cycle is shifted to start at it's minima or maxima.
    **cycle_params
        Parameters for the simulated oscillation cycle.

    Returns
    -------
    sig : 1d array
        Simulated bursty oscillation.

    Notes
    -----
    This function takes a 'tiled' approach to simulating cycles, with evenly spaced
    and consistent cycles across the whole signal, that are either oscillating or not.

    If the cycle length does not fit evenly into the simulated data length,
    then the last few samples will be non-oscillating.

    Examples
    --------
    Simulate a probabilistic bursty oscillation, with a low probability of bursting:

    >>> sig = sim_bursty_oscillation(n_seconds=10, fs=500, freq=5,
    ...                              burst_params={'enter_burst' : 0.2, 'leave_burst' : 0.8})

    Simulate a probabilistic bursty sawtooth oscillation, with a high probability of bursting:

    >>> sig = sim_bursty_oscillation(n_seconds=10, fs=500, freq=5, burst_def='prob',
    ...                              burst_params = {'enter_burst' : 0.8, 'leave_burst' : 0.4},
    ...                              cycle='sawtooth', width=0.3)

    Simulate a bursty oscillation, with specified durations:

    >>> sig = sim_bursty_oscillation(n_seconds=10, fs=500, freq=10, burst_def='durations',
    ...                              burst_params={'n_cycles_burst' : 3, 'n_cycles_off' : 3})
    """

    if isinstance(burst_def, str):
        check_param_options(burst_def, 'burst_def', ['prob', 'durations'])

    # Consistency fix: catch old parameters, and remap into burst_params
    #   This preserves the prior default values, and makes the old API work the same
    burst_params = {} if not burst_params else burst_params
    for burst_param in ['enter_burst', 'leave_burst']:
        temp = cycle_params.pop(burst_param, 0.2)
        if burst_def == 'prob' and burst_param not in burst_params:
            burst_params[burst_param] = temp

    # Simulate a normalized cycle to use for bursts
    n_seconds_cycle = 1 / freq
    osc_cycle = sim_normalized_cycle(n_seconds_cycle,
                                     fs,
                                     cycle,
                                     phase=phase,
                                     **cycle_params)

    # Calculate the number of cycles needed to tile the full signal
    n_cycles = int(np.floor(n_seconds * freq))

    # Determine which periods will be oscillating
    if isinstance(burst_def, np.ndarray):
        is_oscillating = burst_def
    elif burst_def == 'prob':
        is_oscillating = make_is_osc_prob(n_cycles, **burst_params)
    elif burst_def == 'durations':
        is_oscillating = make_is_osc_durations(n_cycles, **burst_params)

    sig = make_bursts(n_seconds, fs, is_oscillating, osc_cycle)

    return sig
Beispiel #10
0
def sim_asine_cycle(n_seconds, fs, rdsym, side='both'):
    """Simulate a cycle of an asymmetric sine wave.

    Parameters
    ----------
    n_seconds : float
        Length of cycle window in seconds.
        Note that this is NOT the period of the cycle, but the length of the returned array
        that contains the cycle, which can be (and usually is) much shorter.
    fs : float
        Sampling frequency of the cycle simulation.
    rdsym : float
        Rise-decay symmetry of the cycle, as fraction of the period in the rise time, where:
        = 0.5 - symmetric (sine wave)
        < 0.5 - shorter rise, longer decay
        > 0.5 - longer rise, shorter decay
    side : {'both', 'peak', 'trough'}
        Which side of the cycle to make asymmetric.

    Returns
    -------
    cycle : 1d array
        Simulated asymmetric cycle.

    Examples
    --------
    Simulate a 2 Hz asymmetric sine cycle:

    >>> cycle = sim_asine_cycle(n_seconds=0.5, fs=500, rdsym=0.75)
    """

    check_param_range(rdsym, 'rdsym', [0., 1.])
    check_param_options(side, 'side', ['both', 'peak', 'trough'])

    # Determine number of samples
    n_samples = compute_nsamples(n_seconds, fs)
    half_sample = int(n_samples / 2)

    # Check for an odd number of samples (for half peaks, we need to fix this later)
    remainder = n_samples % 2

    # Calculate number of samples rising
    n_rise = int(np.round(n_samples * rdsym))
    n_rise1 = int(np.ceil(n_rise / 2))
    n_rise2 = int(np.floor(n_rise / 2))

    # Calculate number of samples decaying
    n_decay = n_samples - n_rise
    n_decay1 = half_sample - n_rise1

    # Create phase definition for cycle with both extrema being asymmetric
    if side == 'both':

        phase = np.hstack([
            np.linspace(0, np.pi / 2, n_rise1 + 1),
            np.linspace(np.pi / 2, -np.pi / 2, n_decay + 1)[1:-1],
            np.linspace(-np.pi / 2, 0, n_rise2 + 1)[:-1]
        ])

    # Create phase definition for cycle with only one extrema being asymmetric
    elif side == 'peak':

        half_sample += 1 if bool(remainder) else 0
        phase = np.hstack([
            np.linspace(0, np.pi / 2, n_rise1 + 1),
            np.linspace(np.pi / 2, np.pi, n_decay1 + 1)[1:-1],
            np.linspace(-np.pi, 0, half_sample + 1)[:-1]
        ])

    elif side == 'trough':

        half_sample -= 1 if not bool(remainder) else 0
        phase = np.hstack([
            np.linspace(0, np.pi, half_sample + 1)[:-1],
            np.linspace(-np.pi, -np.pi / 2, n_decay1 + 1),
            np.linspace(-np.pi / 2, 0, n_rise1 + 1)[:-1]
        ])

    # Convert phase definition to signal
    cycle = np.sin(phase)

    return cycle
Beispiel #11
0
def compute_scv_rs(sig, fs, window='hann', nperseg=None, noverlap=0,
                   method='bootstrap', rs_params=None):
    """Compute a resampled version of the spectral coefficient of variation (SCV).

    Parameters
    ----------
    sig : 1d array
        Time series of measurement values.
    fs : float
        Sampling rate, in Hz.
    window : str or tuple or array_like, optional, default: 'hann'
        Desired window to use. See scipy.signal.get_window for a list of available windows.
        If array_like, the array will be used as the window and its length must be nperseg.
    nperseg : int, optional
        Length of each segment, in number of samples.
        If None, and window is str or tuple, is set to 1 second of data.
        If None, and window is array_like, is set to the length of the window.
    noverlap : int, optional, default: 0
        Number of points to overlap between segments.
    method : {'bootstrap', 'rolling'}, optional
        Method of resampling:

        * 'bootstrap' randomly samples a subset of the spectrogram repeatedly.
        * 'rolling' takes the rolling window scv.
    rs_params : tuple, (int, int), optional
        Parameters for resampling algorithm, depending on the method used:

        * If 'bootstrap', rs_params = (n_slices, n_draws), defaults to (10% of slices, 100 draws).
        * If 'rolling', rs_params = (n_slices, n_steps), defaults to (10, 5).

        Where:

        * `n_slices` is the number of slices per draw
        * `n_draws` is the number of random draws
        * `n_steps` is the number of slices to step forward.

    Returns
    -------
    freqs : 1d array
        Frequencies at which the measure was calculated.
    t_inds : 1d array or None
        Time indices at which the measure was calculated.
        This is only returned for 'rolling' resampling. If 'bootstrap', t_inds = None.
    scv_rs : 2d array
        Resampled spectral coefficient of variation.

    Notes
    -----
    In the resampled version, instead of a single estimate of mean and standard deviation,
    the spectrogram is resampled.

    Resampling can be done either randomly (method='bootstrap') or in a time-stepped
    manner (method='rolling').

    Examples
    --------
    Compute the resampled spectral coefficient of variation, using the bootstrap method:

    >>> from neurodsp.sim import sim_combined
    >>> sig = sim_combined(n_seconds=10, fs=500,
    ...                    components={'sim_powerlaw': {}, 'sim_oscillation' : {'freq': 10}})
    >>> freqs, t_inds, scv_rs = compute_scv_rs(sig, fs=500, method='bootstrap')
    """

    check_param_options(method, 'method', ['bootstrap', 'rolling'])
    nperseg, noverlap = check_spg_settings(fs, window, nperseg, noverlap)

    # Compute spectrogram of data
    freqs, ts, spg = spectrogram(sig, fs, window, nperseg, noverlap)

    if method == 'bootstrap':

        # Params: number of slices of STFT to compute SCV over & number of draws
        #   Defaults to draw 1/10 of STFT slices, 100 draws
        if rs_params is None:
            rs_params = (int(spg.shape[1] / 10.), 100)

        nslices, ndraws = rs_params
        scv_rs = np.zeros((len(freqs), ndraws))

        # Repeated sub-sampling of spectrogram randomly, with replacement between draws
        for draw in range(ndraws):
            idx = np.random.choice(spg.shape[1], size=nslices, replace=False)
            scv_rs[:, draw] = np.std(
                spg[:, idx], axis=-1) / np.mean(spg[:, idx], axis=-1)

        t_inds = None  # no time component, return nothing

    elif method == 'rolling':

        # Params: number of slices of STFT to compute SCV over & number of slices to roll forward
        #   Defaults to 10 STFT slices, move forward by 5 slices
        if rs_params is None:
            rs_params = (10, 5)

        nslices, nsteps = rs_params
        outlen = int(np.ceil((spg.shape[1] - nslices) / float(nsteps))) + 1
        scv_rs = np.zeros((len(freqs), outlen))
        for ind in range(outlen):
            curblock = spg[:, nsteps * ind:nslices + nsteps * ind]
            scv_rs[:, ind] = np.std(
                curblock, axis=-1) / np.mean(curblock, axis=-1)

        # Grab the time indices from the spectrogram
        t_inds = ts[0::nsteps]

    return freqs, t_inds, scv_rs