"""Functions for simulating power spectra."""
import numpy as np
from specparam.utils.checks import check_iter
from specparam.sim.params import collect_sim_params
from specparam.sim.gen import gen_freqs, gen_power_vals, gen_rotated_power_vals
from specparam.sim.transform import compute_rotation_offset
from specparam.modutils.docs import (docs_get_section, replace_docstring_sections,
docs_replace_param)
###################################################################################################
###################################################################################################
[docs]
def sim_power_spectrum(freq_range, aperiodic_params, periodic_params,
nlv=0.005, freq_res=0.5, f_rotation=None, return_params=False):
"""Simulate a power spectrum.
Parameters
----------
freq_range : list of [float, float]
Frequency range to simulate power spectrum across, as [f_low, f_high], inclusive.
aperiodic_params : dict of {str : array}
Mode definition and parameters to create the aperiodic component of a power spectrum.
Should be organized as {mode : params}, where `mode` is a string label for a mode to
simulate with and `params` is a set of parameter values of length aperiodic_mode.n_params.
periodic_params : dict of {str : list of float or list of list of float}
Mode definition and parameters to create the periodic component of a power spectrum.
Should be organized as {mode : params}, where `mode` is a string label for a mode to
simulate with and `params` corresponds to the number of desired peaks, with a total number
of values matching periodic_mode.n_params * n_peaks.
This can can be a flat list (ex: [10, 1, 1, 20, 0.5, 1]) or be grouped into a
list of lists (ex: [[10, 1, 1], [20, 0.5, 1]]).
nlv : float, optional, default: 0.005
Noise level to add to generated power spectrum.
freq_res : float, optional, default: 0.5
Frequency resolution for the simulated power spectrum.
f_rotation : float, optional
Frequency value, in Hz, to rotate around. Should only be set if spectrum is to be rotated.
Can only be used with `powerlaw` aperiodic mode.
return_params : bool, optional, default: False
Whether to return the parameters for the simulated spectrum.
Returns
-------
freqs : 1d array
Frequency values, in linear spacing.
powers : 1d array
Power values, in linear spacing.
sim_params : SimParams
Definition of parameters used to create the spectrum.
Only returned if `return_params` is True.
Notes
-----
- See `check_modes` for more information on the available modes to use to simulate,
as well as descriptions of the parameters for each mode.
Rotating Power Spectra:
- For the powerlaw aperiodic component only, you can optionally specify a rotation frequency,
such that power spectra will be simulated and rotated around that point to the specified
aperiodic exponent. Any power spectra simulated with the same rotation frequency will relate
to each other by having the specified rotation point.
- Note that rotating power spectra changes the offset. If a simulated spectrum is rotated,
the returned spectrum will NOT have the offset of the input parameters, and will instead
have offset value required to create the given aperiodic exponent with the requested
rotation point. If you return SimParams, the recorded offset will be the calculated offset
of the data post rotation, and not the entered value.
Examples
--------
Generate a power spectrum with a single peak, at 10 Hz:
>>> freqs, powers = sim_power_spectrum([1, 50], {'fixed' : [0, 2]},
... {'gaussian' : [10, 0.5, 1]})
Generate a power spectrum with alpha and beta peaks:
>>> freqs, powers = sim_power_spectrum([1, 50], {'fixed' : [0, 2]},
... {'gaussian' : [[10, 0.5, 1], [20, 0.5, 1]]})
Generate a power spectrum, that was rotated around a particular frequency point:
>>> freqs, powers = sim_power_spectrum([1, 50], {'fixed' : [None, 2]},
... {'gaussian' : [10, 0.5, 1]}, f_rotation=15)
"""
freqs = gen_freqs(freq_range, freq_res)
if f_rotation:
powers = gen_rotated_power_vals(freqs, list(aperiodic_params.values())[0],
list(periodic_params.values())[0], nlv, f_rotation)
# The rotation changes the offset, so recalculate it's value & update params
new_offset = compute_rotation_offset(list(aperiodic_params.values())[0][1], f_rotation)
aperiodic_params = [new_offset, list(aperiodic_params.values())[0][1]]
else:
powers = gen_power_vals(freqs, *list(*aperiodic_params.items()),
*list(*periodic_params.items()), nlv)
if return_params:
sim_params = collect_sim_params(aperiodic_params, periodic_params, nlv)
return freqs, powers, sim_params
else:
return freqs, powers
[docs]
def sim_group_power_spectra(n_spectra, freq_range, aperiodic_params, periodic_params,
nlvs=0.005, freq_res=0.5, f_rotation=None,
return_params=False):
"""Simulate multiple power spectra.
Parameters
----------
n_spectra : int
The number of power spectra to generate.
freq_range : list of [float, float]
Frequency range to simulate power spectra across, as [f_low, f_high], inclusive.
aperiodic_params : dict of {str : array}
Mode definition and parameters to create the aperiodic components of the power spectra.
Should be organized as {mode : params}, where `mode` is a string label for a mode to
simulate with and `params` (array_like or generator) defines the aperiodic parameters.
periodic_params : dict of {str : list of float or list of list of float}
Mode definition and parameters to create the periodic components of the power spectras.
Should be organized as {mode : params}, where `mode` is a string label for a mode to
simulate with and `params` (array_like or generator) defines the periodic parameters.
nlvs : float or list of float or generator, optional, default: 0.005
Noise level to add to generated power spectrum.
freq_res : float, optional, default: 0.5
Frequency resolution for the simulated power spectra.
f_rotation : float, optional
Frequency value, in Hz, to rotate around. Should only be set if spectrum is to be rotated.
Can only be used with `powerlaw` aperiodic mode.
See additional notes in `sim_power_spectra` on rotating simulated power spectra.
return_params : bool, optional, default: False
Whether to return the parameters for the simulated spectra.
Returns
-------
freqs : 1d array
Frequency values, in linear spacing.
powers : 2d array
Matrix of power values, in linear spacing, as [n_power_spectra, n_freqs].
sim_params : list of SimParams
Definitions of parameters used for each spectrum. Has length of n_spectra.
Only returned if `return_params` is True.
Notes
-----
Parameters options can be:
- A single set of parameters.
If so, these same parameters are used for all spectra.
- A list of parameters whose length is n_spectra.
If so, each successive parameter set is such for each successive spectrum.
- A generator object that returns parameters for a power spectrum.
If so, each spectrum has parameters sampled from the generator.
Examples
--------
Generate 2 power spectra using the same parameters:
>>> freqs, powers = sim_group_power_spectra(2, [1, 50], {'fixed' : [0, 2]},
... {'gaussian' : [10, 0.5, 1]})
Generate 10 power spectra, randomly sampling possible parameters:
>>> from specparam.sim.params import param_sampler
>>> ap_opts = param_sampler([[0, 1.0], [0, 1.5], [0, 2]])
>>> pe_opts = param_sampler([[], [10, 0.5, 1], [10, 0.5, 1, 20, 0.25, 1]])
>>> freqs, powers = sim_group_power_spectra(10, [1, 50],
... {'fixed' : ap_opts}, {'gaussian' : pe_opts})
Generate 5 power spectra, rotated around 20 Hz:
>>> ap_params = [[None, 1], [None, 1.25], [None, 1.5], [None, 1.75], [None, 2]]
>>> pe_params = [10, 0.5, 1]
>>> freqs, powers = sim_group_power_spectra(5, [1, 50], {'fixed' : ap_params},
... {'gaussian' : pe_params}, f_rotation=20)
Generate power spectra stepping across exponent values, and return parameter values:
>>> from specparam.sim.params import Stepper, param_iter
>>> ap_params = param_iter([0, Stepper(1, 2, 0.25)])
>>> pe_params = [10, 0.5, 1]
>>> freqs, powers, sps = sim_group_power_spectra(5, [1, 50], {'fixed' : ap_params},
... {'gaussian' : pe_params}, return_params=True)
"""
# Initialize things
freqs = gen_freqs(freq_range, freq_res)
powers = np.zeros([n_spectra, len(freqs)])
sim_params = [None] * n_spectra
# Check if inputs are generators, if not, make them into repeat generators
ap_params = check_iter(list(aperiodic_params.values())[0], n_spectra)
pe_params = check_iter(list(periodic_params.values())[0], n_spectra)
nlvs = check_iter(nlvs, n_spectra)
f_rots = check_iter(f_rotation, n_spectra)
# Get the mode definitions
ap_mode = list(aperiodic_params.keys())[0]
pe_mode = list(periodic_params.keys())[0]
# Simulate power spectra
for ind, ap, pe, nlv, f_rot in zip(range(n_spectra), ap_params, pe_params, nlvs, f_rots):
if f_rotation:
powers[ind, :] = gen_rotated_power_vals(freqs, ap, pe, nlv, f_rot)
aperiodic_params = [compute_rotation_offset(ap[1], f_rot), ap[1]]
else:
powers[ind, :] = gen_power_vals(freqs, ap_mode, ap, pe_mode, pe, nlv)
sim_params[ind] = collect_sim_params({ap_mode : ap}, {pe_mode : pe}, nlv)
if return_params:
return freqs, powers, sim_params
else:
return freqs, powers
[docs]
@replace_docstring_sections(\
docs_replace_param(docs_get_section(\
sim_group_power_spectra.__doc__, 'Parameters'),
'n_spectra', 'n_windows : int\n The number of time windows to generate.'))
def sim_spectrogram(n_windows, freq_range, aperiodic_params, periodic_params,
nlvs=0.005, freq_res=0.5, f_rotation=None, return_params=False):
"""Simulate spectrogram.
Parameters
----------
% copied in from `sim_group_power_spectra`
Returns
-------
freqs : 1d array
Frequency values, in linear spacing.
spectrogram : 2d array
Matrix of power values, in linear spacing, as [n_freqs, n_windows].
sim_params : list of SimParams
Definitions of parameters used for each spectrum. Has length of n_spectra.
Only returned if `return_params` is True.
Notes
-----
This function simulates spectra for the spectrogram using `sim_group_power_spectra`.
See `sim_group_power_spectra` for details on the parameters.
"""
outputs = sim_group_power_spectra(n_windows, freq_range, aperiodic_params, periodic_params,
nlvs, freq_res, f_rotation, return_params)
outputs = list(outputs)
outputs[1] = outputs[1].T
return outputs