ezmsg.blackrock.sampling_delay_alignment#
Align channels sampled at different instants by a sequential A/D.
The Gemini front-end samples channels in banks of bank_size (32), one every
channel_sample_interval (~969.7 ns), so channel c’s sample n is the
signal at t_n + tau_c, tau_c = (c % bank_size) * channel_sample_interval.
For any cross-channel operation (CAR, whitening, beamforming) this misalignment
smears the common-mode at high frequency: the phase spread across a bank is
2*pi*f*T_bank – negligible at 60 Hz (0.65 deg) but ~81 deg at 7.5 kHz, so
e.g. CAR’s common-mode rejection collapses toward Nyquist.
This transformer removes that by delaying each channel by tau_c with a
per-slot windowed-sinc fractional-delay filter, bringing every channel onto a
common time grid (the bank start). A windowed-sinc is used rather than linear
interpolation on purpose: linear interpolation is a delay-dependent low-pass
that would impose a different high-frequency rolloff per channel – coloring
the band exactly where the misalignment mattered. There are only bank_size
distinct delays, so only that many distinct filters.
The within-bank slot defaults to acquisition order (c % bank_size). If the
channel axis carries per-channel bank/elec metadata (e.g. attached by
ChannelMapUnit), the slot is taken from elec
(elec - 1) instead, so each channel’s delay is correct even when channels
are reordered relative to hardware acquisition.
- Cost / caveats:
Latency: the causal FIR has a common bulk delay of
(filter_len-1)//2samples (the per-channel fractional delays ride on top). The output time axis offset is shifted to keep timestamps physically correct.It resamples the raw data – downstream sees interpolated samples. Fine for cross-channel cleaning; be deliberate if a step needs raw waveforms.
Railing: clipped (rail) samples are corrupt and a fractional-delay filter would spread that corruption over its support. With
rail_thresholdset, railed samples are held at the last valid value before filtering (a basic mitigation). A production version should also emit a reliability mask so downstream can discount the ~``filter_len`` samples around each rail. FIR (used here) localizes the damage; an IIR all-pass (e.g. Thiran) would ring across it.
Array-API compatible: it detects the input’s namespace and runs on the working
backend (numpy, MLX, torch, jax, cupy, …). The sinc taps are designed in numpy
and moved to the backend; everything else – the FIR tap-sum, concat/state
handling, and the rail forward-fill – runs on the backend using only standard
Array-API ops (the forward-fill’s cumulative max is built from maximum +
shifts, since the standard lacks one). Only the MLX concatenate-vs-concat
spelling is special-cased.
Classes
- class SamplingDelayAlignment(*args, settings=None, **kwargs)[source]#
Bases:
BaseTransformerUnit[SamplingDelayAlignmentSettings,AxisArray,AxisArray,SamplingDelayAlignmentTransformer]- Parameters:
settings (Settings | None)
- SETTINGS#
alias of
SamplingDelayAlignmentSettings
- class SamplingDelayAlignmentSettings(bank_size=32, channel_sample_interval=9.696969696969698e-07, filter_len=33, rail_threshold=None)[source]#
Bases:
SettingsSettings for
SamplingDelayAlignmentTransformer.- Parameters:
- bank_size: int = 32#
Channels per simultaneously-started A/D bank. Used to derive each channel’s sweep slot (
c % bank_size) only as a fallback, when the channel axis carries nobank/elecmetadata.
- channel_sample_interval: float = 9.696969696969698e-07#
Seconds between successive channels within a bank.
- filter_len: int = 33#
Sinc FIR length (odd). Bulk delay is
(filter_len-1)//2samples; longer = flatter passband / better near Nyquist, at more latency and compute. Set to0to disable alignment entirely – the transformer becomes a pass-through that returns its input unchanged.
- class SamplingDelayAlignmentState[source]#
Bases:
objectState for
SamplingDelayAlignmentTransformer.- fir: ndarray[tuple[Any, ...], dtype[_ScalarT]] | None = None#
Per-channel sinc FIR taps, shape
(filter_len, n_ch).
- class SamplingDelayAlignmentTransformer(*args, **kwargs)[source]#
Bases:
BaseStatefulTransformer[SamplingDelayAlignmentSettings,AxisArray,AxisArray,SamplingDelayAlignmentState]Per-channel fractional-delay alignment (see module docstring).
- class SamplingDelayAlignmentSettings(bank_size=32, channel_sample_interval=9.696969696969698e-07, filter_len=33, rail_threshold=None)[source]#
Bases:
SettingsSettings for
SamplingDelayAlignmentTransformer.- Parameters:
- bank_size: int = 32#
Channels per simultaneously-started A/D bank. Used to derive each channel’s sweep slot (
c % bank_size) only as a fallback, when the channel axis carries nobank/elecmetadata.
- channel_sample_interval: float = 9.696969696969698e-07#
Seconds between successive channels within a bank.
- filter_len: int = 33#
Sinc FIR length (odd). Bulk delay is
(filter_len-1)//2samples; longer = flatter passband / better near Nyquist, at more latency and compute. Set to0to disable alignment entirely – the transformer becomes a pass-through that returns its input unchanged.
- class SamplingDelayAlignmentState[source]#
Bases:
objectState for
SamplingDelayAlignmentTransformer.- fir: ndarray[tuple[Any, ...], dtype[_ScalarT]] | None = None#
Per-channel sinc FIR taps, shape
(filter_len, n_ch).
- class SamplingDelayAlignmentTransformer(*args, **kwargs)[source]#
Bases:
BaseStatefulTransformer[SamplingDelayAlignmentSettings,AxisArray,AxisArray,SamplingDelayAlignmentState]Per-channel fractional-delay alignment (see module docstring).
- class SamplingDelayAlignment(*args, settings=None, **kwargs)[source]#
Bases:
BaseTransformerUnit[SamplingDelayAlignmentSettings,AxisArray,AxisArray,SamplingDelayAlignmentTransformer]- Parameters:
settings (Settings | None)
- SETTINGS#
alias of
SamplingDelayAlignmentSettings