def plot_spectra(freqs, power_spectra, log_freqs=False, log_powers=False, colors=None, labels=None, ax=None, **plot_kwargs): """Plot one or multiple power spectra. Parameters ---------- freqs : 1d or 2d array or list of 1d array Frequency values, to be plotted on the x-axis. power_spectra : 1d or 2d array or list of 1d array Power values, to be plotted on the y-axis. log_freqs : bool, optional, default: False Whether to plot the frequency axis in log spacing. log_powers : bool, optional, default: False Whether to plot the power axis in log spacing. colors : list of str, optional, default: None Line colors of the spectra. labels : list of str, optional, default: None Legend labels for the spectra. ax : matplotlib.Axes, optional Figure axes upon which to plot. **plot_kwargs Keyword arguments to pass into the ``style_plot``. """ ax = check_ax(ax, plot_kwargs.pop('figsize', PLT_FIGSIZES['spectral'])) # Create the plot plot_kwargs = check_plot_kwargs(plot_kwargs, {'linewidth': 2.0}) # Make inputs iterable if need to be passed multiple times to plot each spectrum plt_powers = np.reshape(power_spectra, (1, -1)) if np.ndim(power_spectra) == 1 else \ power_spectra plt_freqs = repeat(freqs) if isinstance( freqs, np.ndarray) and freqs.ndim == 1 else freqs # Set labels labels = plot_kwargs.pop('label') if 'label' in plot_kwargs.keys( ) and labels is None else labels labels = repeat(labels) if not isinstance(labels, list) else cycle(labels) colors = repeat(colors) if not isinstance(colors, list) else cycle(colors) # Plot for freqs, powers, color, label in zip(plt_freqs, plt_powers, colors, labels): # Set plot data, logging if requested, and collect color, if absent freqs = np.log10(freqs) if log_freqs else freqs powers = np.log10(powers) if log_powers else powers if color: plot_kwargs['color'] = color ax.plot(freqs, powers, label=label, **plot_kwargs) style_spectrum_plot(ax, log_freqs, log_powers)
def plot_spectra_shading(freqs, power_spectra, shades, shade_colors='r', add_center=False, ax=None, **plot_kwargs): """Plot one or multiple power spectra with a shaded frequency region (or regions). Parameters ---------- freqs : 1d or 2d array or list of 1d array Frequency values, to be plotted on the x-axis. power_spectra : 1d or 2d array or list of 1d array Power values, to be plotted on the y-axis. shades : list of [float, float] or list of list of [float, float] Shaded region(s) to add to plot, defined as [lower_bound, upper_bound]. shade_colors : str or list of string Color(s) to plot shades. add_center : bool, optional, default: False Whether to add a line at the center point of the shaded regions. ax : matplotlib.Axes, optional Figure axes upon which to plot. **plot_kwargs Keyword arguments to pass into :func:`~.plot_spectra`. Notes ----- Parameters for `plot_spectra` can also be passed into this function as keyword arguments. This includes `log_freqs`, `log_powers` & `labels`. See `plot_spectra` for usage details. """ ax = check_ax(ax, plot_kwargs.pop('figsize', PLT_FIGSIZES['spectral'])) plot_spectra(freqs, power_spectra, ax=ax, **plot_kwargs) add_shades(ax, shades, shade_colors, add_center, plot_kwargs.get('log_freqs', False)) style_spectrum_plot(ax, plot_kwargs.get('log_freqs', False), plot_kwargs.get('log_powers', False))
def plot_spectral_error(freqs, error, shade=None, log_freqs=False, ax=None, **plot_kwargs): """Plot frequency by frequency error values. Parameters ---------- freqs : 1d array Frequency values, to be plotted on the x-axis. error : 1d array Calculated error values or mean error values across frequencies, to plot on the y-axis. shade : 1d array, optional Values to shade in around the plotted error. This could be, for example, the standard deviation of the errors. log_freqs : bool, optional, default: False Whether to plot the frequency axis in log spacing. ax : matplotlib.Axes, optional Figure axes upon which to plot. **plot_kwargs Keyword arguments to pass into the ``style_plot``. """ ax = check_ax(ax, plot_kwargs.pop('figsize', PLT_FIGSIZES['spectral'])) plt_freqs = np.log10(freqs) if log_freqs else freqs plot_spectra(plt_freqs, error, ax=ax, linewidth=3) if np.any(shade): ax.fill_between(plt_freqs, error - shade, error + shade, alpha=0.25) ymin, ymax = ax.get_ylim() if ymin < 0: ax.set_ylim([0, ymax]) ax.set_xlim(plt_freqs.min(), plt_freqs.max()) style_spectrum_plot(ax, log_freqs, True) ax.set_ylabel('Absolute Error')
def plot_fm(fm, plot_peaks=None, plot_aperiodic=True, plt_log=False, add_legend=True, save_fig=False, file_name=None, file_path=None, ax=None, data_kwargs=None, model_kwargs=None, aperiodic_kwargs=None, peak_kwargs=None, **plot_kwargs): """Plot the power spectrum and model fit results from a FOOOF object. Parameters ---------- fm : FOOOF Object containing a power spectrum and (optionally) results from fitting. plot_peaks : None or {'shade', 'dot', 'outline', 'line'}, optional What kind of approach to take to plot peaks. If None, peaks are not specifically plotted. Can also be a combination of approaches, separated by '-', for example: 'shade-line'. plot_aperiodic : boolean, optional, default: True Whether to plot the aperiodic component of the model fit. plt_log : boolean, optional, default: False Whether to plot the frequency values in log10 spacing. add_legend : boolean, optional, default: False Whether to add a legend describing the plot components. save_fig : bool, optional, default: False Whether to save out a copy of the plot. file_name : str, optional Name to give the saved out file. file_path : str, optional Path to directory to save to. If None, saves to current directory. ax : matplotlib.Axes, optional Figure axes upon which to plot. data_kwargs, model_kwargs, aperiodic_kwargs, peak_kwargs : None or dict, optional Keyword arguments to pass into the plot call for each plot element. **plot_kwargs Keyword arguments to pass into the ``style_plot``. Notes ----- Since FOOOF objects store power values in log spacing, the y-axis (power) is plotted in log spacing by default. """ ax = check_ax(ax, PLT_FIGSIZES['spectral']) # Log settings - note that power values in FOOOF objects are already logged log_freqs = plt_log log_powers = False # Plot the data, if available if fm.has_data: data_defaults = { 'color': PLT_COLORS['data'], 'linewidth': 2.0, 'label': 'Original Spectrum' if add_legend else None } data_kwargs = check_plot_kwargs(data_kwargs, data_defaults) plot_spectra(fm.freqs, fm.power_spectrum, log_freqs, log_powers, ax=ax, **data_kwargs) # Add the full model fit, and components (if requested) if fm.has_model: model_defaults = { 'color': PLT_COLORS['model'], 'linewidth': 3.0, 'alpha': 0.5, 'label': 'Full Model Fit' if add_legend else None } model_kwargs = check_plot_kwargs(model_kwargs, model_defaults) plot_spectra(fm.freqs, fm.fooofed_spectrum_, log_freqs, log_powers, ax=ax, **model_kwargs) # Plot the aperiodic component of the model fit if plot_aperiodic: aperiodic_defaults = { 'color': PLT_COLORS['aperiodic'], 'linewidth': 3.0, 'alpha': 0.5, 'linestyle': 'dashed', 'label': 'Aperiodic Fit' if add_legend else None } aperiodic_kwargs = check_plot_kwargs(aperiodic_kwargs, aperiodic_defaults) plot_spectra(fm.freqs, fm._ap_fit, log_freqs, log_powers, ax=ax, **aperiodic_kwargs) # Plot the periodic components of the model fit if plot_peaks: _add_peaks(fm, plot_peaks, plt_log, ax, peak_kwargs) # Apply default style to plot style_spectrum_plot(ax, log_freqs, True)
def plot_spectra_yshade(freqs, power_spectra, shade='std', average='mean', scale=1, log_freqs=False, log_powers=False, color=None, label=None, ax=None, **plot_kwargs): """Plot standard deviation or error as a shaded region around the mean spectrum. Parameters ---------- freqs : 1d array Frequency values, to be plotted on the x-axis. power_spectra : 1d or 2d array Power values, to be plotted on the y-axis. ``shade`` must be provided if 1d. shade : 'std', 'sem', 1d array or callable, optional, default: 'std' Approach for shading above/below the mean spectrum. average : 'mean', 'median' or callable, optional, default: 'mean' Averaging approach for the average spectrum to plot. Only used if power_spectra is 2d. scale : int, optional, default: 1 Factor to multiply the plotted shade by. log_freqs : bool, optional, default: False Whether to plot the frequency axis in log spacing. log_powers : bool, optional, default: False Whether to plot the power axis in log spacing. color : str, optional, default: None Line color of the spectrum. label : str, optional, default: None Legend label for the spectrum. ax : matplotlib.Axes, optional Figure axes upon which to plot. **plot_kwargs Keyword arguments to be passed to `plot_spectra` or to the plot call. """ if (isinstance(shade, str) or isfunction(shade)) and power_spectra.ndim != 2: raise ValueError('Power spectra must be 2d if shade is not given.') ax = check_ax(ax, plot_kwargs.pop('figsize', PLT_FIGSIZES['spectral'])) # Set plot data & labels, logging if requested plt_freqs = np.log10(freqs) if log_freqs else freqs plt_powers = np.log10(power_spectra) if log_powers else power_spectra # Organize mean spectrum to plot avg_funcs = {'mean': np.mean, 'median': np.median} if isinstance(average, str) and plt_powers.ndim == 2: avg_powers = avg_funcs[average](plt_powers, axis=0) elif isfunction(average) and plt_powers.ndim == 2: avg_powers = average(plt_powers) else: avg_powers = plt_powers # Plot average power spectrum ax.plot(plt_freqs, avg_powers, linewidth=2.0, color=color, label=label) # Organize shading to plot shade_funcs = {'std': np.std, 'sem': sem} if isinstance(shade, str): shade_vals = scale * shade_funcs[shade](plt_powers, axis=0) elif isfunction(shade): shade_vals = scale * shade(plt_powers) else: shade_vals = scale * shade upper_shade = avg_powers + shade_vals lower_shade = avg_powers - shade_vals # Plot +/- yshading around spectrum alpha = plot_kwargs.pop('alpha', 0.25) ax.fill_between(plt_freqs, lower_shade, upper_shade, alpha=alpha, color=color, **plot_kwargs) style_spectrum_plot(ax, log_freqs, log_powers)
def plot_annotated_peak_search(fm): """Plot a series of plots illustrating the peak search from a flattened spectrum. Parameters ---------- fm : FOOOF FOOOF object, with model fit, data and settings available. """ # Recalculate the initial aperiodic fit and flattened spectrum that # is the same as the one that is used in the peak fitting procedure flatspec = fm.power_spectrum - \ gen_aperiodic(fm.freqs, fm._robust_ap_fit(fm.freqs, fm.power_spectrum)) # Calculate ylims of the plot that are scaled to the range of the data ylims = [ min(flatspec) - 0.1 * np.abs(min(flatspec)), max(flatspec) + 0.1 * max(flatspec) ] # Loop through the iterative search for each peak for ind in range(fm.n_peaks_ + 1): # This forces the creation of a new plotting axes per iteration ax = check_ax(None, PLT_FIGSIZES['spectral']) plot_spectra(fm.freqs, flatspec, ax=ax, linewidth=2.5, label='Flattened Spectrum', color=PLT_COLORS['data']) plot_spectra(fm.freqs, [fm.peak_threshold * np.std(flatspec)] * len(fm.freqs), ax=ax, label='Relative Threshold', color='orange', linewidth=2.5, linestyle='dashed') plot_spectra(fm.freqs, [fm.min_peak_height] * len(fm.freqs), ax=ax, label='Absolute Threshold', color='red', linewidth=2.5, linestyle='dashed') maxi = np.argmax(flatspec) ax.plot(fm.freqs[maxi], flatspec[maxi], '.', color=PLT_COLORS['periodic'], alpha=0.75, markersize=30) ax.set_ylim(ylims) ax.set_title('Iteration #' + str(ind + 1), fontsize=16) if ind < fm.n_peaks_: gauss = gaussian_function(fm.freqs, *fm.gaussian_params_[ind, :]) plot_spectra(fm.freqs, gauss, ax=ax, label='Gaussian Fit', color=PLT_COLORS['periodic'], linestyle=':', linewidth=3.0) flatspec = flatspec - gauss style_spectrum_plot(ax, False, True)
def plot_annotated_model(fm, plt_log=False, annotate_peaks=True, annotate_aperiodic=True, ax=None): """Plot a an annotated power spectrum and model, from a FOOOF object. Parameters ---------- fm : FOOOF FOOOF object, with model fit, data and settings available. plt_log : boolean, optional, default: False Whether to plot the frequency values in log10 spacing. annotate_peaks : boolean, optional, default: True Whether to annotate the periodic components of the model fit. annotate_aperiodic : boolean, optional, default: True Whether to annotate the aperiodic components of the model fit. ax : matplotlib.Axes, optional Figure axes upon which to plot. Raises ------ NoModelError If there are no model results available to plot. """ # Check that model is available if not fm.has_model: raise NoModelError("No model is available to plot, can not proceed.") # Settings fontsize = 15 lw1 = 4.0 lw2 = 3.0 ms1 = 12 # Create the baseline figure ax = check_ax(ax, PLT_FIGSIZES['spectral']) fm.plot(plot_peaks='dot-shade-width', plt_log=plt_log, ax=ax, data_kwargs={ 'lw': lw1, 'alpha': 0.6 }, aperiodic_kwargs={ 'lw': lw1, 'zorder': 10 }, model_kwargs={ 'lw': lw1, 'alpha': 0.5 }, peak_kwargs={ 'dot': { 'color': PLT_COLORS['periodic'], 'ms': ms1, 'lw': lw2 }, 'shade': { 'color': PLT_COLORS['periodic'] }, 'width': { 'color': PLT_COLORS['periodic'], 'alpha': 0.75, 'lw': lw2 } }) # Get freqs for plotting, and convert to log if needed freqs = fm.freqs if not plt_log else np.log10(fm.freqs) ## Buffers: for spacing things out on the plot (scaled by plot values) x_buff1 = max(freqs) * 0.1 x_buff2 = max(freqs) * 0.25 y_buff1 = 0.15 * np.ptp(ax.get_ylim()) shrink = 0.1 # There is a bug in annotations for some perpendicular lines, so add small offset # See: https://github.com/matplotlib/matplotlib/issues/12820. Fixed in 3.2.1. bug_buff = 0.000001 if annotate_peaks and fm.n_peaks_: # Extract largest peak, to annotate, grabbing gaussian params gauss = get_band_peak_fm(fm, fm.freq_range, attribute='gaussian_params') peak_ctr, peak_hgt, peak_wid = gauss bw_freqs = [ peak_ctr - 0.5 * compute_fwhm(peak_wid), peak_ctr + 0.5 * compute_fwhm(peak_wid) ] if plt_log: peak_ctr = np.log10(peak_ctr) bw_freqs = np.log10(bw_freqs) peak_top = fm.power_spectrum[nearest_ind(freqs, peak_ctr)] # Annotate Peak CF ax.annotate('Center Frequency', xy=(peak_ctr, peak_top), xytext=(peak_ctr, peak_top + np.abs(0.6 * peak_hgt)), verticalalignment='center', horizontalalignment='center', arrowprops=dict(facecolor=PLT_COLORS['periodic'], shrink=shrink), color=PLT_COLORS['periodic'], fontsize=fontsize) # Annotate Peak PW ax.annotate('Power', xy=(peak_ctr, peak_top - 0.3 * peak_hgt), xytext=(peak_ctr + x_buff1, peak_top - 0.3 * peak_hgt), verticalalignment='center', arrowprops=dict(facecolor=PLT_COLORS['periodic'], shrink=shrink), color=PLT_COLORS['periodic'], fontsize=fontsize) # Annotate Peak BW bw_buff = (peak_ctr - bw_freqs[0]) / 2 ax.annotate('Bandwidth', xy=(peak_ctr - bw_buff + bug_buff, peak_top - (0.5 * peak_hgt)), xytext=(peak_ctr - bw_buff, peak_top - (1.5 * peak_hgt)), verticalalignment='center', horizontalalignment='right', arrowprops=dict(facecolor=PLT_COLORS['periodic'], shrink=shrink), color=PLT_COLORS['periodic'], fontsize=fontsize, zorder=20) if annotate_aperiodic: # Annotate Aperiodic Offset # Add a line to indicate offset, without adjusting plot limits below it ax.set_autoscaley_on(False) ax.plot([freqs[0], freqs[0]], [ax.get_ylim()[0], fm.fooofed_spectrum_[0]], color=PLT_COLORS['aperiodic'], linewidth=lw2, alpha=0.5) ax.annotate('Offset', xy=(freqs[0] + bug_buff, fm.power_spectrum[0] - y_buff1), xytext=(freqs[0] - x_buff1, fm.power_spectrum[0] - y_buff1), verticalalignment='center', horizontalalignment='center', arrowprops=dict(facecolor=PLT_COLORS['aperiodic'], shrink=shrink), color=PLT_COLORS['aperiodic'], fontsize=fontsize) # Annotate Aperiodic Knee if fm.aperiodic_mode == 'knee': # Find the knee frequency point to annotate knee_freq = compute_knee_frequency( fm.get_params('aperiodic', 'knee'), fm.get_params('aperiodic', 'exponent')) knee_freq = np.log10(knee_freq) if plt_log else knee_freq knee_pow = fm.power_spectrum[nearest_ind(freqs, knee_freq)] # Add a dot to the plot indicating the knee frequency ax.plot(knee_freq, knee_pow, 'o', color=PLT_COLORS['aperiodic'], ms=ms1 * 1.5, alpha=0.7) ax.annotate('Knee', xy=(knee_freq, knee_pow), xytext=(knee_freq - x_buff2, knee_pow - y_buff1), verticalalignment='center', arrowprops=dict(facecolor=PLT_COLORS['aperiodic'], shrink=shrink), color=PLT_COLORS['aperiodic'], fontsize=fontsize) # Annotate Aperiodic Exponent mid_ind = int(len(freqs) / 2) ax.annotate('Exponent', xy=(freqs[mid_ind], fm.power_spectrum[mid_ind]), xytext=(freqs[mid_ind] - x_buff2, fm.power_spectrum[mid_ind] - y_buff1), verticalalignment='center', arrowprops=dict(facecolor=PLT_COLORS['aperiodic'], shrink=shrink), color=PLT_COLORS['aperiodic'], fontsize=fontsize) # Apply style to plot & tune grid styling style_spectrum_plot(ax, plt_log, True) ax.grid(True, alpha=0.5) # Add labels to plot in the legend da_patch = mpatches.Patch(color=PLT_COLORS['data'], label='Original Data') ap_patch = mpatches.Patch(color=PLT_COLORS['aperiodic'], label='Aperiodic Parameters') pe_patch = mpatches.Patch(color=PLT_COLORS['periodic'], label='Peak Parameters') mo_patch = mpatches.Patch(color=PLT_COLORS['model'], label='Full Model') handles = [ da_patch, ap_patch if annotate_aperiodic else None, pe_patch if annotate_peaks else None, mo_patch ] handles = [el for el in handles if el is not None] ax.legend(handles=handles, handlelength=1, fontsize='x-large')