Skip to content

visualdynamics.gui.wavelet_panel

wavelet_panel

The settings the scalogram is computed with, as a table beside it.

Four values are set here: the bottom and top of the frequency range, the lines per octave, and the number of cycles under the wavelet, which sets how it trades time resolution against frequency resolution. Three more are shown that follow from them: the width of the cone of influence at the bottom of the range, the frequency resolution there, and the number of lines.

The record is not chosen here. The scalogram uses the record selected in the project tree, as every other view does, and this panel holds only the settings the tree cannot.

The derived values are shown because the scalogram is the one reading in this toolset where the settings decide whether most of the picture is artifact. Ask for 1 Hz on a two-second record and the bottom of the axis is all cone of influence, and the picture still looks plausible while it says nothing about the measurement. So the panel says so before the transform runs, with a note under the table when the cone at the lowest frequency covers a third of the record or more.

Nothing here computes the transform. The panel shows the settings and reports when they change, as the averaging panel does.

Classes:

Name Description
WaveletPanel

The scalogram's settings. Each edit is reported on changed as

Classes

WaveletPanel

WaveletPanel(parent: QWidget | None = None)

Bases: QWidget

The scalogram's settings. Each edit is reported on changed as the whole settings dict: 'low' and 'high' in Hz, 'per_octave' and 'omega0'.

Methods:

Name Description
show_history

Show the settings for a time history.

suggested

The default settings for this record.

settings

The settings the editors show now, as a dict: 'low' and 'high'

Source code in src/visualdynamics/gui/wavelet_panel.py
def __init__(self, parent: QWidget | None = None) -> None:
    super().__init__(parent)
    self.sample_rate: float = 1.0
    self.samples: int = 0
    self.duration: float = 0.0
    #: set while the panel writes to its own editors, so restating a
    #: clamped value does not read as a fresh edit
    self._loading = False

    self.title: QLabel = QLabel('Wavelet')
    grid = panel_grid(self, self.title)

    self.low_box: DoubleSpinBox = DoubleSpinBox()
    self.low_box.setDecimals(2)
    self.low_box.setSuffix(' Hz')
    self.low_box.setToolTip(
        'The bottom of the frequency axis. Low frequencies use long '
        'wavelets, so this is what decides how much of the picture '
        'is cone of influence')

    self.high_box: DoubleSpinBox = DoubleSpinBox()
    self.high_box.setDecimals(2)
    self.high_box.setSuffix(' Hz')
    self.high_box.setToolTip(
        'The top of the frequency axis. Defaults to everything the '
        'record carries; the reading stays true to a fraction of a '
        'percent right up against Nyquist')

    self.per_octave_box: SpinBox = SpinBox()
    self.per_octave_box.setRange(1, 96)
    self.per_octave_box.setToolTip(
        'Lines per octave. The axis is logarithmic because a '
        "wavelet's bandwidth is a constant fraction of its "
        'frequency — evenly spaced lines would crowd at the top')

    self.omega_box: DoubleSpinBox = DoubleSpinBox()
    self.omega_box.setDecimals(1)
    self.omega_box.setRange(3.0, 30.0)
    self.omega_box.setSingleStep(1.0)
    self.omega_box.setToolTip(
        'Cycles under the wavelet, which is the trade itself: low '
        'resolves when and blurs what, high resolves what and '
        'blurs when. Six is conventional, and near the lowest value '
        'for which the transform is admissible at all')

    rows = (('From', self.low_box),
            ('To', self.high_box),
            ('Per octave', self.per_octave_box),
            ('Cycles', self.omega_box))
    add_settings(grid, rows)

    add_rule(grid, len(rows) + 1)

    self.derived: dict[str, QLabel] = {}
    derived = (('cone', 'Cone at bottom'), ('resolves', 'Resolves'),
               ('transforms', 'Lines'))
    for key, (_name, value) in add_derived(grid, derived,
                                           len(rows) + 2).items():
        self.derived[key] = value

    self.note: QLabel = QLabel()
    self.note.setWordWrap(True)
    self.note.setEnabled(False)
    grid.addWidget(self.note, len(rows) + 2 + len(derived), 0, 1, 2)
    grid.setRowStretch(len(rows) + 3 + len(derived), 1)

    commit_on_enter(self.low_box, self.high_box, self.per_octave_box,
                    self.omega_box)
    for editor in (self.low_box, self.high_box, self.per_octave_box,
                   self.omega_box):
        editor.valueChanged.connect(self._edited)
Methods:
show_history
show_history(history: TimeHistory, settings: dict[str, Any] | None = None) -> None

Show the settings for a time history.

settings is the scalogram's settings on that history, and when it is None the panel opens on suggested. The limits every value is clamped to come from this history's sample rate, not from the values the boxes held for the last record, and the range is clamped as a pair, so it keeps at least an octave of width.

Source code in src/visualdynamics/gui/wavelet_panel.py
def show_history(self, history: TimeHistory,
                 settings: dict[str, Any] | None = None) -> None:
    """Show the settings for a time history.

    `settings` is the scalogram's settings on that history, and when
    it is None the panel opens on `suggested`. The limits every value
    is clamped to come from this history's sample rate, not from the
    values the boxes held for the last record, and the range is
    clamped as a pair, so it keeps at least an octave of width.
    """
    self.sample_rate = history.sample_rate
    self.samples = len(history.abscissa)
    self.duration = self.samples / self.sample_rate if self.sample_rate \
        else 0.0
    settled = dict(self.suggested()) if settings is None else dict(settings)
    # through the clamp, not straight into the boxes: a range tuned
    # on one record and carried to a slower one arrives above this
    # Nyquist, and each box clamping on its own would settle both
    # ends onto the same number — a range of no width, which is not
    # a range the derived rows can be computed from
    settled = self._clamped_against(settled, history.sample_rate)

    self._loading = True
    try:
        self._apply_limits()
        self.low_box.setValue(settled['low'])
        self.high_box.setValue(settled['high'])
        self.per_octave_box.setValue(settled['per_octave'])
        self.omega_box.setValue(settled['omega0'])
    finally:
        self._loading = False
    self._restate(self.settings())
suggested
suggested() -> dict[str, Any]

The default settings for this record.

The range is core.wavelet.default_range: the top at 0.98 of Nyquist, and the bottom at eight cycles over the record's duration, so the longest wavelets still fit inside the record and the bottom of the range is not mostly cone of influence. Lines per octave and cycles are the defaults core.wavelet.PER_OCTAVE and core.wavelet.OMEGA0.

Source code in src/visualdynamics/gui/wavelet_panel.py
def suggested(self) -> dict[str, Any]:
    """The default settings for this record.

    The range is `core.wavelet.default_range`: the top at 0.98 of
    Nyquist, and the bottom at eight cycles over the record's
    duration, so the longest wavelets still fit inside the record and
    the bottom of the range is not mostly cone of influence. Lines per
    octave and cycles are the defaults `core.wavelet.PER_OCTAVE` and
    `core.wavelet.OMEGA0`.
    """
    low, high = wavelet.default_range(self.sample_rate or 1.0,
                                      self.duration)
    return {'low': low, 'high': high,
            'per_octave': wavelet.PER_OCTAVE, 'omega0': wavelet.OMEGA0}
settings
settings() -> dict[str, Any]

The settings the editors show now, as a dict: 'low' and 'high' in Hz, 'per_octave' and 'omega0'.

Source code in src/visualdynamics/gui/wavelet_panel.py
def settings(self) -> dict[str, Any]:
    """The settings the editors show now, as a dict: 'low' and 'high'
    in Hz, 'per_octave' and 'omega0'."""
    return {'low': self.low_box.value(),
            'high': self.high_box.value(),
            'per_octave': self.per_octave_box.value(),
            'omega0': self.omega_box.value()}

Functions: