TimeShiftDSS#

Warning

TimeShiftDSS has substantially more free parameters than spatial DSS. Do not interpret, retain, or remove a component without held-out and surrogate validation.

What it adds to trial-average DSS#

Ordinary DSS learns an instantaneous spatial filter:

[ y_k(t) = \sum_j a_{kj} x_j(t). ]

Time-shift DSS first treats delayed copies of every sensor as additional features and then fits the existing estimator as DSS(bias=AverageBias(axis="epochs")):

[ y_k(t) = \sum_{j,d} a_{kjd} x_j(t-d). ]

The resulting component is a multichannel finite-impulse-response filter. This can separate a reproducible target from interference that overlaps it in the instantaneous sensor space but differs in delay, propagation, or spectrum. It is not the package’s multi-dataset Joint DSS mode. Joint DSS uses AverageBias(axis="datasets"); TimeShiftDSS uses trial averaging after lag augmentation.

The initial package implementation deliberately supports only repeated-trial data: a NumPy array shaped (channels, times, epochs) or MNE Epochs. Raw and Evoked inputs and arbitrary DSS bias functions are outside the source-validated contract.

Explicit fit#

from mne_denoise.dss import TimeShiftDSS

tsdss = TimeShiftDSS(
    lag_samples=[0, 1],
    rank=16,
    n_components=2,
    n_select=1,
    component_action="extract",
)
sources = tsdss.fit_transform(epochs)

Positive lag d contributes X(t - d). Lags are explicit, include zero, and are applied separately inside each epoch. Only the time interval shared by all lags is fitted or extracted. Sensor-valued retain and subtract operations keep the original container and timeline and leave edge samples outside that interval unchanged.

The 2010 paper defines delays as 0, ..., D - 1 but describes its one-sample delayed-noise simulation as D = 1. The package therefore does not expose this ambiguous shorthand: the reproduction declares [0, 1] explicitly.

Source-derived algorithm#

The implementation maps the paper’s steps as follows.

Source claim

Package implementation

Blocking tests/evidence

1. Add delayed sensor copies

Lag-major _lag_augment with equation X(t-d)

Exact lag sign, valid support, and epoch-isolation tests

2. Concatenate trial observations

C-order (feature, time, epoch) flattening

Unequal weight-order regression tests

3. PCA and normalize

Internal DSS estimator and its canonical covariance whitener

Existing DSS/NoiseTools parity plus full-rank reconstruction

4. Average across trials

Shared AverageBias(axis="epochs"), including time-by-epoch weights

Uniform and time-by-epoch weight tests

5. PCA of biased data

Internal DSS estimator’s biased-covariance rotation

Component ordering and held-out score tests

6. Apply rotation

Frozen lag-space filters

Transform-batch invariance tests

7. Optional CCA

Shared training-only canonical_correlation inside the selected reproducible subspace

Shifted-waveform distortion fixture

Zero-weight outliers

Minimum weight across every sample touched by a lag window

Lag-window mask-dilation test

Relative PCA cutoff

Explicit rank and relative reg tolerance

Rank and source-simulation sensitivity sweep

Resampling and CV

Whole-trial bootstrap, grouped CV, nested CV, and max-null surrogates

Reduced CI fixtures and checked validation script

Primary sources:

Centering and covariance weights#

The source/reference-aligned default is center=False: DSS is fitted from uncentered second moments. This preserves meaningful baseline correction. It also avoids the trial-wise mean removal that the 2014 review shows can create a repeatable ramp.

center=True is a package extension. It fits one weighted global mean of the valid augmented training observations, stores feature_mean_, and uses that same mean for every later transform. It never computes a transform-batch or per-epoch mean.

fit(..., sample_weight=weights) accepts either (n_times,) weights broadcast over epochs or an exact (n_times, n_epochs) matrix. Weights must be finite and nonnegative. For each valid reference observation, the effective weight is the minimum across all source samples touched by its lag window. A zero-weight bad sample therefore invalidates every augmented observation containing it.

For augmented observations (z_{t,n}), the repeated-trial contrast is

[ \bar z_t = \frac{\sum_n w_{t,n} z_{t,n}}{\sum_n w_{t,n}}. ]

The baseline second moment uses all valid (time, epoch) observations. The biased second moment uses (\bar z_t), weighted by the valid trial mass (\sum_n w_{t,n}). The two matrices use identical lag support.

Explicit rank and component choice#

TimeShiftDSS does not offer "auto" selection. Callers provide rank, n_components, and, for sensor operations, n_select. The fitted object reports:

  • n_augmented_features_;

  • positive_weight_observations_; and

  • effective_observations_, using the Kish weight formula.

A warning is raised when augmented dimension approaches the effective weighted observation count. This ratio is only a heuristic: autocorrelated samples supply fewer independent constraints than their count suggests.

The source simulation has a real rank cliff. In Simulation 2 with explicit lags [0, 1], five noise sources enter the augmented covariance at three time states. A rank that keeps only those 15 noise directions omits the target; admitting the next direction makes temporal cancellation possible. Rank and lag sensitivity must therefore be reported, not tuned on the final test set.

Held-out and null validation#

Use sklearn splitters on explicit epoch indices. NumPy TSDSS data are channel-first with epochs on the last axis, so passing the array directly to sklearn.model_selection.cross_validate would incorrectly split channels. For example:

import numpy as np
from sklearn.base import clone
from sklearn.model_selection import GroupKFold

fold_scores = []
epoch_indices = np.arange(data.shape[2])
splitter = GroupKFold(n_splits=5)
for train, test in splitter.split(epoch_indices, groups=run_ids):
    fitted = clone(tsdss).fit(data[:, :, train])
    fold_scores.append(fitted.score(data[:, :, test]))

held_out_score = np.mean(fold_scores)

TimeShiftDSS.score requires an explicit n_select and returns one trial-average-to-total power ratio for that leading component subspace. This avoids comparing component ordinals that can reorder across folds and aligns validation with the cumulative subspace used by retain and subtract.

For MNE Epochs, use the same splitter and replace data[:, :, indices] with epochs[indices]. Means, weights, covariances, filters, and optional CCA must be fitted on the training fold only.

The repository’s checked scientific QA additionally calibrates one predeclared model against circularly shifted trials. This procedure lives in scripts/validate_time_shift_dss.py, not in the installed denoising API. It shifts every complete multichannel epoch independently by more than the lag span, preserving its within-epoch structure while destroying trial locking.

If lags, rank, or subspace size are tuned from the same data, use sklearn nested cross-validation and repeat that exact search inside every surrogate; otherwise the null distribution does not include search multiplicity. Continuous event-marker randomization from the 2014 paper remains a future Raw/event-data extension. Bootstrap uncertainty is likewise kept in the checked validation script rather than duplicated as package API.

Reconstruction and distortion#

Standard sensor reconstruction is a weighted least-squares projection of fitted component activity to the undelayed sensor block. It is tested directly and reconstructs the valid interval at full rank.

The optional distortion_control="cca" implements the paper’s step 7. It takes the explicitly fitted reproducible subspace and learns the single combination most correlated with undelayed training sensors. The rotation, component mean, and sensor mean are frozen before held-out transformation. It can reduce FIR latency and waveform distortion, but it trades repeatability for similarity and adds another fitted operation that must stay inside validation folds.

Scientific validation#

Paper parity, efficacy, and null-calibration checks belong in the scientific validation workflow rather than the package unit tests. Run it with:

python scripts/validate_time_shift_dss.py

It records seeds and dependency versions and writes JSON, CSV, and PNG evidence under reports/time_shift_dss/ by default. The script includes:

  • the 2010 Simulation 2 dimensions: 10 channels, 1000 samples, 100 trials, and input SNRs of -20, -40, and -60 dB;

  • held-out static-DSS versus TimeShiftDSS output SNR, gain, correlation, latency, distortion, and noise attenuation;

  • rank and lag-grid sensitivity;

  • 100 whole-training-set bootstrap refits with a fixed held-out test set;

  • 1000 full-pipeline pure-noise surrogates; and

  • the Figure 6 shifted-waveform failure with and without CCA step 7.

--quick reduces the repetitions for a local smoke run. It is marked as such and is not parity evidence. The default run applies predeclared acceptance gates and exits unsuccessfully if any gate fails. Generated reports are review artifacts and are intentionally not embedded in the API documentation.

Interpretation and limitations#

  • An evoked trial-average contrast retains reproducible activity. It cannot decide whether that activity is neural or artifactual.

  • Retaining the leading target component intentionally suppresses off-target activity, including genuine non-target neural signals. The validation report quantifies this rather than calling the operation generally signal preserving.

  • Components define optimized subspaces, not individual neural sources.

  • Preserved edge samples are not FIR-filtered and are excluded from efficacy metrics.

  • The epoched circular-shift null is a package extension, not the continuous random-event surrogate used in the 2014 paper.

  • Arbitrary lag grids, optional centering, weighted/regularized CCA, and the high-dimensional warning threshold are package contracts rather than claims made by the 2010 paper.

  • Apply one fitted transform to all study conditions. Fitting each condition separately can manufacture apparent condition differences.