Add sampling-delay alignment transformer (+ docs rework) - #31
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This branch adds a new
SamplingDelayAlignmenttransformer that corrects the per-channel timing skew introduced by the front-end's sequential A/D converter, and reworks the package documentation to the README-include pattern used across the other ezmsg-namespaced packages.The front-end samples channels in banks of
bank_size(32), one everychannel_sample_interval(~969.7 ns), so within a bank channelc's sample is the signal delayed byc × channel_sample_intervalrelative to the bank start. For any cross-channel operation (CAR, whitening, beamforming) this skew smears the common mode at high frequency — negligible at 60 Hz (~0.65°) but ~81° across a bank near 7.5 kHz, where CAR's common-mode rejection collapses. The transformer removes the skew by delaying each channel back onto a common time grid with a per-slot windowed-sinc fractional-delay FIR filter.What's included
src/ezmsg/blackrock/sampling_delay_alignment.pyexposingSamplingDelayAlignment(Unit),SamplingDelayAlignmentTransformer,SamplingDelayAlignmentSettings, andSamplingDelayAlignmentState, wired into the package's public exports in__init__.py.docs/source/index.rstis replaced bydocs/source/index.md, which simply includesREADME.mdand the toctree — matching the pattern in the sibling ezmsg packages (template, neo, event, tools). All content previously duplicated inindex.rst(setup notes, network/IP config, troubleshooting) already lives in the README in more detail.docs/source/guides/processing.mddocumenting the three processing transformers — channel mapping, CerePlex impedance, and sampling-delay alignment — with MyST cross-references into the auto-generated API reference..gitignorenow ignores the generated API docs (docs/source/api/generated).tests/test_sampling_delay_alignment.py.Design notes
bank_sizedistinct delays, so only that many distinct filters are designed.c % bank_size), but when thechaxis carries structuredbank/elecmetadata (e.g. attached upstream byChannelMapUnit) the slot is taken fromelec - 1instead, so each channel's delay stays correct even when channels are reordered relative to hardware acquisition. The slot layout is folded into the reset hash, so a metadata change (e.g. a newly-pushed channel map) re-designs the filters even when shape/key/gain are unchanged.filter_len = 0disables alignment entirely:_processreturns the input unchanged and_reset_stateskips filter design (the FIR is undefined for zero taps). Useful for A/B comparisons or leaving the unit wired in but inert.rail_thresholdset, clipped samples are held at the last valid value before filtering, so the fractional-delay FIR doesn't ring the corruption across its support.rail_thresholdis listed inNONRESET_SETTINGS_FIELDS, since changing it gates only the forward-fill in_processand does not require rebuilding the (relatively expensive) filter state.(filter_len-1)//2samples; the output time-axis offset is shifted so timestamps stay physically correct.Scope assumptions
The transformer always operates on the
"time"axis of a(time, ch)stream, since it only ever sits downstream of a CereLink source and (optionally) a channel-map transformer, both of which assume a"ch"axis. There is no configurable axis setting.Testing
tests/test_sampling_delay_alignment.pypins the behavior: chunk-invariant streaming (any chunking reproduces the whole-buffer result via carried FIR history), high-frequency common-mode collapse after alignment (CAR residual drops by orders of magnitude where un-aligned CAR fails), rail handling bounds the output, the output offset accounts for the bulk delay, shape/dtype passthrough, cross-backend parity (mlx/torch matching numpy), metadata-driven slot selection overriding acquisition order,filter_len = 0passthrough, andrail_thresholdbeing applied without a state reset.All tests pass locally (the mlx backend test skips when mlx is not installed).