feat: Adaptive DSS with segmentation, auto-selection, and smoothing#28
feat: Adaptive DSS with segmentation, auto-selection, and smoothing#28snesmaeili wants to merge 7 commits into
Conversation
…ing, and pattern normalization
Bug fixes: - transform() now preserves smooth component (was discarded after decomposition) - _clean_segment() centers data before DSS projection (zero-mean assumption) - n_selected_ reports max across segments (was misleading sum) - Empty segmenter results now raise ValueError - narrowband_scan rejects segmented=True with clear error Tests: - 29 new tests for segmentation, selection, segmented DSS, auto-select, smoothing decomposition, cap/floor, and narrowband guard - tests/utils/test_segmentation.py (11 tests) - tests/utils/test_selection.py (18 tests) - tests/test_linear_dss.py extended with adaptive DSS test classes
- Add eigenvalue_ratio_selection, max_gap_selection to utils/__init__.py - Re-export CovarianceSegmenter, FixedWindowSegmenter, and selection utilities from dss/__init__.py for public API access - Add empty tests/utils/__init__.py for test discovery
- Replace duplicate segment_data() in adaptive.py with thin wrapper delegating to CovarianceSegmenter from dss.utils.segmentation - Update core.py _run_adaptive() to use CovarianceSegmenter directly - Update plot_04_adaptive_mode.py example to use CovarianceSegmenter - Fix 19 broken tests in test_linear_dss.py: * Add required bias=LineNoiseBias() to all DSS constructors * Replace string segmenter names with segmenter instances * Fix LineNoiseBias param names (freq, sfreq) * Fix test_invalid_method to test selection_method param * Fix test_fit_then_transform_preserves_smooth return_type All 359 tests pass (11 skipped: MATLAB parity).
- New 'crossfade' parameter on DSS (float, seconds, default 0.0) - When crossfade > 0 and segmented=True, adjacent segments are extended by crossfade seconds, cleaned independently, then blended via raised-cosine (Hann) overlap-add window - Eliminates discontinuities at segment boundaries - Overlap auto-clamped to half the smallest segment length - crossfade=0.0 preserves original hard-concatenation behaviour - New _crossfade_combine() helper method - 6 new tests in TestCrossfade: output shape, no boundary jump, single-segment identity, backward compat, energy preservation, overlap clamping All 365 tests pass (11 skipped: MATLAB parity).
…osterman 2022) Validate PR #28's adaptive-DSS machinery against Klug & Kloosterman 2022 (ZapLine-plus) and de Cheveigné 2020 (ZapLine), and correct the docstring attributions: - iterative_outlier_removal: cite Klug 2022 §2.4 -- this IS the ZapLine-plus automatic component detector (mean+SD threshold, iterate until none remain, sigma=3 default, reported more robust than MAD), alongside NoiseTools - max_prop_remove / min_select: note they mirror ZapLine-plus's one-fifth cap and fixed-removal floor - crossfade: note ZapLine-plus concatenates chunks; the Hann cross-fade is an mne-denoise addition - SmoothingBias: cite de Cheveigné 2020 for the period-matched smooth/residual decomposition - eigenvalue_ratio_selection / max_gap_selection: label as mne-denoise convenience heuristics (not in ZapLine-plus or the DSS literature) Docstring-only; no behavior change.
|
Literature validation: adaptive DSS ↔ ZapLine-plus I cross-checked the adaptive-DSS machinery in this PR against the source papers — Klug & Kloosterman 2022 (ZapLine-plus) and de Cheveigné 2020 (ZapLine), plus Särelä & Valpola 2005, de Cheveigné & Parra 2014, de Cheveigné & Arzounian 2018, and Almeida 2005. Net result: the implementation is faithful to ZapLine-plus; the main gap was attribution, which Faithful to the literature (now cited in the docstrings):
Honestly flagged as
|
Resolve conflicts from main's advancement (ZapLine auto-mode fix #34, centralized MNE channel picking, icanclean, docs/CI): - dss/utils/selection.py: union the branch's eigenvalue_ratio_selection / max_gap_selection with main's detect_eigenvalue_knee / auto_select_components_robust (keeps the Klug 2022 attribution) - dss/utils/__init__.py: export both selection sets - zapline/core.py: use main's auto_select_components_robust for auto n_remove - dss/linear.py: adopt main's 6-value extract_data_from_mne signature in the segmented paths; restore main's get_normalized_patterns method - examples/zapline/plot_04: adopt main's moved viz import path - tests: union both selection/linear suites; adopt main's compute_dss no-variance / scale-invariant tests (compute_dss now warns-not-raises on heavy rank reduction) Full local suite: 575 passed, 11 skipped (MATLAB parity).
Summary
Implements adaptive / continuous DSS for handling non-stationary artifacts, and unifies segmentation across DSS and ZapLine.
Closes #27
Changes
Bug fixes (on top of original adaptive DSS commit)
transform()smooth preservation:data_smoothwas being discarded after decomposition — now captured and added back to reconstruction_clean_segment()centering: data wasn't centered before DSS projection (violating zero-mean assumption) — now centers residual before projectingn_selected_semantics: was reporting sum across all segments (misleading) — now reports max_run_segmented()now raisesValueErrorif segmenter returns no segmentsnarrowband_scanguard: raisesValueErrorifsegmented=Trueis passed (unsupported combination)ZapLine / DSS segmentation unification
segment_data()inadaptive.py: replaced 99 lines of duplicate code with thin backward-compatible wrapper delegating to sharedCovarianceSegmentercore.py_run_adaptive(): now usesCovarianceSegmenterdirectly instead ofsegment_data()CovarianceSegmenterfromdss.utils.segmentationNew features
segmented=Truemode withFixedWindowSegmenterandCovarianceSegmentern_select='outlier'|'ratio'|'max_gap'|'combined'smooth=intmax_prop_remove) and floors (min_select)Tests
tests/utils/test_segmentation.py(11 tests)tests/utils/test_selection.py(18 tests)tests/test_linear_dss.pyextended withTestSegmentedDSS,TestAutoSelect,TestSmoothingDecomposition,TestCapAndFloor, andtest_narrowband_scan_rejects_segmentedtests/test_zapline_adaptive.pyverifyingsegment_datadelegates toCovarianceSegmenter