Process reference: onset
Generated from the docstrings of vaft.process.onset:
13 public functions. Each entry is what
vaft.process.describe("onset.<name>") prints.
All process categories
Overview
Every detector here answers one question about one waveform – when does it
become active, and on what evidence – and returns an OnsetRecord.
Nothing here knows what the waveform is. The diagnostic preprocessing, the
choice of which signal is authoritative for a plasma onset and the verdicts
built on top live in vaft.omas.plasma_timing and vaft.validation.
Three ideas, kept separable because a study of the VEST raw database showed
each does a different job. The study is workflow/plasma_onset/scan_corpus.py
and its table test/data/onset_corpus.json (its summary carries the
counts); the figures quoted below are from the hand-reviewed first pass that
motivated the rules:
- threshold –
baseline + max(fraction * peak, sigma * robust_sigma). The fraction-of-peak term makes the boundary independent of a channel’s noise floor (two H-alpha channels with a ten-fold noise difference agree to 0.2 ms); the sigma term keeps a weak record from being thresholded at its own noise. - persistence – the signal must stay above threshold for
hold_s. 0.5 ms removes the coil-firing pickup spikes from plasma-current records (1 false onset in 84 shots, from 51) and most isolated optical spikes. - morphology – width, prominence and integral of the accepted run. Width
is the one that matters on that corpus: the residual false onsets after
persistence are 0.5-1 ms flashes on failed breakdowns and the noisy burst an
ECH switch-on induces in every channel;
min_width_s = 1 msremoves them (1 in 51 non-plasma shots, and that one is genuine light).
For a pulse-shaped record principal_pulse_onset walks back from the
global maximum, so an isolated excursion is unreachable by construction; its
one weakness is a record with no pulse, where the maximum is the pickup –
pickup_scale measures the record’s own impulsive excursions and a
floor on the pulse amplitude relative to them refuses that case.
Two contracts every consumer can rely on: OnsetRecord.time is always a
sample of the input grid (or None), which is what lets a half-open window
[start, onset) exclude exactly the samples from the onset onward; and no
onset is never the whole record – a detector that finds nothing says so with
time=None and a flag, never by returning the record bounds.
Functions
active_window | The window over which a waveform is active, onset to offset. Convention |
excess_threshold | Baseline, spread, peak and activity threshold of one waveform. |
isolated_excursions | How many runs above a threshold are shorter than a given length. |
median_smooth | Median filter that removes excursions shorter than half the kernel. Convention |
pickup_scale | The record’s own impulsive-excursion amplitude. Convention |
principal_pulse_onset | Onset of the pulse that contains the global maximum. Convention |
principal_pulse_window | (onset, offset) of the pulse holding the global maximum.
|
robust_baseline | Median and robust sigma (1.4826 * MAD) over the reference samples.
|
robust_peak | The representative peak of a record: its largest sustained excursion, not its loudest sample. Convention |
run_features | Width, peak, prominence and integral of one run above a baseline. Convention |
sustained_excess_onset | First run above the threshold that persists and has the right shape. Convention |
zero_crossing_after_excursion | First sample after an anchored excursion where the record changes sign. Convention |
zero_phase_lowpass | Forward-backward Butterworth low-pass: no group delay. Convention |
No function matches.
active_window
Machine-independentConvention-sensitive
source
active_window(time, values, *, fraction=0.02, sigma=5.0, hold_s=0.0005, min_width_s=0.0, min_prominence_sigma=0.0, min_integral_fraction=0.0, gap_s=0.001, post_quiet_s=0.002, principal_only=False, pickup_floor=0.0, impulse_max_s=0.002, reference_mask=None, reference_fraction=0.2, trailing_fraction=0.1, trailing_max_fraction=0.1, end_fraction=None, collapse_fallback=True, collapse_rate_fraction=0.1, collapse_min_drop=0.5, search_mask=None, prefilter_samples=1, cutoff_hz=None, fs=None, order=4)
The window over which a waveform is active, onset to offset.
The detector behind every plasma window in VAFT: what the onset detectors do for the start, plus the harder half, which is where a pulse ends.
| Name | Type | Unit | Description |
|---|---|---|---|
time | array_like | s | Time grid of the record. |
values | array_like | any | The waveform. |
fraction | float, optional | - | Fraction-of-peak term of the onset threshold. |
sigma | float, optional | - | Robust-sigma term of the onset threshold. |
hold_s | float, optional | s | How long a run must stay above the threshold to be a segment. |
min_width_s | float, optional | s | Shortest accepted segment; |
min_prominence_sigma | float, optional | - | How far a segment must rise above its surroundings, in robust sigmas; |
min_integral_fraction | float, optional | - | Fraction of the record’s total positive excess a segment must carry; |
gap_s | float, optional | s | Longest dip below the threshold that does not split a segment. |
post_quiet_s | float, optional | s | A segment beginning within this of the previous one’s end is merged into it. |
principal_only | bool, optional | - | Keep only the segment holding the global maximum. |
pickup_floor | float, optional | - | Principal-segment floor relative to |
impulse_max_s | float, optional | s | Longest run still treated as an impulse. |
reference_mask | array_like of bool or None, optional | - | Samples that define the quiet reference; |
reference_fraction | float, optional | - | Fraction of the record used as the reference when no mask is given. |
trailing_fraction | float, optional | - | Fraction of the record at its end used as the trailing reference. |
trailing_max_fraction | float, optional | - | How far above the leading baseline that trailing level may sit, as a fraction of the peak, for it to be used at all. |
end_fraction | float or None, optional | - | Fraction of the peak the signal must fall below, above the trailing level, for the window to end; |
collapse_fallback | bool, optional | - | Whether to end a window that never falls below the end threshold at the last steep fall. |
collapse_rate_fraction | float, optional | - | How steep that fall must be, as a fraction of the steepest fall in the record. |
collapse_min_drop | float, optional | - | Fraction of the level present before the fall that it must remove. |
search_mask | array_like of bool or None, optional | - | Samples the detector may look at; the peak that scales the threshold is taken inside it too. |
prefilter_samples | int, optional | - | Median-filter kernel applied first; |
cutoff_hz | float or None, optional | Hz | Zero-phase low-pass cutoff; |
fs | float or None, optional | Hz | Sample rate; inferred from |
order | int, optional | - | Butterworth order of that low-pass. |
| Returns | |||
| — | PulseWindow | s |
|
Convention
offset.time is the last sample of the pulse – the last sample above the
end threshold, or, with offset_from_collapse, the last sample of the
quench that ended it – so a half-open consumer window is
[onset.time, time[offset.index + 1]). A window is never assumed: nothing
here reports the record bounds when it found no pulse.
Processing steps · Defaults · Applicability · Limitations · Provenance
Processing steps
- Median-filter over
prefilter_samples, low-pass (zero-phase) when acutoff_hzis given and the grid can carry it. - Threshold as
excess_thresholdagainst the leading reference. - Bridge dips shorter than
gap_s; keep the runs that pass persistence (hold_s) and morphology as segments; merge a segment that begins withinpost_quiet_sof the previous one’s end into it, so a brief quiet moment does not end the window while a real gap does. - With
principal_only, keep only the segment holding the global maximum, guarded by the impulsive-run andpickup_floorrules ofprincipal_pulse_onset; otherwise the window is the envelope of every segment andmultiple_segmentssays when there is more than one. - Judge the offset against a trailing reference – the last
trailing_fractionof the record, used only when it is quiet (spread within three times the leading one) and sits belowtrailing_max_fractionof the peak – atend_fractionof the peak above it. Where that end threshold is never crossed andcollapse_fallbackis on, end the window at the end of the last steep fall after the peak (collapse_rate_fraction,collapse_min_drop) and flagoffset_from_collapse.
The baseline after a pulse need not be the one before it: a VEST plasma-current record settles a few percent of its peak above zero once the plasma is gone, and after a termination the Rogowski keeps reading the induced vessel current – a few to ten percent of the peak, decaying over tens of milliseconds – which no level between it and the trailing baseline separates from plasma. The collapse does. The last fall, not the steepest: a plasma survives a mid-pulse drop and terminates later.
Defaults
The signature values are numerical convenience; trailing_fraction,
trailing_max_fraction, collapse_rate_fraction and
collapse_min_drop are empirical, fixed on the hand-reviewed VEST
discharges and then checked against the whole corpus [corpus]. The VEST
values a pipeline actually runs with are policy [vest.yaml].
Applicability
Machine-independent.
Limitations
The end rule is the weak one. A disruption’s vessel-current tail can sit at a third of the peak for a hundred milliseconds, above any end threshold that still separates plasma from a normal termination, and is then ended by the collapse fallback rather than by the threshold. The threshold is scaled from the amplitude the detector accepted – the largest segment, not the largest sample in the search stretch – because a spike the segment tests refused would otherwise set the level the pulse has to fall below (#726).
And the window is the envelope of its segments, which for a record of two
brief blips tens of milliseconds apart is mostly gap: three corpus records
report windows that are 7 to 19 % above threshold. multiple_segments
says a window has gaps but not how much of it is gap, and the extent is
consumed as a duration – tracked in issue #752.
Provenance
- Issue #409, which introduced these primitives and fixed their rules against the VEST raw database.
workflow/plasma_onset/scan_corpus.pyand the table it writes,test/data/onset_corpus.json: the scan of VEST shots 39900-41700 the thresholds, hold times and widths were judged on.- The VEST values for these rules are policy, not defaults:
vaft/machine_mapping/vest.yamlcarries them inplasma_timing,discharge_timingandplasma_features, resolved byvaft.machine_mapping.utilsand passed in byvaft.omas.plasma_timing,vaft.omas.discharge_timingandvaft.omas.plasma_features.
excess_threshold
Machine-independent
source
excess_threshold(values, reference_mask, *, fraction, sigma, search_mask=None)
Baseline, spread, peak and activity threshold of one waveform.
The threshold is baseline + max(fraction * peak, sigma * robust_sigma).
The fraction-of-peak term makes the boundary independent of a channel’s noise
floor – two VEST H-alpha channels with a ten-fold noise difference agree to
0.2 ms on it – and the sigma term keeps a weak record from being thresholded
at its own noise.
| Name | Type | Unit | Description |
|---|---|---|---|
values | array_like | any | The waveform. |
reference_mask | array_like of bool or None | - | Samples that define the quiet reference, passed to |
fraction | float | - | Fraction of the peak excess the threshold sits at. |
sigma | float | - | Number of robust sigmas the threshold sits at. |
search_mask | array_like of bool or None, optional | - | Samples the detector may look at; the peak that scales the threshold is taken inside it too. |
| Returns | |||
baseline | float | any | Median of the reference samples. |
robust_sigma | float | any | Robust sigma of the reference samples. |
peak | float | any | Largest excess over the baseline inside the search mask, |
threshold | float | any | The activity threshold, in the units of the record, not of the excess |
Defaults · Applicability · Limitations · Provenance
Defaults
None; both terms are the caller’s. The VEST values are a machine-specific setting [vest.yaml].
Applicability
Machine-independent.
Limitations
A negative peak (every sample below the baseline inside the mask) contributes nothing: the fraction term is clipped at zero and the threshold falls back to the sigma term. Detectors treat a non-finite baseline or threshold as no evidence rather than thresholding on it.
Provenance
- Issue #409, which introduced these primitives and fixed their rules against the VEST raw database.
workflow/plasma_onset/scan_corpus.pyand the table it writes,test/data/onset_corpus.json: the scan of VEST shots 39900-41700 the thresholds, hold times and widths were judged on.- The VEST values for these rules are policy, not defaults:
vaft/machine_mapping/vest.yamlcarries them inplasma_timing,discharge_timingandplasma_features, resolved byvaft.machine_mapping.utilsand passed in byvaft.omas.plasma_timing,vaft.omas.discharge_timingandvaft.omas.plasma_features.
isolated_excursions
Machine-independent
source
isolated_excursions(values, threshold, max_run_samples)
Show source vaft/process/onset.py, lines 694–733
def isolated_excursions(values, threshold: float, max_run_samples: int) -> int:
"""How many runs above a threshold are shorter than a given length.
The count of spikes a record carries: the detectors report it as
``isolated_excursions_before_onset`` so a consumer can see how noisy the
stretch before an onset was without re-reading the waveform.
Parameters
----------
values : array_like
The waveform [any].
threshold : float
Level a sample must exceed to be part of a run [any].
max_run_samples : int
Runs strictly shorter than this many samples are counted [-].
Returns
-------
int
Number of such runs [-].
Applicability
-------------
Machine-independent.
Limitations
-----------
Purely a count: which runs, and where, is not reported. The detectors keep
the runs they rejected, with reasons, in ``OnsetRecord.rejected``.
Provenance
----------
.. [409] Issue #409, which introduced these primitives and fixed their rules
against the VEST raw database.
.. [corpus] ``workflow/plasma_onset/scan_corpus.py`` and the table it writes,
``test/data/onset_corpus.json``: the scan of VEST shots 39900-41700 the
thresholds, hold times and widths were judged on.
"""
y = np.asarray(values, dtype=float).reshape(-1)
return sum(1 for a, b in _runs(y > threshold) if (b - a) < int(max_run_samples))How many runs above a threshold are shorter than a given length.
The count of spikes a record carries: the detectors report it as
isolated_excursions_before_onset so a consumer can see how noisy the
stretch before an onset was without re-reading the waveform.
| Name | Type | Unit | Description |
|---|---|---|---|
values | array_like | any | The waveform. |
threshold | float | any | Level a sample must exceed to be part of a run. |
max_run_samples | int | - | Runs strictly shorter than this many samples are counted. |
| Returns | |||
| — | int | - | Number of such runs. |
Applicability · Limitations · Provenance
Applicability
Machine-independent.
Limitations
Purely a count: which runs, and where, is not reported. The detectors keep
the runs they rejected, with reasons, in OnsetRecord.rejected.
Provenance
- Issue #409, which introduced these primitives and fixed their rules against the VEST raw database.
workflow/plasma_onset/scan_corpus.pyand the table it writes,test/data/onset_corpus.json: the scan of VEST shots 39900-41700 the thresholds, hold times and widths were judged on.
median_smooth
Machine-independentConvention-sensitive
source
median_smooth(values, kernel_samples)
Show source vaft/process/onset.py, lines 329–390
def median_smooth(values, kernel_samples: int) -> np.ndarray:
"""Median filter that removes excursions shorter than half the kernel.
The prefilter every optical detector runs first: an isolated digitizer spike
is not evidence of light, and a median of an odd kernel removes any excursion
narrower than half of it without moving the edges of the ones it keeps.
Parameters
----------
values : array_like
The waveform; non-finite samples are linearly interpolated first [any].
kernel_samples : int
Median kernel width in samples, forced odd; ``1`` or less returns the
finite-filled input unchanged [-].
Returns
-------
numpy.ndarray
The filtered waveform, same length and grid as the input [any].
Defaults
--------
None; the kernel is the caller's. The VEST widths are a machine-specific
setting [vest.yaml]: five samples on the slow optical and current
channels, one (no filtering) where the rule wants the raw record.
Convention
----------
Zero-phase by construction: a median filter of an odd kernel introduces no
group delay, so an onset read off the output is not shifted from the input's.
An even ``kernel_samples`` is incremented rather than applied, because an even
median averages two order statistics and does shift edges.
Applicability
-------------
Machine-independent.
Limitations
-----------
A kernel at or above the record length is clipped to the longest odd kernel
that fits. A flat top wider than the kernel survives, which is what lets
:func:`robust_peak` still see a railed digitizer.
Provenance
----------
.. [409] Issue #409, which introduced these primitives and fixed their rules
against the VEST raw database.
.. [corpus] ``workflow/plasma_onset/scan_corpus.py`` and the table it writes,
``test/data/onset_corpus.json``: the scan of VEST shots 39900-41700 the
thresholds, hold times and widths were judged on.
"""
y = _fill_non_finite(np.asarray(values, dtype=float).reshape(-1))
k = int(kernel_samples)
if k <= 1:
return y
if k % 2 == 0:
k += 1
if k >= y.size:
k = y.size if y.size % 2 else y.size - 1
if k <= 1:
return y
return scipy_signal.medfilt(y, kernel_size=k)Median filter that removes excursions shorter than half the kernel.
The prefilter every optical detector runs first: an isolated digitizer spike is not evidence of light, and a median of an odd kernel removes any excursion narrower than half of it without moving the edges of the ones it keeps.
| Name | Type | Unit | Description |
|---|---|---|---|
values | array_like | any | The waveform; non-finite samples are linearly interpolated first. |
kernel_samples | int | - | Median kernel width in samples, forced odd; |
| Returns | |||
| — | numpy.ndarray | any | The filtered waveform, same length and grid as the input. |
Convention
Zero-phase by construction: a median filter of an odd kernel introduces no
group delay, so an onset read off the output is not shifted from the input’s.
An even kernel_samples is incremented rather than applied, because an even
median averages two order statistics and does shift edges.
Defaults · Applicability · Limitations · Provenance
Defaults
None; the kernel is the caller’s. The VEST widths are a machine-specific setting [vest.yaml]: five samples on the slow optical and current channels, one (no filtering) where the rule wants the raw record.
Applicability
Machine-independent.
Limitations
A kernel at or above the record length is clipped to the longest odd kernel
that fits. A flat top wider than the kernel survives, which is what lets
robust_peak still see a railed digitizer.
Provenance
- Issue #409, which introduced these primitives and fixed their rules against the VEST raw database.
workflow/plasma_onset/scan_corpus.pyand the table it writes,test/data/onset_corpus.json: the scan of VEST shots 39900-41700 the thresholds, hold times and widths were judged on.
pickup_scale
Machine-independentConvention-sensitive
source
pickup_scale(values, baseline, robust_sigma, dt, *, impulse_max_s=0.002, sigma=6.0)
Show source vaft/process/onset.py, lines 736–806
def pickup_scale(values, baseline: float, robust_sigma: float, dt: float,
*, impulse_max_s: float = 2.0e-3, sigma: float = 6.0) -> float:
"""The record's own impulsive-excursion amplitude.
The largest absolute excess over the baseline among runs above
``sigma * robust_sigma`` that last less than ``impulse_max_s`` -- on a
plasma-current record, the coil-firing pickup measured on that very shot.
:func:`principal_pulse_onset` compares a candidate pulse against it, which is
what refuses a record whose maximum *is* the pickup.
Parameters
----------
values : array_like
The waveform [any].
baseline : float
Level the excess is measured from [any].
robust_sigma : float
Robust spread of the reference, as returned by
:func:`robust_baseline` [any].
dt : float
Sample spacing, used to turn ``impulse_max_s`` into samples [s].
impulse_max_s : float, optional
Longest run still counted as an impulse [s].
sigma : float, optional
Number of robust sigmas a sample must clear to be part of a run [-].
Returns
-------
float
The largest such excursion, or ``0.0`` when there is none or the
baseline or spread is not usable [any].
Defaults
--------
``sigma = 6`` is empirical and deliberately above the detectors' five: white
noise does not reach six sigma in a record of a few thousand samples, so the
scale measures pickup rather than the noise tail. ``impulse_max_s = 2 ms``
is empirical too, the longest coil-firing transient seen on VEST
plasma-current records [corpus].
Convention
----------
Absolute: a pickup transient of either sign counts, because the induced
excursion's polarity depends on the coil and the winding, not on the plasma.
Applicability
-------------
Machine-independent. Both defaults were fixed on VEST records.
Limitations
-----------
The scale is measured wherever the caller points it. Inside a pulse the
pulse's own noisy threshold crossings would be counted, so
:func:`principal_pulse_onset` measures it outside the principal run, one
impulse length clear of both edges.
Provenance
----------
.. [409] Issue #409, which introduced these primitives and fixed their rules
against the VEST raw database.
.. [corpus] ``workflow/plasma_onset/scan_corpus.py`` and the table it writes,
``test/data/onset_corpus.json``: the scan of VEST shots 39900-41700 the
thresholds, hold times and widths were judged on.
"""
y = np.asarray(values, dtype=float).reshape(-1)
if not (np.isfinite(baseline) and np.isfinite(robust_sigma) and robust_sigma > 0):
return 0.0
excess = np.abs(y - baseline)
max_samples = max(1, int(round(float(impulse_max_s) / float(dt))))
peaks = [excess[a:b].max() for a, b in _runs(excess > sigma * robust_sigma) if (b - a) < max_samples]
return float(max(peaks)) if peaks else 0.0The record’s own impulsive-excursion amplitude.
The largest absolute excess over the baseline among runs above
sigma * robust_sigma that last less than impulse_max_s – on a
plasma-current record, the coil-firing pickup measured on that very shot.
principal_pulse_onset compares a candidate pulse against it, which is
what refuses a record whose maximum is the pickup.
| Name | Type | Unit | Description |
|---|---|---|---|
values | array_like | any | The waveform. |
baseline | float | any | Level the excess is measured from. |
robust_sigma | float | any | Robust spread of the reference, as returned by |
dt | float | s | Sample spacing, used to turn |
impulse_max_s | float, optional | s | Longest run still counted as an impulse. |
sigma | float, optional | - | Number of robust sigmas a sample must clear to be part of a run. |
| Returns | |||
| — | float | any | The largest such excursion, or |
Convention
Absolute: a pickup transient of either sign counts, because the induced excursion’s polarity depends on the coil and the winding, not on the plasma.
Defaults · Applicability · Limitations · Provenance
Defaults
sigma = 6 is empirical and deliberately above the detectors’ five: white
noise does not reach six sigma in a record of a few thousand samples, so the
scale measures pickup rather than the noise tail. impulse_max_s = 2 ms
is empirical too, the longest coil-firing transient seen on VEST
plasma-current records [corpus].
Applicability
Machine-independent. Both defaults were fixed on VEST records.
Limitations
The scale is measured wherever the caller points it. Inside a pulse the
pulse’s own noisy threshold crossings would be counted, so
principal_pulse_onset measures it outside the principal run, one
impulse length clear of both edges.
Provenance
- Issue #409, which introduced these primitives and fixed their rules against the VEST raw database.
workflow/plasma_onset/scan_corpus.pyand the table it writes,test/data/onset_corpus.json: the scan of VEST shots 39900-41700 the thresholds, hold times and widths were judged on.
principal_pulse_onset
Machine-independentConvention-sensitive
source
principal_pulse_onset(time, values, *, fraction=0.02, sigma=5.0, reference_mask=None, reference_fraction=0.2, search_mask=None, cutoff_hz=None, fs=None, order=4, pickup_floor=3.0, impulse_max_s=0.002, bridge_samples=2)
Onset of the pulse that contains the global maximum.
An excursion not connected to the maximum can never be chosen, which is what makes this the detector for a record known to be pulse-shaped. A record with no pulse has its maximum at the pickup, and two floors guard that case.
| Name | Type | Unit | Description |
|---|---|---|---|
time | array_like | s | Time grid of the record. |
values | array_like | any | The waveform. |
fraction | float, optional | - | Fraction-of-peak term of the threshold. |
sigma | float, optional | - | Robust-sigma term of the threshold. |
reference_mask | array_like of bool or None, optional | - | Samples that define the quiet reference; |
reference_fraction | float, optional | - | Fraction of the record used as the reference when no mask is given. |
search_mask | array_like of bool or None, optional | - | Samples the detector may look at; the peak that scales the threshold is taken inside it too. |
cutoff_hz | float or None, optional | Hz | Zero-phase low-pass cutoff; |
fs | float or None, optional | Hz | Sample rate; inferred from |
order | int, optional | - | Butterworth order of that low-pass. |
pickup_floor | float, optional | - | How many times the record’s own impulsive scale the pulse must exceed; |
impulse_max_s | float, optional | s | Longest run still treated as an impulse, both for the principal-run test and for |
bridge_samples | int, optional | - | Longest dip below the threshold that does not split the run. |
| Returns | |||
| — | OnsetRecord | s |
|
Convention
The onset is the run’s first sample. The low-pass is zero-phase, so the onset is not shifted by the filtering.
Processing steps · Defaults · Applicability · Limitations · Provenance
Processing steps
- Low-pass the record (zero-phase) when a
cutoff_hzis given and the grid can carry it; otherwise flaglowpass_skipped. - Settle the reference, measure baseline and spread, threshold as
excess_threshold. - Refuse when the peak does not clear
sigmarobust sigmas (peak_below_noise). - Walk back and forward from the maximum inside
search_maskwhile the excess stays above the threshold; that connected run is the pulse. - Refuse a run narrower than
impulse_max_s(principal_run_impulsive) and a peak belowpickup_floortimespickup_scalemeasured outside the run (peak_below_pickup_floor); otherwise the onset is the run’s first sample.
Defaults
Numerical convenience, as in sustained_excess_onset; pickup_floor
and impulse_max_s are empirical, fixed on VEST plasma-current records
[corpus]. The VEST values are policy [vest.yaml].
Applicability
Machine-independent.
Limitations
Only for a record whose activity is one pulse: on a record with two comparable
pulses the answer is the onset of whichever holds the maximum, with no flag
saying the other exists. active_window reports that case as
multiple_segments.
Provenance
- Issue #409, which introduced these primitives and fixed their rules against the VEST raw database.
workflow/plasma_onset/scan_corpus.pyand the table it writes,test/data/onset_corpus.json: the scan of VEST shots 39900-41700 the thresholds, hold times and widths were judged on.- The VEST values for these rules are policy, not defaults:
vaft/machine_mapping/vest.yamlcarries them inplasma_timing,discharge_timingandplasma_features, resolved byvaft.machine_mapping.utilsand passed in byvaft.omas.plasma_timing,vaft.omas.discharge_timingandvaft.omas.plasma_features.
principal_pulse_window
Machine-independent
source
principal_pulse_window(time, values, **kwargs)
Show source vaft/process/onset.py, lines 1754–1818
def principal_pulse_window(time, values, **kwargs) -> tuple[OnsetRecord, OnsetRecord]:
"""``(onset, offset)`` of the pulse holding the global maximum.
One :func:`active_window` call with ``principal_only=True`` and the principal
defaults, for callers that want the pair of records rather than the window
object. Every other keyword argument is forwarded to :func:`active_window`
unchanged.
Parameters
----------
time : array_like
Time grid of the record [s].
values : array_like
The waveform [any].
Returns
-------
onset : OnsetRecord
The principal run's first sample -- exactly what
:func:`principal_pulse_onset` returns -- or ``None`` with the window's
flags [s].
offset : OnsetRecord
The principal run's last sample, carrying the window's flags, so
``offset_at_record_end`` and ``offset_from_collapse`` reach a caller that
never sees the :class:`PulseWindow` [s].
Defaults
--------
``pickup_floor = 3`` and ``hold_s = 0`` are set here unless the caller
overrides them: empirical, and the pair that makes this agree with
:func:`principal_pulse_onset` -- the pulse is the run holding the maximum, so
persistence is redundant and the pickup floor does the refusing.
Applicability
-------------
Machine-independent.
Limitations
-----------
Both records carry the *window's* evidence rather than an onset detector's, so
``evidence`` is keyed as :func:`active_window` keys it.
Provenance
----------
.. [409] Issue #409, which introduced these primitives and fixed their rules
against the VEST raw database.
.. [corpus] ``workflow/plasma_onset/scan_corpus.py`` and the table it writes,
``test/data/onset_corpus.json``: the scan of VEST shots 39900-41700 the
thresholds, hold times and widths were judged on.
"""
kwargs.setdefault("pickup_floor", 3.0)
kwargs.setdefault("hold_s", 0.0)
window = active_window(time, values, principal_only=True, **kwargs)
if not window.found:
none = OnsetRecord(time=None, index=None, method="principal_pulse", evidence=dict(window.evidence),
flags=window.flags)
return none, OnsetRecord(time=None, index=None, method="principal_pulse_offset",
evidence=dict(window.evidence), flags=window.flags)
onset = OnsetRecord(time=window.onset.time, index=window.onset.index, method="principal_pulse",
evidence=dict(window.evidence), flags=window.onset.flags,
rejected=window.onset.rejected, accepted=window.onset.accepted)
offset = OnsetRecord(time=window.offset.time, index=window.offset.index,
method="principal_pulse_offset", evidence=dict(window.evidence),
flags=window.flags, accepted=window.offset.accepted)
return onset, offset(onset, offset) of the pulse holding the global maximum.
One active_window call with principal_only=True and the principal
defaults, for callers that want the pair of records rather than the window
object. Every other keyword argument is forwarded to active_window
unchanged.
| Name | Type | Unit | Description |
|---|---|---|---|
time | array_like | s | Time grid of the record. |
values | array_like | any | The waveform. |
| Returns | |||
onset | OnsetRecord | s | The principal run’s first sample – exactly what |
offset | OnsetRecord | s | The principal run’s last sample, carrying the window’s flags, so |
Defaults · Applicability · Limitations · Provenance
Defaults
pickup_floor = 3 and hold_s = 0 are set here unless the caller
overrides them: empirical, and the pair that makes this agree with
principal_pulse_onset – the pulse is the run holding the maximum, so
persistence is redundant and the pickup floor does the refusing.
Applicability
Machine-independent.
Limitations
Both records carry the window’s evidence rather than an onset detector’s, so
evidence is keyed as active_window keys it.
Provenance
- Issue #409, which introduced these primitives and fixed their rules against the VEST raw database.
workflow/plasma_onset/scan_corpus.pyand the table it writes,test/data/onset_corpus.json: the scan of VEST shots 39900-41700 the thresholds, hold times and widths were judged on.
robust_baseline
Machine-independent
source
robust_baseline(values, reference_mask=None)
Show source vaft/process/onset.py, lines 266–326
def robust_baseline(values, reference_mask=None) -> tuple[float, float]:
"""Median and robust sigma (``1.4826 * MAD``) over the reference samples.
The MAD is computed here rather than through ``vaft.formula`` so this module
stays a scipy-only import.
Parameters
----------
values : array_like
The waveform [any].
reference_mask : array_like of bool or None, optional
Samples to measure over; ``None`` uses all of them [-].
Returns
-------
baseline : float
Median of the finite reference samples, ``nan`` when fewer than two
exist [any].
robust_sigma : float
``1.4826`` times the median absolute deviation about that median, on the
same samples, ``nan`` under the same condition [any].
Defaults
--------
The MAD-to-sigma factor ``1.4826`` (``MAD_TO_SIGMA``) is a physical constant
of the Gaussian distribution, not a tuning value: it makes the returned
spread equal the standard deviation for Gaussian noise.
Assumptions
-----------
The reference samples are quiet. Nothing here checks that; a caller that
cannot guarantee it asks a detector, which settles the reference itself and
flags ``reference_contaminated``.
Applicability
-------------
Machine-independent.
Limitations
-----------
``(nan, nan)`` on fewer than two finite reference samples, so a caller can
flag the reference rather than threshold against garbage. A reference of
identical samples gives ``robust_sigma == 0``; a threshold built on it
degenerates to the fraction-of-peak term alone.
Provenance
----------
.. [409] Issue #409, which introduced these primitives and fixed their rules
against the VEST raw database.
.. [corpus] ``workflow/plasma_onset/scan_corpus.py`` and the table it writes,
``test/data/onset_corpus.json``: the scan of VEST shots 39900-41700 the
thresholds, hold times and widths were judged on.
"""
y = np.asarray(values, dtype=float).reshape(-1)
if reference_mask is not None:
y = y[np.asarray(reference_mask, dtype=bool).reshape(-1)]
y = y[np.isfinite(y)]
if y.size < 2:
return float("nan"), float("nan")
median = float(np.median(y))
return median, float(MAD_TO_SIGMA * np.median(np.abs(y - median)))Median and robust sigma (1.4826 * MAD) over the reference samples.
The MAD is computed here rather than through vaft.formula so this module
stays a scipy-only import.
| Name | Type | Unit | Description |
|---|---|---|---|
values | array_like | any | The waveform. |
reference_mask | array_like of bool or None, optional | - | Samples to measure over; |
| Returns | |||
baseline | float | any | Median of the finite reference samples, |
robust_sigma | float | any |
|
Defaults · Assumptions · Applicability · Limitations · Provenance
Defaults
The MAD-to-sigma factor 1.4826 (MAD_TO_SIGMA) is a physical constant
of the Gaussian distribution, not a tuning value: it makes the returned
spread equal the standard deviation for Gaussian noise.
Assumptions
The reference samples are quiet. Nothing here checks that; a caller that
cannot guarantee it asks a detector, which settles the reference itself and
flags reference_contaminated.
Applicability
Machine-independent.
Limitations
(nan, nan) on fewer than two finite reference samples, so a caller can
flag the reference rather than threshold against garbage. A reference of
identical samples gives robust_sigma == 0; a threshold built on it
degenerates to the fraction-of-peak term alone.
Provenance
- Issue #409, which introduced these primitives and fixed their rules against the VEST raw database.
workflow/plasma_onset/scan_corpus.pyand the table it writes,test/data/onset_corpus.json: the scan of VEST shots 39900-41700 the thresholds, hold times and widths were judged on.
robust_peak
Machine-independentConvention-sensitive
source
robust_peak(time, values, *, polarity='positive', prefilter_samples=5, cutoff_hz=None, fs=None, order=4, sigma=5.0, level_fraction=0.5, min_width_s=0.001, max_candidates=8, edge_samples=1, impulse_max_s=0.002, reference_mask=None, reference_fraction=0.2, search_mask=None, bridge_samples=2)
The representative peak of a record: its largest sustained excursion, not its loudest sample.
The measurement behind every peak value VAFT reports – the plasma current, a line’s brightness, the diamagnetic flux – for records where the loudest sample is routinely a coil-firing impulse or a digitizer glitch.
| Name | Type | Unit | Description |
|---|---|---|---|
time | array_like | s | Time grid of the record. |
values | array_like | any | The waveform. |
polarity | {'positive', 'negative', 'absolute'}, optional | - | What counts as up. |
prefilter_samples | int, optional | - | Median-filter kernel applied first; |
cutoff_hz | float or None, optional | Hz | Zero-phase low-pass cutoff; |
fs | float or None, optional | Hz | Sample rate; inferred from |
order | int, optional | - | Butterworth order of that low-pass. |
sigma | float, optional | - | How many robust sigmas the excursion must clear. |
level_fraction | float, optional | - | Height at which the run holding a candidate is measured, as a fraction of the candidate’s own excursion. |
min_width_s | float, optional | s | How long that run must last for the candidate to be a peak rather than a spike. |
max_candidates | int, optional | - | How many candidates may be refused before the record is given up on; must be at least 1. |
edge_samples | int, optional | - | How close to the edge of the search stretch a peak may lie before |
impulse_max_s | float, optional | s | Longest run treated as an impulse, for both the refusal mask and the reported |
reference_mask | array_like of bool or None, optional | - | Samples that define the quiet reference; |
reference_fraction | float, optional | - | Fraction of the record used as the reference when no mask is given. |
search_mask | array_like of bool or None, optional | - | Samples the detector may look at; the peak that scales the threshold is taken inside it too. |
bridge_samples | int, optional | - | Longest dip below a candidate’s level that does not split its run. |
| Returns | |||
| — | PeakRecord | any |
|
Convention
polarity decides the sign of the answer: with negative or
absolute the reported value is still the record’s own signed sample,
not the score, so a VEST diamagnetic flux keeps its stored sign (positive
for a paramagnetic plasma, #1196). value is the smoothed sample; the raw one is in the
evidence.
Raises
ValueErrorWhenpolarityis not one of the three, ormax_candidatesis below one.
Processing steps · Defaults · Applicability · Limitations · Provenance
Processing steps
- Median-filter over
prefilter_samplesand, when acutoff_hzis given and the grid can carry it, zero-phase low-pass. - Settle the reference and take the baseline as its median.
- Orient the record by
polarity:positivescores the excess over the baseline,negativethe deficit,absolutethe magnitude. - Take the largest score inside
search_maskas a candidate; the run holding it atlevel_fractionof its height must lastmin_width_s. A narrower run is a spike: it is refused (rejected, reasonimpulsive), masked whole – shoulders included, bounded byimpulse_max_sand by the baseline crossing, so it cannot come back – and the next candidate is tried, up tomax_candidatestimes. - Report the accepted run’s extreme sample, with the raw record’s own maximum kept in the evidence.
Defaults
The signature values are numerical convenience. The VEST values are policy
in the plasma_features block [vest.yaml], per signal: the plasma current,
H-alpha, each impurity line and the diamagnetic flux carry their own
prefilter, sigma, level_fraction and min_width_s, and an
H-alpha value is never reused for an impurity line.
Applicability
Machine-independent.
Limitations
Flags on a found peak say when the answer is not the loudest sample:
peak_is_spike (the raw maximum lay in a refused run),
raw_max_outside_run (it lies outside the accepted run for any other
reason), peak_at_window_edge (the excursion may continue past the search
stretch), peak_plateau (the raw record holds its extreme value for three
samples or more – a railed digitizer), reference_flat (the reference has
no spread; a peak is still reported, because an integrated record can have an
exactly zero lead). A record whose every excursion is impulsive returns
all_candidates_impulsive rather than a value.
Provenance
- Issue #409, which introduced these primitives and fixed their rules against the VEST raw database.
workflow/plasma_onset/scan_corpus.pyand the table it writes,test/data/onset_corpus.json: the scan of VEST shots 39900-41700 the thresholds, hold times and widths were judged on.- The VEST values for these rules are policy, not defaults:
vaft/machine_mapping/vest.yamlcarries them inplasma_timing,discharge_timingandplasma_features, resolved byvaft.machine_mapping.utilsand passed in byvaft.omas.plasma_timing,vaft.omas.discharge_timingandvaft.omas.plasma_features.
run_features
Machine-independentConvention-sensitive
source
run_features(time, values, baseline, start, stop)
Show source vaft/process/onset.py, lines 621–691
def run_features(time, values, baseline: float, start: int, stop: int) -> RunFeatures:
"""Width, peak, prominence and integral of one run above a baseline.
The temporal shape of a candidate run, on which the morphology rules of the
detectors are stated. Prominence comes from ``scipy.signal.peak_prominences``
on the whole excess record, so it measures how far the run rises above its
surroundings, not above the baseline.
Parameters
----------
time : array_like
Time grid of the record; must be as long as ``values`` [s].
values : array_like
The waveform [any].
baseline : float
Level the excess is measured from [any].
start, stop : int
Half-open index bounds of the run, ``values[start:stop]`` [-].
Returns
-------
RunFeatures
``start_time`` and ``end_time`` (first and last sample of the run) [s],
``width_s`` (``samples * dt``) [s], ``samples`` [-], ``peak`` and
``prominence`` (excess over the baseline) [any], ``peak_time`` [s] and
``integral`` (excess integrated over the run) [any*s].
Convention
----------
``end_time`` is the run's last sample, not the first sample after it, so a
run of one sample has ``start_time == end_time`` and ``width_s == dt``. The
sample spacing is the median of ``diff(time)``, so a record with a few
irregular samples still gets a representative width.
Applicability
-------------
Machine-independent.
Limitations
-----------
A run touching either end of the record has no surroundings on that side;
``prominence`` then falls back to the run's own peak excess. Nothing checks
that ``start`` and ``stop`` bound a run that is actually above the baseline.
Provenance
----------
.. [409] Issue #409, which introduced these primitives and fixed their rules
against the VEST raw database.
.. [corpus] ``workflow/plasma_onset/scan_corpus.py`` and the table it writes,
``test/data/onset_corpus.json``: the scan of VEST shots 39900-41700 the
thresholds, hold times and widths were judged on.
"""
t, y = _as_arrays(time, values)
seg = y[start:stop] - baseline
dt = float(np.median(np.diff(t)))
i_max = start + int(np.argmax(seg))
excess = y - baseline
if 0 < i_max < y.size - 1:
prominence = float(scipy_signal.peak_prominences(excess, [i_max])[0][0])
else:
prominence = float(seg.max())
return RunFeatures(
start_time=float(t[start]),
end_time=float(t[stop - 1]),
width_s=float((stop - start) * dt),
samples=int(stop - start),
peak=float(seg.max()),
peak_time=float(t[i_max]),
prominence=prominence,
integral=float(np.sum(seg) * dt),
)Width, peak, prominence and integral of one run above a baseline.
The temporal shape of a candidate run, on which the morphology rules of the
detectors are stated. Prominence comes from scipy.signal.peak_prominences
on the whole excess record, so it measures how far the run rises above its
surroundings, not above the baseline.
| Name | Type | Unit | Description |
|---|---|---|---|
time | array_like | s | Time grid of the record; must be as long as |
values | array_like | any | The waveform. |
baseline | float | any | Level the excess is measured from. |
start, stop | int | - | Half-open index bounds of the run, |
| Returns | |||
| — | RunFeatures | any*s |
|
Convention
end_time is the run’s last sample, not the first sample after it, so a
run of one sample has start_time == end_time and width_s == dt. The
sample spacing is the median of diff(time), so a record with a few
irregular samples still gets a representative width.
Applicability · Limitations · Provenance
Applicability
Machine-independent.
Limitations
A run touching either end of the record has no surroundings on that side;
prominence then falls back to the run’s own peak excess. Nothing checks
that start and stop bound a run that is actually above the baseline.
Provenance
- Issue #409, which introduced these primitives and fixed their rules against the VEST raw database.
workflow/plasma_onset/scan_corpus.pyand the table it writes,test/data/onset_corpus.json: the scan of VEST shots 39900-41700 the thresholds, hold times and widths were judged on.
sustained_excess_onset
Machine-independentConvention-sensitive
source
sustained_excess_onset(time, values, *, fraction=0.02, sigma=5.0, hold_s=0.0005, min_width_s=0.0, min_prominence_sigma=0.0, min_integral_fraction=0.0, reference_mask=None, reference_fraction=0.2, search_mask=None, prefilter_samples=1, bridge_samples=2)
First run above the threshold that persists and has the right shape.
The general-purpose detector: it makes no assumption that the record is pulse-shaped, so it is the one to use where the first activity matters and a later, larger excursion does not.
| Name | Type | Unit | Description |
|---|---|---|---|
time | array_like | s | Time grid of the record. |
values | array_like | any | The waveform. |
fraction | float, optional | - | Fraction-of-peak term of the threshold. |
sigma | float, optional | - | Robust-sigma term of the threshold. |
hold_s | float, optional | s | How long a run must stay above the threshold. |
min_width_s | float, optional | s | Shortest accepted run; |
min_prominence_sigma | float, optional | - | How far a run must rise above its surroundings, in robust sigmas; |
min_integral_fraction | float, optional | - | Fraction of the record’s total positive excess a run must carry; |
reference_mask | array_like of bool or None, optional | - | Samples that define the quiet reference; |
reference_fraction | float, optional | - | Fraction of the record used as the reference when no mask is given. |
search_mask | array_like of bool or None, optional | - | Samples the detector may look at; the peak that scales the threshold is taken inside it too. |
prefilter_samples | int, optional | - | Median-filter kernel applied first; |
bridge_samples | int, optional | - | Longest dip below the threshold that does not split a run. |
| Returns | |||
| — | OnsetRecord | s |
|
Convention
The onset is the run’s first sample, so a half-open consumer window
[start, onset) excludes exactly the samples from the onset onward.
Processing steps · Defaults · Applicability · Limitations · Provenance
Processing steps
- Median-filter the record over
prefilter_samples(an optical channel’s isolated spikes are removed before they can be counted). - Settle the reference stretch and measure baseline and spread on it.
- Threshold as
excess_threshold; refuse the record outright when the baseline, spread or peak is degenerate. - Bridge gaps of at most
bridge_samplesbelow the threshold, so a single noise sample during a rise does not split the run. - Walk the runs in time order; the first that passes persistence
(
hold_s) and morphology (min_width_s,min_prominence_sigma,min_integral_fraction) is the onset, at its first sample. Every run that fails is kept inrejectedwith the reason.
Defaults
The signature values are numerical convenience – a working detector for a caller with no policy – and are documented as rules, not as numbers to copy. The VEST values are policy [vest.yaml]; the reasoning behind their magnitudes, and the false-onset counts that fixed them, is in the module docstring and in the corpus table [corpus].
Applicability
Machine-independent.
Limitations
No pulse shape is assumed, so an early isolated excursion that passes both
persistence and morphology is the onset. Where the record is known to be
pulse-shaped and the principal pulse is what is wanted,
principal_pulse_onset is unreachable by such an excursion by
construction.
Provenance
- Issue #409, which introduced these primitives and fixed their rules against the VEST raw database.
workflow/plasma_onset/scan_corpus.pyand the table it writes,test/data/onset_corpus.json: the scan of VEST shots 39900-41700 the thresholds, hold times and widths were judged on.- The VEST values for these rules are policy, not defaults:
vaft/machine_mapping/vest.yamlcarries them inplasma_timing,discharge_timingandplasma_features, resolved byvaft.machine_mapping.utilsand passed in byvaft.omas.plasma_timing,vaft.omas.discharge_timingandvaft.omas.plasma_features.
zero_crossing_after_excursion
Machine-independentConvention-sensitive
source
zero_crossing_after_excursion(time, values, *, anchor_time, fraction=0.05, sigma=5.0, hold_s=0.0005, anchor_tolerance_s=0.0005, approach_fraction=0.1, approach_hysteresis=2.0, reference_mask=None, reference_fraction=0.2, search_mask=None, prefilter_samples=1, bridge_samples=2)
First sample after an anchored excursion where the record changes sign.
Written for a drive waveform: the loop voltage crosses zero once the ohmic swing that drove it is over, and that crossing – not the record’s loudest moment – is the event. Anchoring is what makes a later, larger pulse irrelevant: the excursion is judged where the drive starts.
| Name | Type | Unit | Description |
|---|---|---|---|
time | array_like | s | Time grid of the record. |
values | array_like | any | The waveform. |
anchor_time | float | s | Where the excursion is expected to start, usually another detector’s onset. |
fraction | float, optional | - | Fraction-of-peak term of the excursion threshold. |
sigma | float, optional | - | Robust-sigma term of the excursion threshold. |
hold_s | float, optional | s | How long the excursion must stay above that threshold. |
anchor_tolerance_s | float, optional | s | How far the run’s start may lie from the anchor when it does not contain it. |
approach_fraction | float, optional | - | Fraction of the extremum the decay must reach to count as an approach |
approach_hysteresis | float, optional | - | Multiple of that level the record must climb back above for the approach to be reported as one that did not cross; the hysteresis keeps noise around the level from counting as a re-rise. |
reference_mask | array_like of bool or None, optional | - | Samples that define the quiet reference; |
reference_fraction | float, optional | - | Fraction of the record used as the reference when no mask is given. |
search_mask | array_like of bool or None, optional | - | Samples the detector may look at; the peak that scales the threshold is taken inside it too. |
prefilter_samples | int, optional | - | Median-filter kernel applied first; |
bridge_samples | int, optional | - | Longest dip below the threshold that does not split the excursion. |
| Returns | |||
| — | OnsetRecord | s |
|
Convention
The sign is measured relative to the baseline, not to zero, so a record with an offset crosses where it returns through its own quiet level. The crossing must be strict: a sample exactly at the baseline is not one.
Processing steps · Defaults · Applicability · Limitations · Provenance
Processing steps
- Median-filter over
prefilter_samples, settle the reference, measure baseline and spread. - Threshold
|values - baseline|atmax(fraction * peak, sigma * robust_sigma), the peak taken insidesearch_mask. - Take the first sustained run (
hold_s) that starts withinanchor_tolerance_sofanchor_timeor contains the anchor sample; with none, returnno_excursion_at_anchor. - From that run’s extremum, walk forward to the first sample whose baseline-relative sign is opposite to the extremum’s – strictly, so a sample exactly at the baseline is not a crossing.
- Watch the decay in between: when the deviation drops below
approach_fractionof the extremum and climbs back aboveapproach_hysteresistimes that level before crossing, flagapproached_without_crossingand record the minimum and its time.
Defaults
The signature values are numerical convenience; the VEST loop-voltage values – the
fraction, the hold, the anchor tolerance and both approach numbers – are
policy in the discharge_timing.vloop block [vest.yaml].
Applicability
Machine-independent.
Limitations
One crossing only, the first after the anchored excursion. A record that
decays towards the baseline and turns back without reaching it is reported as
approached_without_crossing and no_zero_crossing – the flag is
evidence for a consumer, not an event.
Provenance
- Issue #409, which introduced these primitives and fixed their rules against the VEST raw database.
workflow/plasma_onset/scan_corpus.pyand the table it writes,test/data/onset_corpus.json: the scan of VEST shots 39900-41700 the thresholds, hold times and widths were judged on.- The VEST values for these rules are policy, not defaults:
vaft/machine_mapping/vest.yamlcarries them inplasma_timing,discharge_timingandplasma_features, resolved byvaft.machine_mapping.utilsand passed in byvaft.omas.plasma_timing,vaft.omas.discharge_timingandvaft.omas.plasma_features.
zero_phase_lowpass
Machine-independentConvention-sensitive
source
zero_phase_lowpass(values, cutoff_hz, fs, order=4)
Show source vaft/process/onset.py, lines 393–462
def zero_phase_lowpass(values, cutoff_hz: float, fs: float, order: int = 4) -> np.ndarray:
"""Forward-backward Butterworth low-pass: no group delay.
An onset read off the output is not shifted from the input's. A causal filter
of the same order moved the plasma-current onset by +0.1 to +0.9 ms on VEST
records; this one by less than a sample.
Parameters
----------
values : array_like
The waveform; non-finite samples are linearly interpolated first [any].
cutoff_hz : float
Low-pass cutoff [Hz].
fs : float
Sample rate of the record [Hz].
order : int, optional
Butterworth order; the filter is applied twice, so the effective
roll-off is twice this [-].
Returns
-------
numpy.ndarray
The filtered waveform, same length and grid as the input [any].
Raises
------
ValueError
When the record has no more samples than ``filtfilt``'s padding length,
``3 * (order + 1)``. The detectors call :func:`_too_short` first and
report ``record_too_short`` instead of raising.
Defaults
--------
``order = 4`` is a numerical convenience: the same order the VEST soft X-ray
viewer and ``vaft.process.signal_processing`` use, steep enough to matter and
short enough to pad on a few-thousand-sample record.
Convention
----------
Zero-phase (``scipy.signal.filtfilt``, forward then backward), which is
mandatory wherever an onset time is read off the result and is the opposite
choice from ``vaft.process.signal_processing.butterworth_lowpass``, whose
default is causal to match the validated viewer.
Applicability
-------------
Machine-independent.
Limitations
-----------
A cutoff at or above the record's Nyquist frequency cannot be designed; the
detectors detect that case themselves and report ``lowpass_skipped`` rather
than filtering.
Provenance
----------
.. [409] Issue #409, which introduced these primitives and fixed their rules
against the VEST raw database.
.. [corpus] ``workflow/plasma_onset/scan_corpus.py`` and the table it writes,
``test/data/onset_corpus.json``: the scan of VEST shots 39900-41700 the
thresholds, hold times and widths were judged on.
"""
y = _fill_non_finite(np.asarray(values, dtype=float).reshape(-1))
padlen = 3 * (int(order) + 1) # filtfilt's default for a b/a filter of this order
if y.size <= padlen:
raise ValueError(
f"a zero-phase low-pass of order {order} needs more than {padlen} samples; "
f"the record has {y.size}"
)
return np.asarray(butterworth_lowpass(y, float(cutoff_hz), float(fs), int(order), zero_phase=True))Forward-backward Butterworth low-pass: no group delay.
An onset read off the output is not shifted from the input’s. A causal filter of the same order moved the plasma-current onset by +0.1 to +0.9 ms on VEST records; this one by less than a sample.
| Name | Type | Unit | Description |
|---|---|---|---|
values | array_like | any | The waveform; non-finite samples are linearly interpolated first. |
cutoff_hz | float | Hz | Low-pass cutoff. |
fs | float | Hz | Sample rate of the record. |
order | int, optional | - | Butterworth order; the filter is applied twice, so the effective roll-off is twice this. |
| Returns | |||
| — | numpy.ndarray | any | The filtered waveform, same length and grid as the input. |
Convention
Zero-phase (scipy.signal.filtfilt, forward then backward), which is
mandatory wherever an onset time is read off the result and is the opposite
choice from vaft.process.signal_processing.butterworth_lowpass, whose
default is causal to match the validated viewer.
Raises
ValueErrorWhen the record has no more samples thanfiltfilt’s padding length,3 * (order + 1). The detectors call_too_shortfirst and reportrecord_too_shortinstead of raising.
Defaults · Applicability · Limitations · Provenance
Defaults
order = 4 is a numerical convenience: the same order the VEST soft X-ray
viewer and vaft.process.signal_processing use, steep enough to matter and
short enough to pad on a few-thousand-sample record.
Applicability
Machine-independent.
Limitations
A cutoff at or above the record’s Nyquist frequency cannot be designed; the
detectors detect that case themselves and report lowpass_skipped rather
than filtering.
Provenance
- Issue #409, which introduced these primitives and fixed their rules against the VEST raw database.
workflow/plasma_onset/scan_corpus.pyand the table it writes,test/data/onset_corpus.json: the scan of VEST shots 39900-41700 the thresholds, hold times and widths were judged on.