visualdynamics.core.resample¶
resample
¶
A record taken to a lower sample rate, its anti-alias filter first.
A record sampled at one rate can represent frequencies up to half that rate, its Nyquist frequency, and no higher. Content above it does not disappear when the record is sampled: it lands in the record at a lower frequency, the alias frequency |n fs - F| for whichever whole number n brings it into the band from zero to half the sample rate, and once it is in the samples nothing in the record can tell it from real content. Keeping every q-th sample of a record is sampling it again, at a q-th of the rate, and the same rule applies: everything the record holds between the new Nyquist frequency and the old one folds into the new band.
So resample filters before it decimates. The filter is a zero-phase
Butterworth low-pass (core.filters.design, run forward and backward
as filters.filtered runs it) that keeps the alias-free span flat:
the span is the new rate divided by 2.56, the band of lines an analyzer
keeps and shows. Above the span, up to the new Nyquist frequency, is the
filter's roll-off, and those lines are computed and dropped. A frame of
1,024 samples has 512 lines up to the Nyquist frequency, of which
1,024 / 2.56 = 400 lie inside the span and are kept, and 112 are
dropped; the line counts 400, 800 and 1,600 on an analyzer's menu come
from that division. The lowest frequency that can fold into the span is
the new rate less the span, 1.56 spans. The filter's corner is at 1.2
spans (CORNER), among the dropped lines, so a zero-phase pair, which
is 6.02 dB down at its nominal corner, is about 0.1 dB down at the span
and 0.01 dB at 0.9 of it. The lowest fold is then 1.3 corners up, and
the default twelfth-order design, doubled by the forward and backward
pass, is 55 to 56 dB down there at the factors of 10 to 16 in common
use and never less than 54.7 dB at any factor, past 50 dB of
attenuation where content would fold.
The order matters in one direction only. A filter run after the record
is decimated cannot help: the alias is in the band by then, at a
frequency where real content could be, and the same low-pass run on
the decimated record leaves it exactly where it was. read_resample reads that out on a
record: the record's autospectrum as recorded, and at the new rate
three ways (filtered first, decimated bare, and low-passed after
decimating), with the level each puts at the frequency where each
given frequency above the new Nyquist frequency folds.
resample with anti_alias=False keeps every q-th sample with no
filter, for the rare record already band-limited below the new span,
and warns (AliasWarning) every time, because whether it was is not
something the record can show.
References
Decimation and the folding rule are textbook; these are where the folding rule and the anti-alias filter a sampled record needs are set out, and decimation is the same rule applied to a record already sampled.
- Department of Defense (2019). MIL-STD-810H, Environmental Engineering Considerations and Laboratory Tests. Method 516.8, Annex A, paragraph 1.1: the alias frequency |n Fs - F|, and an anti-alias filter ahead of the sampling that attenuates by 50 dB or more anything that would fold into the passband.
- Bendat, J. S., & Piersol, A. G. (2010). Random Data: Analysis and Measurement Procedures, 4th ed. Wiley. Sampling, aliasing, and the folding of content above the Nyquist frequency into the band below it.
Classes:
| Name | Description |
|---|---|
AliasWarning |
A record was resampled with no anti-alias filter, so whatever it |
Fold |
Where one frequency above the new Nyquist frequency lands, and |
ResampleReading |
A record's resampling read out: the rates, the span and its |
Functions:
| Name | Description |
|---|---|
alias_free_span |
The band of lines kept at |
alias_frequency |
Where content at |
line_counts |
(lines, kept, dropped) for a frame of |
decimation_factor |
How many samples of a record at |
anti_alias_filter |
The low-pass a record is filtered through before it is decimated |
resample |
|
level_change |
A level change in words: '61.8 dB down', 'unchanged' within a |
ordinal |
2nd, 3rd, 10th, 21st: the sample kept, in words. |
read_resample |
Read what taking channel |
Classes¶
AliasWarning
¶
Bases: UserWarning
A record was resampled with no anti-alias filter, so whatever it held between the new Nyquist frequency and the old one has folded into the new band, where nothing can take it out again.
Fold
dataclass
¶
Fold(frequency: float, alias: float, multiple: int, in_span: bool, source: float = float('nan'), bare: float = float('nan'), after: float = float('nan'), filtered: float = float('nan'))
Where one frequency above the new Nyquist frequency lands, and the level it lands at in each of the reading's spectra.
frequency is the content's own frequency and alias where it
lands, |n fs - F| with n multiple. in_span says whether it lands
inside the alias-free span, where it would be read as content, or
among the dropped lines above it. The levels are densities in SI,
as TimeHistory.compute_psds gives them, each the tallest line
within one line of the frequency: source at frequency in the
record as recorded, and at alias the record decimated bare
(bare), decimated then low-passed through the same filter
(after), and filtered first (filtered).
Methods:
| Name | Description |
|---|---|
describe |
The fold in words: where it lands, and what each order of |
Attributes:
| Name | Type | Description |
|---|---|---|
after_db |
float
|
What low-passing after the decimation did at the alias, in dB |
filtered_db |
float
|
What filtering first did at the alias, in dB against the bare |
Attributes¶
after_db
property
¶
What low-passing after the decimation did at the alias, in dB against the bare record's level there: about zero inside the span, where the alias stays.
filtered_db
property
¶
What filtering first did at the alias, in dB against the bare record's level there.
Methods:¶
describe
¶
The fold in words: where it lands, and what each order of filtering left there.
Source code in src/visualdynamics/core/resample.py
ResampleReading
dataclass
¶
ResampleReading(record: int, label: str, source_rate: float, rate: float, factor: int, order: int, frame_length: int, lines: int, kept: int, dropped: int, folds: tuple[Fold, ...], density_dim: str, spectra: dict[str, tuple[ndarray, ndarray]], unfolded: tuple[float, ...] = (), findings: tuple[str, ...] = ())
A record's resampling read out: the rates, the span and its lines, the filter and what it does at the edges that matter, where each given frequency folds, and the spectra that show it.
Rates and frequencies are in Hz and gains in dB. corner is the
anti-alias filter's corner, CORNER (1.2) spans; gain_at_span is
the zero-phase gain at the span and gain_at_fold at
lowest_fold, the lowest frequency that folds into the span (the
new rate less the span). lines, kept and dropped are line_counts for the
spectra's frame at the new rate, and resolution their line
spacing. spectra holds (frequencies, densities) for 'source', the
record as recorded on the same line spacing, and at the new rate
'filtered' (filtered first), 'bare' (decimated with no filter) and
'after' (decimated, then low-passed through the same filter); the
densities are in SI with the dimension density_dim. unfolded
holds the frequencies asked for that are at or below the new Nyquist
frequency, which do not fold, and notes names them in words, with
findings: what the channel held that left nothing to read (a
channel of zeros, or no line above the new Nyquist frequency to
fold).
Methods:
| Name | Description |
|---|---|
describe |
The reading in words, as the plot's title and the status line |
Attributes:
| Name | Type | Description |
|---|---|---|
nyquist |
float
|
Half the new rate: the highest frequency the result holds. |
span |
float
|
The alias-free span at the new rate. |
corner |
float
|
The anti-alias filter's corner, |
resolution |
float
|
The spectra's line spacing, Hz. |
lowest_fold |
float
|
The lowest frequency that folds into the span: the new rate |
filtering |
Filtering
|
The anti-alias filter the record was read through. |
gain_at_span |
float
|
The filter's zero-phase gain at the span's top line, dB. |
gain_at_fold |
float
|
The filter's zero-phase gain at the lowest frequency that |
notes |
tuple[str, ...]
|
Each frequency asked for that does not fold, in words: a |
Attributes¶
lowest_fold
property
¶
The lowest frequency that folds into the span: the new rate less the span.
gain_at_span
property
¶
The filter's zero-phase gain at the span's top line, dB.
gain_at_fold
property
¶
The filter's zero-phase gain at the lowest frequency that folds into the span, dB.
notes
property
¶
Each frequency asked for that does not fold, in words: a
frequency typed at or below the new Nyquist frequency is said to
be there rather than dropped without a word; then the
findings.
Methods:¶
describe
¶
The reading in words, as the plot's title and the status line give it.
Source code in src/visualdynamics/core/resample.py
Functions:¶
alias_free_span
¶
The band of lines kept at sample_rate, in Hz: the rate over
ratio (2.56). The lines above it, up to the Nyquist frequency,
sit in the anti-alias filter's roll-off and are dropped.
Source code in src/visualdynamics/core/resample.py
alias_frequency
¶
Where content at frequency lands in a record sampled at
sample_rate: |n fs - F| for the whole number n nearest F / fs,
which brings it into the band from zero to the Nyquist frequency.
alias_frequency(4600.0, 5120.0) # 520.0
A frequency at or below the Nyquist frequency is its own alias. Takes a number or an array, and returns the same.
Source code in src/visualdynamics/core/resample.py
line_counts
¶
(lines, kept, dropped) for a frame of frame_length samples:
the lines from zero up to the Nyquist frequency (half the frame),
the ones inside the alias-free span, and the ones between the span
and the Nyquist frequency.
line_counts(1024) # (512, 400, 112)
Source code in src/visualdynamics/core/resample.py
decimation_factor
¶
How many samples of a record at source_rate make one at
rate: a whole number of at least 2.
A rate that does not divide the source rate into a whole factor is refused with the nearest two that do, since keeping every q-th sample is the only resampling done here, and a rate at or above the source rate is refused, since that is not a decimation.
Source code in src/visualdynamics/core/resample.py
anti_alias_filter
¶
anti_alias_filter(rate: float, order: int = ORDER, ratio: float = SPAN_RATIO) -> Filtering
The low-pass a record is filtered through before it is decimated
to rate: its corner at CORNER (1.2) times the alias-free span,
rate over ratio (2.56), of design order order (ORDER, 12).
The corner sits among the dropped lines, above the span and below
the new Nyquist frequency, so the span is flat; a ratio that puts
it at or above the Nyquist frequency is refused.
Source code in src/visualdynamics/core/resample.py
resample
¶
resample(data: TimeHistory, rate: float, *, anti_alias: bool = True, order: int = ORDER) -> TimeHistory
data at the lower sample rate rate, every channel: filtered
through anti_alias_filter at zero phase, then every q-th sample
kept.
slow = resample(history, 5120.0)
rate must divide the record's sample rate into a whole factor of
at least 2 (decimation_factor). The filter runs at the record's
own rate over the whole record, so its edge transients land at the
record's ends; the first sample is kept, and the result's clock is
the source's own at every q-th sample. The records' descriptions
and the shock windows are carried over, the windows being in
seconds from the record's start, which is kept. The averaging is
not: it counts its frame in samples, which spans another time at
the new rate, so the result is framed afresh. The record is
filtered and decimated a row at a time, so the record filtered at
the full rate is never held whole beside it.
With anti_alias=False no filter is applied and an
AliasWarning is issued: whatever the record holds between the new
Nyquist frequency and the old one is folded into the new band, and
no filter applied afterward can find it. Use it only for a record
known to be band-limited below the new span.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
TimeHistory
|
The record, evenly sampled. |
required |
rate
|
float
|
The new sample rate, in Hz. |
required |
anti_alias
|
bool
|
Filter before decimating. |
True
|
order
|
int
|
The anti-alias filter's design order ( |
12
|
Returns:
| Type | Description |
|---|---|
TimeHistory
|
The record at |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the record is not evenly sampled, |
Source code in src/visualdynamics/core/resample.py
level_change
¶
A level change in words: '61.8 dB down', 'unchanged' within a tenth of a dB, '3.1 dB up'.
ordinal
¶
2nd, 3rd, 10th, 21st: the sample kept, in words.
read_resample
¶
read_resample(data: TimeHistory, rate: float, record: int = 0, *, frequencies: float | Sequence[float] | None = None, order: int = ORDER, frame_length: int | None = None) -> ResampleReading
Read what taking channel record of data to rate does.
reading = read_resample(history, 5120.0, frequencies=[4600.0])
print(reading.describe())
The channel is resampled filtered first (resample), decimated
bare, and decimated then low-passed through the same filter at the
new rate, and each of the
three and the channel as recorded is turned into an autospectrum on
one line spacing: Hann frames of frame_length samples at the new
rate (FRAME, 1,024, or the longest power of two the decimated
record holds) and as many samples again times the factor at the
record's own rate, half overlapped. frequencies above the new
Nyquist frequency are folded (alias_frequency) and the level each
spectrum has where each lands is read; frequencies at or below the
new Nyquist frequency do not fold: they are left out of the folds
and named in the reading's notes. With none given, the tallest line the record has above the new Nyquist
frequency is the one folded.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
TimeHistory
|
The record, evenly sampled. |
required |
rate
|
float
|
The new sample rate, in Hz, a whole division of the record's. |
required |
record
|
int
|
Which channel. |
0
|
frequencies
|
float or sequence of float
|
Frequencies above the new Nyquist frequency to fold, in Hz. |
None
|
order
|
int
|
The anti-alias filter's design order. |
12
|
frame_length
|
int
|
The spectra's frame at the new rate, in samples. |
None
|
Returns:
| Type | Description |
|---|---|
ResampleReading
|
The rates, the span and its lines, the folds and the spectra. |
Raises:
| Type | Description |
|---|---|
ValueError
|
As |
Source code in src/visualdynamics/core/resample.py
596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 | |