Skip to content

LFP/ECP Plotting API

bmtool.bmplot.lfp.plot_spectrogram(sxx_xarray, remove_aperiodic=None, log_power=False, plt_range=None, clr_freq_range=None, pad=0.03, ax=None, vmin=None, vmax=None)

Plot a power spectrogram with optional aperiodic removal and frequency-based coloring.

Parameters:

Name Type Description Default
sxx_xarray array - like

Spectrogram data as an xarray DataArray with PSD values.

required
remove_aperiodic optional

FOOOF model object for aperiodic subtraction. If None, raw spectrum is displayed.

None
log_power bool or str

If True or 'dB', convert power to log scale. Default is False.

False
plt_range tuple of float

Frequency range to display as (f_min, f_max). If None, displays full range.

None
clr_freq_range tuple of float

Frequency range to use for determining color limits. If None, uses full range.

None
pad float

Padding for colorbar. Default is 0.03.

0.03
ax Axes

Axes to plot on. If None, creates a new figure and axes.

None
vmin float

Minimum value for colorbar scaling. If None, computed from data.

None
vmax float

Maximum value for colorbar scaling. If None, computed from data.

None

Returns:

Type Description
Figure

The figure object containing the spectrogram.

Examples:

>>> fig = plot_spectrogram(
...     sxx_xarray, log_power='dB',
...     plt_range=(10, 100), clr_freq_range=(20, 50)
... )
Source code in bmtool/bmplot/lfp.py
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def plot_spectrogram(
    sxx_xarray: Any,
    remove_aperiodic: Optional[Any] = None,
    log_power: bool = False,
    plt_range: Optional[Tuple[float, float]] = None,
    clr_freq_range: Optional[Tuple[float, float]] = None,
    pad: float = 0.03,
    ax: Optional[plt.Axes] = None,
    vmin: Optional[float] = None,
    vmax: Optional[float] = None,
) -> Figure:
    """
    Plot a power spectrogram with optional aperiodic removal and frequency-based coloring.

    Parameters
    ----------
    sxx_xarray : array-like
        Spectrogram data as an xarray DataArray with PSD values.
    remove_aperiodic : optional
        FOOOF model object for aperiodic subtraction. If None, raw spectrum is displayed.
    log_power : bool or str, optional
        If True or 'dB', convert power to log scale. Default is False.
    plt_range : tuple of float, optional
        Frequency range to display as (f_min, f_max). If None, displays full range.
    clr_freq_range : tuple of float, optional
        Frequency range to use for determining color limits. If None, uses full range.
    pad : float, optional
        Padding for colorbar. Default is 0.03.
    ax : matplotlib.axes.Axes, optional
        Axes to plot on. If None, creates a new figure and axes.
    vmin : float, optional
        Minimum value for colorbar scaling. If None, computed from data.
    vmax : float, optional
        Maximum value for colorbar scaling. If None, computed from data.

    Returns
    -------
    matplotlib.figure.Figure
        The figure object containing the spectrogram.

    Examples
    --------
    >>> fig = plot_spectrogram(
    ...     sxx_xarray, log_power='dB',
    ...     plt_range=(10, 100), clr_freq_range=(20, 50)
    ... )
    """
    sxx = sxx_xarray.PSD.values.copy()
    t = sxx_xarray.time.values.copy()
    f = sxx_xarray.frequency.values.copy()

    cbar_label = "PSD" if remove_aperiodic is None else "PSD Residual"
    if log_power:
        with np.errstate(divide="ignore"):
            sxx = np.log10(sxx)
        cbar_label += " dB" if log_power == "dB" else " log(power)"

    if remove_aperiodic is not None:
        f1_idx = 0 if f[0] else 1
        ap_fit = gen_aperiodic(f[f1_idx:], remove_aperiodic.aperiodic_params)
        sxx[f1_idx:, :] -= (ap_fit if log_power else 10**ap_fit)[:, None]
        sxx[:f1_idx, :] = 0.0

    if log_power == "dB":
        sxx *= 10

    if ax is None:
        _, ax = plt.subplots(1, 1)
    plt_range = np.array(f[-1]) if plt_range is None else np.array(plt_range)
    if plt_range.size == 1:
        plt_range = [f[0 if f[0] else 1] if log_power else 0.0, plt_range.item()]
    f_idx = (f >= plt_range[0]) & (f <= plt_range[1])

    # Determine vmin and vmax: explicit parameters take precedence, then clr_freq_range, then None
    if vmin is None:
        if clr_freq_range is not None:
            c_idx = (f >= clr_freq_range[0]) & (f <= clr_freq_range[1])
            vmin = sxx[c_idx, :].min()

    if vmax is None:
        if clr_freq_range is not None:
            c_idx = (f >= clr_freq_range[0]) & (f <= clr_freq_range[1])
            vmax = sxx[c_idx, :].max()

    f = f[f_idx]
    pcm = ax.pcolormesh(t, f, sxx[f_idx, :], shading="gouraud", vmin=vmin, vmax=vmax, rasterized=True)
    if "cone_of_influence_frequency" in sxx_xarray:
        coif = sxx_xarray.cone_of_influence_frequency
        ax.plot(t, coif)
        ax.fill_between(t, coif, step="mid", alpha=0.2)
    ax.set_xlim(t[0], t[-1])
    # ax.set_xlim(t[0],0.2)
    ax.set_ylim(f[0], f[-1])
    plt.colorbar(mappable=pcm, ax=ax, label=cbar_label, pad=pad)
    ax.set_xlabel("Time (sec)")
    ax.set_ylabel("Frequency (Hz)")
    return ax.figure