Skip to content

visualdynamics.plot.scalogram

scalogram

Drawing a scalogram: time across, frequency up, magnitude as color.

One channel is drawn at a time, because the image is dense and a second one beside it at the same size is too hard to read.

The frequency axis is logarithmic. A wavelet's bandwidth is a constant fraction of its frequency, so the rows are evenly spaced in log frequency, and drawing them evenly spaced in linear frequency would put them in the wrong place. The image is drawn in log space and the axis is labeled in Hz.

The cone of influence is shaded over the image. Near each end of the record, over a time that is longer at lower frequencies, the values depend on where the record was cut and not only on the record. They look the same as the rest of the image, so the shading is the only way to tell them apart.

Functions:

Name Description
scalogram_image

Draw one channel's scalogram onto plot, and return the image.

Functions:

scalogram_image

scalogram_image(plot: Any, magnitude: ndarray, times: ndarray, frequencies: ndarray, colors: Mapping[str, str], *, label: str = '', units: str = '', omega0: float = OMEGA0, time_label: str = 'time [s]', levels: tuple[float, float] | None = None) -> Any

Draw one channel's scalogram onto plot, and return the image.

Parameters:

Name Type Description Default
plot PlotItem

Where to draw. Nothing on it is cleared; the caller owns the layout.

required
magnitude ndarray

(frequencies, times), already the magnitude. No transform is computed here.

required
times ndarray

The record's clock, in seconds, one per column.

required
frequencies ndarray

One per row, ascending, evenly spaced in log frequency.

required
colors mapping

The theme.

required
label str

The color bar's quantity and unit: the record's own, which the transform's amplitude normalization keeps.

''
units str

The color bar's quantity and unit: the record's own, which the transform's amplitude normalization keeps.

''
omega0 float

The wavelet's width parameter, used to compute the cone of influence.

OMEGA0
time_label str

The bottom axis's label, units included.

'time [s]'
levels (float, float)

The color scale's ends. The default is zero to the largest finite magnitude. A caller drawing the magnitude in decibels, which is never above its reference, passes the range it wants.

None

Returns:

Type Description
ImageItem

The image, so the caller can change its levels.

Source code in src/visualdynamics/plot/scalogram.py
def scalogram_image(plot: Any, magnitude: np.ndarray, times: np.ndarray,
                    frequencies: np.ndarray, colors: Mapping[str, str],
                    *, label: str = '', units: str = '',
                    omega0: float = wavelet.OMEGA0,
                    time_label: str = 'time [s]',
                    levels: tuple[float, float] | None = None) -> Any:
    """Draw one channel's scalogram onto `plot`, and return the image.

    Parameters
    ----------
    plot : pyqtgraph.PlotItem
        Where to draw. Nothing on it is cleared; the caller owns the
        layout.
    magnitude : numpy.ndarray
        ``(frequencies, times)``, already the magnitude. No transform
        is computed here.
    times : numpy.ndarray
        The record's clock, in seconds, one per column.
    frequencies : numpy.ndarray
        One per row, ascending, evenly spaced in log frequency.
    colors : mapping
        The theme.
    label, units : str
        The color bar's quantity and unit: the record's own, which the
        transform's amplitude normalization keeps.
    omega0 : float
        The wavelet's width parameter, used to compute the cone of
        influence.
    time_label : str
        The bottom axis's label, units included.
    levels : (float, float), optional
        The color scale's ends. The default is zero to the largest
        finite magnitude. A caller drawing the magnitude in decibels,
        which is never above its reference, passes the range it wants.

    Returns
    -------
    pyqtgraph.ImageItem
        The image, so the caller can change its levels.
    """
    import pyqtgraph as pg

    foreground = pg.mkPen(colors['plot_foreground'])
    for edge in ('left', 'bottom', 'top', 'right'):
        axis = plot.getAxis(edge)
        axis.setPen(foreground)
        axis.setTextPen(foreground)

    image = pg.ImageItem(np.asarray(magnitude).T)
    image.setColorMap(pg.colormap.get('viridis'))
    if levels is None:
        finite = np.isfinite(magnitude)
        top = float(np.max(magnitude[finite])) if finite.any() else 1.0
        levels = (0.0, top if top > 0.0 else 1.0)
    levels = (float(levels[0]), float(levels[1]))
    image.setLevels(levels)

    left, right = float(times[0]), float(times[-1])
    # drawn in log frequency, which is where the rows are actually
    # evenly spaced; the axis is labeled back in Hz below
    low, high = np.log10(frequencies[0]), np.log10(frequencies[-1])
    image.setRect(left, float(low), right - left, float(high - low))
    plot.addItem(image)

    plot.setXRange(left, right, padding=0)
    plot.setYRange(float(low), float(high), padding=0)
    plot.getViewBox().setLimits(xMin=left, xMax=right,
                                yMin=float(low), yMax=float(high))
    plot.setLabel('bottom', time_label)
    plot.setLabel('left', 'frequency [Hz]')
    plot.getAxis('left').setTicks([_decade_ticks(frequencies)])
    plot.showGrid(x=True, y=True, alpha=0.2)

    _draw_cone(plot, times, frequencies, colors, omega0)

    bar = pg.ColorBarItem(
        values=levels,
        colorMap=pg.colormap.get('viridis'),
        label=f'{label} [{units}]' if units else label, interactive=False)
    bar.setImageItem(image, insert_in=plot)
    return image