Skip to content

visualdynamics.gui.sine_panel

sine_panel

The sine extraction's settings, as a table beside the time history.

The settings say how a sweep's levels are read out of the recording: the smoothing, chosen automatically or set as a number of cycles, and whether the sweep clock is fitted to the recording. Below them are the values that follow: the cycles the automatic choice used, the smoothing window in seconds at the start and end of the first tone, the median scatter a reading is predicted to have, and the share of samples under the noise floor. A preview plot shows the readings themselves, sampled along each tone at the smoothing shown (core.sine.sample_levels, a few short solves, so it follows every edit). The Extract Sine Levels button runs the project verb with whatever is set at that moment, the same split as in the filter panel, since these are also settings carried by the record. Under the button, when the specification holds a dwell, the dwell readout reads it against time (gui.dwell_panel); a held tone is one point over frequency, and its story is in time. And once the levels have been extracted, the distortion readout checks them against the record read unfiltered (gui.distortion_panel): what the channel carried besides the tone the extraction tracked; and the frequency readout checks the tone's own frequency on the record against the specification's (gui.frequency_panel). Under that, when the project also holds a random specification of the floor, the mixed-mode readout reads the record's data sheet, floor, tones and total, each by its own rule (gui.mixed_panel).

Classes:

Name Description
SamplePlot

The sampled readings against frequency: a dot per control

SinePanel

The settings table. Edits are emitted as a whole SineExtraction.

Classes

SamplePlot

SamplePlot(parent: QWidget | None = None)

Bases: PreviewPlot

The sampled readings against frequency: a dot per control channel at the smoothing shown, or a hollow ring at the noise floor where the tone is under it.

It is small on purpose, like the filter panel's response plot: it shows at a glance how clean the readings are at this smoothing, and the extracted levels show the rest.

Methods:

Name Description
show_samples

Draw the sampled readings: one color per tone, a dot per

Source code in src/visualdynamics/gui/sine_panel.py
def __init__(self, parent: QWidget | None = None) -> None:
    super().__init__(parent)
    self.items: list[Any] = []
Methods:
show_samples
show_samples(sampled: list[dict[str, Any]]) -> None

Draw the sampled readings: one color per tone, a dot per channel and point, and a ring at the floor where the tone was under it.

Source code in src/visualdynamics/gui/sine_panel.py
def show_samples(self, sampled: list[dict[str, Any]]) -> None:
    """Draw the sampled readings: one color per tone, a dot per
    channel and point, and a ring at the floor where the tone was
    under it."""
    import numpy as np
    import pyqtgraph as pg

    from ..plot import curve_color

    for item in self.items:
        self.plot.removeItem(item)
    self.items = []
    for k, reading in enumerate(sampled):
        color = curve_color(k, self.colors)
        amplitude = np.asarray(reading['amplitude'])
        floor = np.asarray(reading['floor'])
        frequency = np.asarray(reading['frequency'])
        under = amplitude <= floor
        shown = np.where(under, floor, amplitude)
        for row in range(amplitude.shape[0]):
            x = frequency[~under[row]]
            if x.size:
                self.items.append(self.plot.plot(
                    x, shown[row][~under[row]], pen=None, symbol='o',
                    symbolSize=5, symbolPen=None,
                    symbolBrush=pg.mkBrush(color)))
            x = frequency[under[row]]
            if x.size:
                self.items.append(self.plot.plot(
                    x, shown[row][under[row]], pen=None, symbol='o',
                    symbolSize=6, symbolBrush=None,
                    symbolPen=pg.mkPen(color, width=1)))

SinePanel

SinePanel(parent: QWidget | None = None)

Bases: QWidget

The settings table. Edits are emitted as a whole SineExtraction.

Methods:

Name Description
show_history

Point the panel at a record, the sweep it is read against

set_setting

Show a setting in the panel without emitting changed.

setting

The setting the editors currently describe, carrying the

Source code in src/visualdynamics/gui/sine_panel.py
def __init__(self, parent: QWidget | None = None) -> None:
    super().__init__(parent)
    #: the record and the sweep the parameters describe
    self._history: Any = None
    self._specification: Any = None
    #: what the automatic chose for this record, from the last
    #: sampling — carried on the setting as `chosen`
    self._chosen: float | None = None
    #: the last sampled readings, for the derived rows
    self._sampled: list[dict[str, Any]] = []
    #: set while the panel is writing to its own editors
    self._loading = False
    self.title: QLabel = QLabel('Sine Levels')
    grid = panel_grid(self, self.title)
    self.mode_box: QComboBox = QComboBox()
    self.mode_box.addItems(MODE_LABELS)
    self.mode_box.setToolTip(
        'Automatic: the smoothing climbs until a reading is '
        'predicted to scatter less than the target, measured from '
        'this recording. Set: the number of cycles you type')
    self.cycles_box: DoubleSpinBox = DoubleSpinBox()
    self.cycles_box.setRange(CYCLES_LADDER[0], CYCLES_LADDER[-1])
    self.cycles_box.setDecimals(0)
    self.cycles_box.setSingleStep(10.0)
    self.cycles_box.setValue(CYCLES_LADDER[0])
    self.cycles_box.setToolTip(
        'How many cycles of the tone’s own frequency each reading '
        'averages over. Fewer follow a resonance closely; more hold '
        'the random environment out of the reading')
    self.target_box: DoubleSpinBox = DoubleSpinBox()
    self.target_box.setRange(0.1, 6.0)
    self.target_box.setDecimals(1)
    self.target_box.setSingleStep(0.5)
    self.target_box.setSuffix(' dB')
    self.target_box.setValue(1.0)
    self.target_box.setToolTip(
        'The scatter the automatic smoothing aims to hold a '
        'reading under, one standard deviation')
    self.refine_box: QCheckBox = QCheckBox()
    self.refine_box.setChecked(True)
    self.refine_box.setToolTip(
        'Fit the sweep clock to the recording and solve again when '
        'the recorded sweep drifted from the one commanded — a long '
        'run whose rate was not quite the specification’s would '
        'otherwise read low toward its end')
    rows = (('mode', 'Smoothing', self.mode_box),
            ('cycles', 'Cycles', self.cycles_box),
            ('target', 'Scatter under', self.target_box),
            ('refine', 'Follow the clock', self.refine_box))
    self._labels, self._editors = add_keyed_settings(grid, rows)
    add_rule(grid, len(rows) + 1)
    # what follows from the settings, read where they are set
    self.derived: dict[str, QLabel] = {}
    self.derived_names: dict[str, QLabel] = {}
    derived = (('chosen', 'Cycles used'),
               ('window', 'Window, start → end'),
               ('scatter', 'Predicted scatter'),
               ('under', 'Under the floor'))
    for key, (name, value) in add_derived(grid, derived,
                                          len(rows) + 2).items():
        self.derived[key] = value
        self.derived_names[key] = name
    self.preview: SamplePlot = SamplePlot()
    grid.addWidget(self.preview, len(rows) + 2 + len(derived), 0, 1, 2)
    self.extract_button: QPushButton = QPushButton('Extract Sine Levels')
    self.extract_button.setToolTip(
        'Read every tone’s level out of the recording with exactly '
        'the smoothing shown, one object for the set')
    self.extract_button.clicked.connect(self.apply_asked.emit)
    grid.addWidget(self.extract_button, len(rows) + 3 + len(derived),
                   0, 1, 2)
    # the dwell readout, shown only beside a specification that
    # holds a dwell; it lives in this panel so it comes and goes
    # with the sine view, and reads nothing the extraction sets
    self.dwell: DwellPanel = DwellPanel()
    self.dwell.hide()
    grid.addWidget(self.dwell, len(rows) + 4 + len(derived), 0, 1, 2)
    # the distortion readout, shown only beside extracted levels; it
    # reads them and the record and sets nothing the extraction uses
    self.distortion: DistortionPanel = DistortionPanel()
    self.distortion.hide()
    grid.addWidget(self.distortion, len(rows) + 5 + len(derived), 0, 1,
                   2)
    # and the frequency readout under it, the same way: the tone's own
    # frequency on the record against the specification's
    self.frequency: FrequencyPanel = FrequencyPanel()
    self.frequency.hide()
    grid.addWidget(self.frequency, len(rows) + 6 + len(derived), 0, 1,
                   2)
    # and the mixed-mode data sheet, shown only when the project
    # holds the floor's random specification beside the tones
    self.mixed: MixedPanel = MixedPanel()
    self.mixed.hide()
    grid.addWidget(self.mixed, len(rows) + 7 + len(derived), 0, 1, 2)
    grid.setRowStretch(len(rows) + 8 + len(derived), 1)
    commit_on_enter(self.cycles_box, self.target_box)
    self.mode_box.currentIndexChanged.connect(self._edited)
    self.cycles_box.valueChanged.connect(self._edited)
    self.target_box.valueChanged.connect(self._edited)
    self.refine_box.toggled.connect(self._edited)
    self._show_mode('automatic')
Methods:
show_history
show_history(history: TimeHistory, specification: SineSweepSpecification, setting: SineExtraction) -> None

Point the panel at a record, the sweep it is read against and the setting on it, and sample the readings at that setting.

Source code in src/visualdynamics/gui/sine_panel.py
def show_history(self, history: TimeHistory,
                 specification: SineSweepSpecification,
                 setting: SineExtraction) -> None:
    """Point the panel at a record, the sweep it is read against
    and the setting on it, and sample the readings at that
    setting."""
    self._history = history
    self._specification = specification
    self._chosen = setting.chosen
    self.set_setting(setting)
set_setting
set_setting(setting: SineExtraction) -> None

Show a setting in the panel without emitting changed.

Source code in src/visualdynamics/gui/sine_panel.py
def set_setting(self, setting: SineExtraction) -> None:
    """Show a setting in the panel without emitting `changed`."""
    self._loading = True
    try:
        self.mode_box.setCurrentIndex(
            MODES.index('automatic' if setting.automatic else 'set'))
        if setting.cycles is not None:
            self.cycles_box.setValue(float(setting.cycles))
        elif setting.chosen is not None:
            self.cycles_box.setValue(float(setting.chosen))
        self.target_box.setValue(float(setting.target_db))
        self.refine_box.setChecked(bool(setting.refine))
    finally:
        self._loading = False
    self._show_mode('automatic' if setting.automatic else 'set')
    self._restate()
setting
setting() -> SineExtraction

The setting the editors currently describe, carrying the cycles the automatic choice picked for this record.

Source code in src/visualdynamics/gui/sine_panel.py
def setting(self) -> SineExtraction:
    """The setting the editors currently describe, carrying the
    cycles the automatic choice picked for this record."""
    automatic = MODES[self.mode_box.currentIndex()] == 'automatic'
    return SineExtraction(
        cycles=None if automatic else float(self.cycles_box.value()),
        target_db=float(self.target_box.value()),
        refine=self.refine_box.isChecked(),
        chosen=self._chosen if automatic else None)

Functions: