Note
Go to the end to download the full example code.
Basic Sensor Noise Suppression#
This example adds independent sensor noise to a spatially redundant signal, fits SNS, and applies the same workflow to an MNE Raw object.

Relative error before SNS: 0.201
Relative error after SNS: 0.117
Effective neighbors: 12
Samples rejected while fitting: 0
RawArray (24, 2500)
import mne
import numpy as np
from mne_denoise.sns import SNS
from mne_denoise.viz import plot_signal_overlay
rng = np.random.default_rng(12)
n_channels, n_sources, n_times = 24, 4, 2500
sources = rng.standard_normal((n_sources, n_times))
shared = rng.standard_normal((n_channels, n_sources)) @ sources
observed = shared + 0.45 * rng.standard_normal(shared.shape)
model = SNS(n_neighbors=12, outlier_threshold=8.0)
cleaned = model.fit_transform(observed)
error_before = np.linalg.norm(observed - shared) / np.linalg.norm(shared)
error_after = np.linalg.norm(cleaned - shared) / np.linalg.norm(shared)
print(f"Relative error before SNS: {error_before:.3f}")
print(f"Relative error after SNS: {error_after:.3f}")
print(f"Effective neighbors: {model.n_neighbors_}")
print(f"Samples rejected while fitting: {model.rejected_sample_count_}")
plot_signal_overlay(
observed,
cleaned,
np.arange(n_times) / 250.0,
pick=0,
start=0.0,
stop=1.6,
scale_after=False,
before_label="Observed",
after_label="SNS",
reference=shared[0],
reference_label="Shared signal",
x_label="Time (s)",
title="Sensor Noise Suppression — channel 0",
show=False,
)
# MNE containers use the same estimator. The returned object is a copy with its
# metadata and channel layout preserved.
info = mne.create_info(
[f"EEG {index:03d}" for index in range(n_channels)], 250.0, "eeg"
)
raw = mne.io.RawArray(observed, info, verbose=False)
raw_clean = SNS(n_neighbors=12).fit_transform(raw)
print(type(raw_clean).__name__, raw_clean.get_data().shape)
Total running time of the script: (0 minutes 0.343 seconds)