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 |
Exact lag sign, valid support, and epoch-isolation tests |
2. Concatenate trial observations |
C-order |
Unequal weight-order regression tests |
3. PCA and normalize |
Internal |
Existing DSS/NoiseTools parity plus full-rank reconstruction |
4. Average across trials |
Shared |
Uniform and time-by-epoch weight tests |
5. PCA of biased data |
Internal |
Component ordering and held-out score tests |
6. Apply rotation |
Frozen lag-space filters |
Transform-batch invariance tests |
7. Optional CCA |
Shared training-only |
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 |
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_; andeffective_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.