visualdynamics.core.averaging¶
averaging
¶
How a time history is cut into frames before it is averaged.
A spectral average is not one number but a recipe: where to start, how long each frame is, how far they overlap, what window shapes them, and how many of them there are. Those five are independent; everything else — the hop between frames, where the analysis ends, how long it runs — follows from them, and is derived here rather than stored, so nothing can disagree.
The frame count is what is kept, not the stop time. Dragging the end of the analysis in the GUI adds or removes whole frames, because half a frame is not an average.
Classes:
| Name | Description |
|---|---|
Averaging |
The six independent numbers, and everything else derived. |
Functions:
| Name | Description |
|---|---|
normalise_detrend |
The key for a detrend named any of the ways it might be. |
normalise_window |
The key for a window named any of the ways a file might name it. |
from_span |
The averaging a dragged span means, in whole frames. |
window_shape |
The window itself, as the values each frame is multiplied by. |
Classes¶
Averaging
dataclass
¶
Averaging(
frame_length: int,
overlap: float = 0.0,
window: str = "hann",
frames: int = 1,
start: float = 0.0,
detrend: str = "none",
window_parameter: float | None = None,
)
The six independent numbers, and everything else derived.
start is in seconds from the beginning of the record; overlap is
a fraction of a frame, so 0.5 is the usual half-overlap; frames is
how many are averaged per record. A history that already holds one
frame per record — a burst-random run saved as 20 captures — is
described by a frame the length of a record with frames=1, which
is what for_records builds and what averaging with no parameters
has always done.
There is no zero-padding field, and there was for two days
(2026-08-27/28, pad). It was removed once the mode fitter was
shown not to need it — the apparent accuracy it bought was the
fitter's own search railing on a leash, fixed in the search — and
what remained was presentation: interpolated lines that resolve
nothing new. The demonstration projects were regenerated rather
than kept compatible with a two-day experiment.
Methods:
| Name | Description |
|---|---|
for_records |
Each record its own frame, which is what a stored capture is. |
shape |
This averaging's window, as the values each frame is |
stop |
When the analysis ends, in seconds — derived, never stored. |
frame_bounds |
[(start, stop)] in seconds, one per frame, overlaps included — |
fits |
Is there record enough for every frame asked for? |
most_frames |
How many whole frames the record can carry from |
levels |
(level per frame, how many levels). |
rail |
Where the flat rail's slots sit, in fractions of the |
filled |
This averaging with as many frames as actually fit. |
Attributes:
| Name | Type | Description |
|---|---|---|
hop |
int
|
Samples from the start of one frame to the start of the next. |
span |
int
|
Samples the whole analysis covers, first frame to last. |
Attributes¶
Methods:¶
for_records
classmethod
¶
for_records(
samples: int, window: str = "boxcar"
) -> Averaging
Each record its own frame, which is what a stored capture is.
Everything but the window follows from the record: it starts where the record starts, runs its whole length, and there is no second frame inside it to overlap with. The window is still the user's, so a saved capture can be re-averaged under Hann without the frames themselves moving.
Source code in src/visualdynamics/core/averaging.py
shape
¶
This averaging's window, as the values each frame is
multiplied by — window_shape with this frame length and
this parameter, so no caller can pair them wrongly.
Returns:
| Type | Description |
|---|---|
ndarray
|
One value per sample of a frame. |
Source code in src/visualdynamics/core/averaging.py
stop
¶
frame_bounds
¶
[(start, stop)] in seconds, one per frame, overlaps included — what the time history plot shades.
Source code in src/visualdynamics/core/averaging.py
fits
¶
most_frames
¶
How many whole frames the record can carry from start.
What the drag handle snaps to: a frame is added only when there is room for all of it.
Source code in src/visualdynamics/core/averaging.py
levels
¶
(level per frame, how many levels).
Frames that overlap cannot share a row without their windows running through each other, so each takes the lowest row whose last frame has already ended. With no overlap they all sit on one row; at half overlap they alternate between two.
Here rather than in the overlay because the report draws the same marks and the two must not drift — a figure that stacked them differently from the app would be describing a different analysis.
Source code in src/visualdynamics/core/averaging.py
rail
¶
Where the flat rail's slots sit, in fractions of the visible height — the 2-D overlay's own geometry, and the report's, from one set of numbers.
Here for the same reason levels is: the app draws this rail
over a live plot and the report draws it in a canvas, and a
figure whose rail sat differently from the app's would be
describing a different analysis. The page used to restate
these constants in its JavaScript and was held to them by
matching strings, which is a weaker seam than being told.
Returns the level of each frame, how many levels there are,
one slot's share, the glyph's height inside its slot, the tick
under each end, the baseline of every frame, and foot — the
share of the height the trace keeps once the rail has its
room above.
Source code in src/visualdynamics/core/averaging.py
Functions:¶
normalise_detrend
¶
The key for a detrend named any of the ways it might be.
Source code in src/visualdynamics/core/averaging.py
normalise_window
¶
The key for a window named any of the ways a file might name it.
Source code in src/visualdynamics/core/averaging.py
from_span
¶
from_span(
averaging: Averaging,
low: float,
high: float,
sample_rate: float,
samples: int,
) -> Averaging
The averaging a dragged span means, in whole frames.
The frame length and the window are the table's; a drag says only where the analysis starts and how many frames it holds. One implementation for both editors — the 2-D region and the stage's handles commit through this, so they cannot disagree about what a span means.
Source code in src/visualdynamics/core/averaging.py
window_shape
¶
The window itself, as the values each frame is multiplied by.
scipy's get_window, periodic (fftbins=True) — the convention
for spectral work, where a frame is one period of an
assumed-repeating signal.
These were hand-written cosine sums until 2026-08-28, because scipy
was a test-only dependency and fifty lines was the better trade
(principle 7). scipy became a runtime dependency that week, and the
trade reversed: the tables were verified identical to scipy's to
machine epsilon at every length that can hold a frame before being
deleted, and the sdynpy oracle now checks the windows through the
whole spectral pipeline on every run. The one behavioural change is
a one-sample window, which reads 1.0 where the old taper read its
own first point — and a one-sample frame cannot exist
(Averaging requires two).