mne_rt.RTStream#
- class mne_rt.RTStream(subject_id: str, session: str, subjects_dir: str, montage: str | None, data_type: str = 'eeg', mri: bool = False, subject_fs_id: str = 'fsaverage', subjects_fs_dir: str | None = None, source_space: str = 'surface', source_atlas: str | None = None, source_pos: float = 5.0, bandpass_freq: tuple | None = None, notch_freq: float | list | None = None, artifact_correction: bool | str = False, save_nf_signal: bool = True, config_file: str | None = None, verbose: bool | str | None = None)[source]#
Bases:
ModalityMixinReal-time Real-time M/EEG session controller.
Orchestrates LSL streaming, optional artifact rejection, parallel feature extraction, and real-time visualisation for a complete neurofeedback session. Inherits all feature-extraction methods from
ModalityMixin.- Parameters:
- subject_id
str Unique subject identifier (non-empty string). Used as the BIDS subject label (e.g.
"sub01"→ foldersub-sub01/).- session
str BIDS session label (e.g.
"01","pre","week1"). Used to name output files and directories following the BIDS conventionsub-<ID>_ses-<session>_task-<task>.- subjects_dir
str Root directory that holds one sub-folder per subject.
- montage
str|None EEG montage — a MNE built-in name (e.g.
"easycap-M1"), a path to a.bvctCapTrak file, orNonefor MEG.- data_type{“eeg”, “meg”}, default “eeg”
Recording modality. Controls channel selection and forward model.
- mribool, default
False If
True, individual MRI anatomy is used for source localisation instead of thefsaveragetemplate.- subject_fs_id
str, default “fsaverage” FreeSurfer subject identifier. Use
"fsaverage"for template- based source localisation.- subjects_fs_dir
str|None, defaultNone FreeSurfer subjects directory. Required when
subject_fs_id != "fsaverage"or whenshow_brain_activationis requested.- source_space{“surface”, “volume”}, default “surface”
Geometry of the source space built by
compute_inv_operator()."surface"is the cortical-surface model used historically."volume"builds a volumetric grid, which is required for subcortical ROIs (hippocampus, amygdala, thalamus) — these do not exist on the cortical surface at all.- source_atlas
str|None, defaultNone Atlas used to resolve ROI names.
Noneselects a sensible default forsource_space:"aparc"(a surface annotation) for"surface", and"aparc+aseg"(a volumetric.mgz) for"volume". Noteaparc+asegcontains cortical parcels and subcortical structures, so one volume source space covers both.- source_pos
float, default 5.0 Grid spacing in mm for a volumetric source space.
- bandpass_freq
tuple(float,float) |None, defaultNone Online band-pass filter applied to the LSL stream before feature extraction, as
(l_freq, h_freq)in Hz.Nonedisables band-pass filtering. Example:(1.0, 40.0)for a standard EEG band-pass.- notch_freq
float|list[float] |None, defaultNone One or more frequencies (Hz) to suppress with an IIR notch filter.
Nonedisables notch filtering. Example:50or[50, 100]for 50 Hz power-line interference and its harmonic.- artifact_correction{
False, “lms”, “orica”, “gedai”, “asr”, “maxwell”}, defaultFalse Real-time artifact correction strategy applied sample-by-sample inside the acquisition loop.
False— no correction"lms"— adaptive LMS regression on a frontal reference channel; fast and lightweight (EEG only). (AdaptiveLMSFilter)"orica"— online recursive ICA; blind source separation with continuous weight updates. (ORICA)"gedai"— generalised eigendecomposition artefact isolation; callfit_gedai()afterrecord_baseline(). (GEDAIDenoiser)"asr"— Artifact Subspace Reconstruction; callfit_asr()afterrecord_baseline(). (ASRDenoiser)"maxwell"— Signal Space Separation / tSSS for MEG; callfit_maxwell()beforerecord_main()(MEG only). (RTMaxwellFilter)
- save_nf_signalbool, default
True Save extracted feature time-series as JSON.
- config_file
str|None, defaultNone Path to a YAML configuration file.
Noneuses the bundled default (config_methods.yml).- verbosebool |
str|None, defaultNone Verbosity level. Mirrors MNE’s convention:
True/"INFO"→ informational,False/"WARNING"→ warnings only,"DEBUG"→ all messages.
- subject_id
- Raises:
ValueErrorIf any constructor argument fails validation.
See also
ant.modalities.ModalityMixinAll supported NF feature methods.
mne_rt.viz.NFPlotScrolling real-time NF signal display.
mne_rt.viz.RawPlotScrolling raw M/EEG channel viewer (bad-channel / bad-segment marking).
mne_rt.viz.EpochPlotScrolling raw viewer with trigger/epoch overlays.
mne_rt.viz.BrainPlot3D brain activation display.
Notes
Typical workflow:
nf = RTStream("sub01", session="01", subjects_dir="/data/subjects", montage="easycap-M1") nf.connect_to_lsl() nf.record_baseline(baseline_duration=120) nf.record_main(duration=600, modality=["sensor_power", "erd_ers"])
The main main loop runs M/EEG acquisition in a background daemon thread and drives all visualisation windows (StreamViewer, signal plot, brain plot) from the Qt event loop on the main thread via a 33 ms pump timer, ensuring all three windows are truly parallel and non-blocking.
Added in version 1.0.0.
- __init__(subject_id: str, session: str, subjects_dir: str, montage: str | None, data_type: str = 'eeg', mri: bool = False, subject_fs_id: str = 'fsaverage', subjects_fs_dir: str | None = None, source_space: str = 'surface', source_atlas: str | None = None, source_pos: float = 5.0, bandpass_freq: tuple | None = None, notch_freq: float | list | None = None, artifact_correction: bool | str = False, save_nf_signal: bool = True, config_file: str | None = None, verbose: bool | str | None = None) None[source]#
Methods
__init__(subject_id, session, subjects_dir, ...)compute_inv_operator([loose, depth, ...])Compute and save the inverse operator for source localisation.
connect_marker_array(codes, onsets[, ...])Attach a fixed marker schedule instead of a live LSL outlet.
connect_marker_stream([stream_name, ...])Connect the LSL stream that carries the experiment's event markers.
connect_to_array(data, info[, chunk_size, ...])Connect to a plain numpy array instead of an LSL stream.
connect_to_lsl([chunk_size, mock_lsl, ...])Connect to an LSL M/EEG stream.
create_report([overwrite, include_psd, ...])Generate a self-contained HTML report for the session.
fit_asr([cutoff, window_len, ...])Fit an
ASRDenoiserfrom the recorded baseline.fit_gedai([band, shrinkage, use_leadfield, ...])Fit a
GEDAIDenoiserfrom the recorded baseline.fit_maxwell([int_order, ext_order, origin, ...])Prepare real-time Maxwell filtering (SSS / tSSS) for MEG data.
get_blink_template([max_iter, method, ...])Identify the eye-blink ICA component from baseline EEG.
load_nf_data(path)Load a saved NF data file produced by
save().open_stream_viewer([bufsize])Open the mne-lsl StreamViewer for raw M/EEG monitoring.
record_baseline(baseline_duration[, ...])Record a resting-state baseline segment.
record_main(duration[, modality, picks, ...])Stream M/EEG, extract neural features, and drive NF visualisation.
replay(fname[, modality, duration, winsize, ...])Replay a saved recording as a mock LSL session.
run_blocks(blocks[, rest_duration, verbose])Run multiple NF blocks separated by rest periods.
run_orica(n_channels[, learning_rate, ...])Initialise an Online Recursive ICA (ORICA) instance.
save([nf_data, acq_delay, artifact_delay, ...])Save session outputs and, by default, disconnect the LSL stream.
set_decoder(decoder)Attach a fitted decoder for the
"decode"modality.Attributes
Per-modality parameter overrides applied during the NF session.
- connect_to_lsl(chunk_size: int = 10, mock_lsl: bool = False, fname: str | None = None, n_repeat: int | float = inf, bufsize_baseline: int = 4, bufsize_main: int = 3, acquisition_delay: float = 0.001, timeout: float = 15.0, stream_name: str | None = None, stream_source_id: str | None = None, pick_types: str | None = None, processing_flags: str | Sequence[str] | None = None, verbose: bool | str | None = None) None[source]#
Connect to an LSL M/EEG stream.
- Parameters:
- chunk_size
int, default 10 Samples per chunk for mock streaming.
- mock_lslbool, default
False Stream a pre-recorded file instead of live hardware. Requires
fnameor uses the bundled sample file.- fname
str|None, defaultNone Path to any MNE-readable recording for mock streaming (.fif, .vhdr, .edf, .bdf, .set, …).
Noneuses the bundled sample recording.- n_repeat
int|float, defaultnp.inf How many times to loop the mock recording.
- bufsize_baseline
int, default 4 LSL buffer size in seconds for baseline sessions.
- bufsize_main
int, default 3 LSL buffer size in seconds for main sessions.
- acquisition_delay
float, default 0.001 Seconds between acquisition polling attempts.
- timeout
float, default 15.0 Maximum wait time in seconds for the LSL connection.
- stream_name
str|None, defaultNone Connect by stream name (e.g.
neuromag2lslfor MEG devices).- stream_source_id
str|None, defaultNone Connect by stream source ID.
- pick_types
str|None, defaultNone Channel type to keep (e.g.
"eeg","mag").Nonekeeps all available channels.- processing_flags
str| sequenceofstr|None, defaultNone Forwarded to
mne_lsl.stream.StreamLSL.connect(). Pass"all"to enableclocksync, which is required when a marker stream published from another machine has to be aligned against this one — seeconnect_marker_stream().- verbosebool |
str|None, defaultNone Override the instance-level verbosity for this call.
- chunk_size
- Raises:
RuntimeErrorIf no LSL stream matching the given criteria is found within
timeoutseconds.
Notes
All public methods of
mne_lsl.stream.StreamLSLare also exposed directly on theRTStreaminstance after connection.Examples
Connect to a live EEG amplifier:
nf.connect_to_lsl()
Simulate from any MNE-readable file:
nf.connect_to_lsl(mock_lsl=True, fname="path/to/data.fif") nf.connect_to_lsl(mock_lsl=True, fname="path/to/data.edf")
- connect_to_array(data: ndarray, info: Info, chunk_size: int = 10, n_repeat: int | float = 1, bufsize_main: float = 3.0, pick_types: str | None = None, verbose: bool | str | None = None) None[source]#
Connect to a plain numpy array instead of an LSL stream.
Drives the exact same acquisition/feature-extraction pipeline as
connect_to_lsl(), backed byArrayStream— no LSL networking or recorded file is required. Useful for offline analysis, unit tests, and demos, e.g. feeding synthetic data or an already-loaded recording straight throughrecord_baseline()/record_main().- Parameters:
- data
arrayofshape(n_channels,n_samples) The full recording to stream.
- info
mne.Info Channel/sampling-rate metadata;
info["nchan"]must equaldata.shape[0].- chunk_size
int, default 10 Samples released into the buffer per acquisition tick.
- n_repeat
int|float, default 1 Number of times to loop over
dataonce exhausted. Usenp.infto loop indefinitely for open-ended sessions.- bufsize_main
float, default 3.0 Ring-buffer size in seconds.
- pick_types
str|None, defaultNone Channel type to keep (e.g.
"eeg","mag").Nonekeeps all available channels.- verbosebool |
str|None, defaultNone Override the instance-level verbosity for this call.
- data
- Raises:
ValueErrorIf
datais not 2D, or its channel count does not matchinfo["nchan"].
See also
connect_to_lslConnect to a live or mock-replayed LSL stream.
Notes
All public methods of
ArrayStreamare also exposed directly on theRTStreaminstance after connection — a narrower surface thanconnect_to_lsl()exposes, sinceArrayStreamhas no LSL-specific extras (e.g.name, used byopen_stream_viewer(), which is unavailable for array-backed sessions).Examples
Stream a synthetic recording end-to-end without any LSL infra:
info = mne.create_info(ch_names, sfreq=256.0, ch_types="eeg") nf.connect_to_array(data, info) nf.record_baseline(baseline_duration=60) nf.record_main(duration=300, modality="sensor_power")
- connect_marker_stream(stream_name: str | None = None, source_id: str | None = None, marker_id: dict | None = None, bufsize: int = 200, ch_name: str | None = None, timeout: float = 10.0, acquisition_delay: float = 0.005, processing_flags: str | Sequence[str] | None = 'all', verbose: bool | str | None = None) None[source]#
Connect the LSL stream that carries the experiment’s event markers.
This is the inbound counterpart to
LSLSender: a stimulus program such as PsychoPy publishes trial markers on their own LSL outlet, and connecting it here letsrecord_main()tag every analysis window with the condition that was running, and optionally mute feedback outside chosen conditions.The marker stream is kept separate from the M/EEG stream in
marker_stream; it is never mixed into the signal path.- Parameters:
- stream_name
str|None, defaultNone Connect by stream name.
- source_id
str|None, defaultNone Connect by source ID. May be given instead of, or together with,
stream_name; together they must identify exactly one stream.- marker_id
dict|None, defaultNone Condition label to marker code, e.g.
{"speech": 1, "rest": 2}. Used to label windows and to interpretgate_conditions. WithNonethe codes are their own labels.- bufsize
int, default 200 Buffer size in markers. A marker outlet is irregularly sampled, so a duration would carry no meaning.
- ch_name
str|None, defaultNone Name of the channel carrying the codes.
Noneuses the stream’s first channel.- timeout
float, default 10.0 LSL connection timeout in seconds.
- acquisition_delay
float, default 0.005 Seconds between acquisition polling attempts.
- processing_flags
str| sequenceofstr|None, default “all” Forwarded to
mne_lsl.stream.StreamLSL.connect(). The default enablesclocksync— see Notes.- verbosebool |
str|None, defaultNone
- stream_name
- Raises:
RuntimeErrorIf no matching stream is found, or if the outlet publishes strings.
See also
connect_marker_arrayOffline equivalent, for tests and demos.
RTEpochs.connect_to_lslThe same idea for event-triggered epochs.
Notes
Clock synchronisation. Two LSL streams share a time base only once their clocks are synchronised. Without it, markers published from a second machine sit at an arbitrary offset from the M/EEG data and every one of them lands in the wrong window, with nothing to report it. This method therefore defaults to
processing_flags="all", and warns if the M/EEG stream was connected without the same treatment — passprocessing_flags="all"toconnect_to_lsl()as well.Publishing the markers. The outlet must be numeric; mne-lsl refuses string streams, and
channel_format="string"is what most PsychoPy marker examples use. It should label its channel, since an unlabelled outlet is exposed as"0". A minimal publisher:from mne_lsl.lsl import StreamInfo, StreamOutlet sinfo = StreamInfo( "psychopy_markers", "Markers", 1, 0.0, "int32", "psychopy_uid" ) sinfo.set_channel_names(["markers"]) outlet = StreamOutlet(sinfo) outlet.push_sample([1]) # a "speech" trial
Examples
Tag every window with the running condition, and reward only during speech attempts:
nf.connect_to_lsl(stream_name="ANT", processing_flags="all") nf.connect_marker_stream( stream_name="psychopy_markers", marker_id={"speech": 1, "rest": 2}, ) nf.record_main(duration=300, gate_conditions=["speech"])
- connect_marker_array(codes, onsets, marker_id: dict | None = None, bufsize: int = 200, ch_name: str = 'markers', verbose: bool | str | None = None) None[source]#
Attach a fixed marker schedule instead of a live LSL outlet.
The marker counterpart to
connect_to_array(): drives the exact same gating and window-tagging path asconnect_marker_stream()with no LSL networking, which is what makes gated sessions testable and demonstrable offline.- Parameters:
- codesarray_like
ofint Marker codes to publish, in order.
- onsetsarray_like
offloat When to publish each code, in seconds after this call.
- marker_id
dict|None, defaultNone Condition label to marker code. See
connect_marker_stream().- bufsize
int, default 200 Buffer size in markers.
- ch_name
str, default “markers” Name of the marker channel.
- verbosebool |
str|None, defaultNone
- codesarray_like
Examples
Alternate 10 s speech and rest blocks:
nf.connect_marker_array( codes=[1, 2] * 5, onsets=[10.0 * i for i in range(10)], marker_id={"speech": 1, "rest": 2}, )
- record_baseline(baseline_duration: float, winsize: float = 3.0, verbose: bool | str | None = None) None[source]#
Record a resting-state baseline segment.
Collects
baseline_durationseconds of M/EEG, stores it asraw_baseline, and saves it to disk.The head model is not built here. For
fsaveragethat means downloading roughly 700 MB of anatomy, which a sensor-space session never needs; it is built on first use instead, by a source-space modality or by the brain-activation display. Callcompute_inv_operator()directly to build it eagerly, or to pass non-default arguments.- Parameters:
Notes
Output files written under
<subjects_dir>/sub-<ID>/ses-<session>/:eeg/sub-<ID>_ses-<session>_task-baseline_eeg.fif
The
inv/files listed undercompute_inv_operator()appear once something asks for the head model.Examples
>>> nf.record_baseline(baseline_duration=120)
- set_decoder(decoder: RTDecode) None[source]#
Attach a fitted decoder for the
"decode"modality.- Parameters:
See also
mne_rt.RTDecodeFit-offline / predict-online single-trial decoder.
Examples
>>> decoder = RTDecode(info=epochs_info).fit(X_cal, y_cal) >>> nf.set_decoder(decoder) >>> nf.record_main(modality=["decode"])
- record_main(duration: float, modality: str | list[str] = 'sensor_power', picks: str | list[str] | None = None, winsize: float = 1.0, estimate_delays: bool = False, modality_params: dict[str, Any] | None = None, show_raw_signal: bool = True, show_nf_signal: bool = True, time_window: float = 30.0, show_topo: bool = False, topo_bands: dict | None = None, show_brain_activation: bool = False, brain_surf: str = 'inflated', brain_mode: str = 'power', brain_freq_range: tuple[float, float] = (8.0, 13.0), zscore_normalize: bool = False, zscore_warmup: int = 10, zscore_alpha: float = 0.0, osc_sender: Any | None = None, lsl_sender: Any | None = None, protocol: Any | None = None, combiner: Any | None = None, combined_name: str = 'combined', gate_conditions: list[str] | None = None, save_raw: bool = False, save_tsv: bool = True, run: int | None = None, disconnect: bool = True, ref_channel: str = 'Fp1', signal_smoothing: float = 0.25, display_smoothing: float = 0.3, topo_display_smoothing: float = 1.0, brain_display_smoothing: float = 0.3, track_artifact_rate: bool = True, artifact_threshold_uv: float = 100.0, track_snr: bool = False, snr_frange: tuple | None = None, verbose: bool | str | None = None) None[source]#
Stream M/EEG, extract neural features, and drive NF visualisation.
This is the main closed-loop entry point. It:
Prepares each requested modality (calls
_<modality>_prep).Opens the selected visualisation windows (StreamViewer, signal plot, brain plot) on the main thread.
Starts a background daemon thread that continuously fetches data, runs artifact correction, and computes features in parallel via a thread pool.
Drives all windows from the Qt event loop via a 33 ms pump timer.
Saves raw data and feature time-series to disk when finished.
- Parameters:
- duration
float Total recording length in seconds.
- modality
str|listofstr, default “sensor_power” NF feature(s) to extract. Must match keys in
config_methods.yml. Multiple modalities are extracted in parallel. Available modalities:"sensor_power","band_ratio","erd_ers","laterality","laterality_erd_ers","hjorth","spectral_centroid","argmax_freq","individual_peak_power","entropy","instantaneous_phase","scp","peak_alpha_freq","sensor_connectivity","cfc_sensor","sensor_graph","connectivity_ratio","source_power","source_connectivity","source_graph","decode"(requiresset_decoder()beforehand).A name may carry an instance label after an
"@"—"source_connectivity@theta"— so the same measure can run several times over with different parameters (seemodality_params). The label distinguishes the instances everywhere they appear: plot traces, protocol keys, combiner feature names, OSC addresses, LSL channels and saved columns.- picks
str|listofstr|None, defaultNone Channel selection passed to the LSL stream.
Noneuses all available channels. Must beNonefor source-space modalities.- winsize
float, default 1.0 Analysis window length in seconds.
- estimate_delaysbool, default
False Measure and save per-step timing (acquisition, artifact correction, feature extraction).
- modality_params
dict|None, defaultNone Per-modality parameter overrides. Keys are modality names; values are dicts of
{parameter: new_value}pairs that override the config-file defaults.A key may name either a base modality or a specific instance. A base entry applies to every instance of it, and an instance’s own entry takes precedence — so shared settings are written once:
modality=["source_connectivity@theta", "source_connectivity@alpha"], modality_params={ "source_connectivity": {"method": "imcoh"}, "source_connectivity@theta": {"frange": [4, 8]}, "source_connectivity@alpha": {"frange": [8, 13]}, },
- show_raw_signalbool, default
True Show the
RawPlotscrolling raw M/EEG viewer.- show_nf_signalbool, default
True Show the
NFPlotreal-time NF monitor.- time_window
float, default 30.0 Visible time range in seconds for the signal plot.
- show_topobool, default
False Show the
TopomapPlotreal-time scalp topomap display. Requires the montage to be set on the channel info.- topo_bands
dict|None, defaultNone Frequency bands to show in the topomap as
{label: (f_low, f_high)}.Noneuses the default δ/θ/α/β/γ bands.- show_brain_activationbool, default
False Show the
BrainPlot3D brain activation display (requiressubjects_fs_dirand a fitted inverse operator).- brain_surf{“inflated”, “pial”, “white”, “sphere”}, default “pial”
Cortical surface geometry for the brain display.
"pial"shows the true cortical folding;"inflated"unfolds gyri/sulci for easier label inspection.- brain_mode{“power”, “activation”}, default “power”
Source-space display mode.
"power"shows mean squared amplitude;"activation"shows mean amplitude.- brain_freq_range(
float,float), default (8.0, 13.0) Frequency band in Hz used to band-pass the data before computing source power for the brain display.
- zscore_normalizebool, default
False Apply online z-score normalisation to each NF feature value before storing and displaying it. During the first
zscore_warmupwindows the raw value is passed through unchanged; once warmup completes, each value is normalised asz = (x − μ) / σwhere μ and σ are estimated from the warmup windows. Ifzscore_alpha > 0the statistics are updated after every window with an exponential moving average so the normaliser slowly tracks drift.The estimate is scale-free: σ is used exactly as observed, whatever the feature’s units, so band power (~1e-13 V²/Hz) and a connectivity index (~1) both come out around 1. A feature with no spread at all has no z-score and yields
0.0. When this is enabled the plot’s display scales become 1.0, since the traces are no longer in native units.Changed in version 1.2.0: Previously σ was floored at
1e-6, which for power-like features replaced the real spread and made z-scores several orders of magnitude too small.- zscore_warmup
int, default 10 Number of windows to collect before activating normalisation. The mean and standard deviation of these windows are used as the initial statistics.
Longer is better: σ estimated from a handful of windows carries its own sampling error, and the first analysis window of a run tends to read low (the acquisition buffer is still filling), which inflates σ and shrinks every later z-score. Measured on synthetic EEG band power, the spread of the resulting trace was ~0.5 at
zscore_warmup=8and ~1.0 at 60.- zscore_alpha
float, default 0.0 EMA forgetting factor for updating μ and σ each window.
0.0freezes statistics after warmup (recommended for most NF protocols). Values in [0.01, 0.1] add slow adaptation.- osc_sender
OSCSender|None, defaultNone If provided, each computed NF value is also broadcast over OSC to the configured host/port after every update cycle. See
OSCSender.- lsl_sender
LSLSender|None, defaultNone If provided, each computed NF value is pushed into an LSL stream outlet after every update cycle. Faster and more reliable than OSC for same-machine feedback delivery. See
LSLSender.- protocol
Protocolinstance|dict|None, defaultNone Real-time NF reward protocol evaluated on every analysis window. Pass a single Protocol instance (e.g.
ThresholdProtocol) to apply it to the first modality, or a{modality_name: protocol}dict to apply different protocols to different modalities. Keys must name active modalities exactly; where a modality runs as several instances, each needs its own protocol object, since protocols accumulate state and one shared between instances would be fed several different values per window. On each window the protocol’sevaluate(value)method is called and(crossed, magnitude)is recorded. Results are accessible viareward_dataafter the session. Whenshow_nf_signal=True, each protocol’scurrent_threshold(fixed or adaptive) is also drawn live as a dashed horizontal line on the correspondingNFPlotsubplot; modalities without a protocol, or protocols with no single-level threshold (e.g.LinearTrendProtocol), simply show no line.- combiner
FeatureCombiner|None, defaultNone Reduce the per-modality values to a single scalar once per window, after z-scoring and EMA smoothing. The result is appended as an extra trace named
combined_nameand is treated as a modality throughout: it is plotted, can be driven by its own protocol, is broadcast over OSC/LSL, and is saved alongside the others. The per-modality traces are kept, not replaced.Combining features whose units differ by orders of magnitude (band power in V²/Hz against a dimensionless laterality index, say) requires
zscore_normalize=True; otherwise the largest-scale feature dominates, and a warning is issued. Seemne_rt.combiners.- gate_conditions
listofstr|None, defaultNone Restrict feedback to windows running one of these conditions, as labelled by the marker stream attached with
connect_marker_stream().None— the default — leaves feedback running continuously, which is the behaviour of earlier versions. A window outside the gate still computes its features and is saved, but no protocol is evaluated for it and nothing is sent over OSC or LSL; see Notes.- combined_name
str, default “combined” Name of the combined trace. Must not collide with a modality name.
- save_rawbool, default
False Persist the raw pre-correction M/EEG acquired during the main session to
raw/<stem>-raw.fif. Off by default because FIF files can be large; enable when the raw continuous signal is needed for offline re-analysis or provenance.- run
int|None, defaultNone BIDS
run-index for the output filenames, 1-based.Noneomits the entity, which is the behaviour of earlier versions.run_blocks()sets it per block, so one block no longer overwrites the previous one’s files.- disconnectbool, default
True Disconnect the stream once the session has been saved.
Falsekeeps it open for a following block; seerun_blocks().- save_tsvbool, default
True Also write the per-window values as a BIDS
_beh.tsvtable with a_beh.jsonsidecar, alongside the session JSON. The table carries anonsetanddurationcolumn per window, so the trace can be aligned against a stimulus log without assuming a regular grid.- signal_smoothing
float, default 0.25 Exponential moving average (EMA) factor applied to each NF feature value before it is stored and displayed. Controls the trade-off between signal smoothness and responsiveness:
1.0— no smoothing; raw per-window estimate passed through.0.5— moderate smoothing; each value is 50 % new + 50 % history.0.1— heavy smoothing; very slow response to rapid changes.
The EMA is applied after z-score normalisation (if enabled) and before protocol evaluation, so protocols see the smoothed value.
- display_smoothing
float, default 0.3 Additional EMA factor applied only inside the live signal plot. Does not affect stored
nf_dataor protocol evaluation. Lower values give a smoother, slower-reacting display curve;1.0disables this extra layer and shows the alreadysignal_smoothing-filtered values directly.- topo_display_smoothing
float, default 1.0 EMA factor for the
TopomapPlotband-power maps.1.0(default) disables smoothing so transient artifacts remain visible for operator monitoring. Lower values progressively smooth the spatial maps across consecutive windows.- brain_display_smoothing
float, default 0.3 EMA factor for the
BrainPlotper-vertex activation arrays. Blends consecutive frames so the cortical map transitions smoothly.1.0disables smoothing.- ref_channel
str, default “Fp1” Reference channel used for LMS artifact correction (
artifact_correction="lms"only). Ignored for all other correction methods.- track_artifact_ratebool, default
True If
True, count windows whose peak-to-peak amplitude exceedsartifact_threshold_uvand store the fraction asartifact_rateat the end of the session.- artifact_threshold_uv
float, default 100.0 Peak-to-peak amplitude threshold in µV used to classify a window as artifactual when
track_artifact_rate=True.- track_snrbool, default
False If
True, compute a per-window signal-to-noise ratio (band power insnr_frangedivided by broadband noise power, in dB) and store the resulting time-series assnr_data.- snr_frange
tuple(float,float) |None, defaultNone Frequency band
(f_low, f_high)in Hz used as the “signal” band whentrack_snr=True.Nonedefaults to the alpha band(8.0, 13.0).- verbosebool |
str|None, defaultNone Override the instance-level verbosity for this call.
- duration
- Raises:
NotImplementedErrorIf a requested
modalityis not implemented.ValueErrorIf a source-space modality is requested together with non-
Nonepicks.RuntimeErrorIf
show_brain_activation=Truebutsubjects_fs_diris not set, or ifartifact_correction="gedai"butfit_gedai()has not been called.
Notes
Output files written under
<subjects_dir>/sub-<ID>/ses-<session>/:beh/<stem>_beh.json— NF feature time-series plus session metadatabeh/<stem>_beh.tsv— the same as a BIDS behavioural tabledelays/<stem>_delays.json— per-step timing (only whenestimate_delays=True)eeg/<stem>_eeg.fif— pre-correction M/EEG (only whensave_raw=True)
where
<stem>issub-<ID>_ses-<session>_task-neurofeedback, with_run-<NN>appended whenrunis given.Examples
Single-modality alpha-power NF with brain activation:
nf.record_main( duration=300, modality="sensor_power", show_brain_activation=True, )
Multi-modality session with custom parameters:
nf.record_main( duration=600, modality=["sensor_power", "erd_ers", "laterality"], modality_params={"sensor_power": {"frange": [10, 12]}}, show_nf_signal=True, )
Added in version 1.0.0.
- replay(fname: str, modality: str | list[str] = 'sensor_power', duration: float | None = None, winsize: float = 1.0, verbose: bool | str | None = None, **record_main_kwargs) None[source]#
Replay a saved recording as a mock LSL session.
Loads a pre-recorded M/EEG file (any MNE-readable format), streams it via
PlayerLSLat its native sampling rate, and passes the live stream throughrecord_main()— exercising the full real-time pipeline (artifact correction, feature extraction, protocol evaluation) without live hardware.Useful for:
Offline parameter tuning (test different protocols on the same data).
Verifying pipeline latency and modality behaviour.
Reproducing a session with modified parameters.
- Parameters:
- fname
str Path to any MNE-readable recording (
.fif,.vhdr,.edf,.bdf,.set, …).- modality
str|listofstr, default “sensor_power” NF modality(ies) to extract.
- duration
float|None, defaultNone Duration of replay in seconds.
Noneinfers the full recording length from the file.- winsize
float, default 1.0 Analysis window length in seconds.
- verbosebool |
str|None, defaultNone Override instance verbosity for this call.
- **record_main_kwargs
Additional keyword arguments forwarded to
record_main()(e.g.protocol,modality_params,track_snr).
- fname
Examples
Replay a saved EEG file and run a ZScore protocol offline:
from mne_rt.protocols import ZScoreProtocol nf.replay( "sub-01/ses-01/eeg/sub-01_ses-01_task-neurofeedback_eeg.fif", modality="sensor_power", protocol=ZScoreProtocol(), show_nf_signal=False, show_raw_signal=False, ) print(f"Artifact rate: {nf.artifact_rate:.1%}")
Added in version 1.0.0.
- run_blocks(blocks: list[dict], rest_duration: float = 30.0, verbose: bool | str | None = None) list[dict][source]#
Run multiple NF blocks separated by rest periods.
Each block calls
record_main()with the parameters given in the corresponding dict. Blocks are separated byrest_durationseconds of silence (no acquisition, no feedback).- Parameters:
- blocks
listofdict Each dict is passed as keyword arguments to
record_main(). The key"rest"(optional) overridesrest_durationfor the pause after that block. All other keys must be validrecord_main()parameters.Minimal example:
blocks = [ {"duration": 120, "modality": "sensor_power"}, {"duration": 120, "modality": "sensor_power", "rest": 60}, {"duration": 120, "modality": "sensor_power"}, ]
- rest_duration
float, default 30.0 Default inter-block rest period in seconds.
- verbosebool |
str|None, defaultNone Override instance verbosity for this call.
- blocks
- Returns:
- all_nf_data
listofdict One dict per block, each matching
nf_datafrom that block’srecord_main()call. Also stored asblock_nf_data.
- all_nf_data
Notes
save()is called internally at the end of each block byrecord_main(). Each block is written under its own BIDSrun-entity —run-01,run-02, … — so one block no longer overwrites the previous one’s files. Pass"run"in a block dict to override the index.The stream stays connected between blocks. Only the final block disconnects it, because
save()’s teardown also stops the mock player, which reconnecting the stream alone cannot undo.The returned list carries each block’s feature time-series.
block_resultscarries the rest — rewards, window onsets and durations, dropped-window counts and artifact rates — which are otherwise overwritten by the following block.Protocol objects are not reset between blocks: a
ZScoreProtocolpassed to two blocks carries its running statistics from one into the next. A combiner is reset. If you want a protocol to start fresh, pass a new instance per block.Examples
Three 2-minute NF runs separated by 30-second rests:
nf.connect_to_lsl(mock_lsl=True, fname="recording.fif") nf.record_baseline(baseline_duration=60) results = nf.run_blocks( blocks=[ {"duration": 120, "modality": "sensor_power", "show_nf_signal": False, "show_raw_signal": False}, {"duration": 120, "modality": "sensor_power", "show_nf_signal": False, "show_raw_signal": False}, {"duration": 120, "modality": "sensor_power", "show_nf_signal": False, "show_raw_signal": False}, ], rest_duration=30.0, ) for i, block_data in enumerate(results): vals = block_data["sensor_power"] print(f"Block {i+1}: {len(vals)} windows, mean={sum(vals)/len(vals):.4f}")
Added in version 1.0.0.
- property modality_params: dict#
Per-modality parameter overrides applied during the NF session.
A flat or nested dict that maps modality keys (e.g.
"sensor_power") to keyword arguments forwarded to the corresponding feature extractor.Noneis accepted on assignment and normalised to{}.Examples
>>> nf.modality_params = {"sensor_power": {"frange": [10, 12]}, ... "erd_ers": {"frange": [8, 13]}}
- get_blink_template(max_iter: int = 800, method: str = 'infomax', n_components: int | float | None = 0.95, fit_params: dict | None = None, random_state: int | None = 0, iclabel_threshold: float = 0.5) None[source]#
Identify the eye-blink ICA component from baseline EEG.
Sets
self.blink_template(ndarray) — the spatial template vector for the blink component, used by artifact correction during the main session.- Parameters:
- max_iter
int, default 800 Maximum number of ICA fitting iterations.
- method
str, default “infomax” ICA algorithm passed to
mne.preprocessing.ICA. Common choices:"infomax","fastica","picard".- n_components
int|float|None, default 0.95 Number of PCA components before ICA. A float in (0, 1) retains enough components to explain that fraction of variance.
Noneuses all channels. Falls back to5if the initial fit fails.- fit_params
dict|None, defaultNone Extra keyword arguments forwarded to the ICA solver.
Nonedefaults to{"extended": True}for infomax (recommended).- random_state
int|None, default 0 Seed for reproducible ICA solutions.
- iclabel_threshold
float, default 0.5 Minimum ICLabel probability for a component to be classified as
"eye blink". Lower values are more permissive; higher values require stronger confidence.
- max_iter
- run_orica(n_channels: int, learning_rate: float = 0.1, block_size: int = 256, online_whitening: bool = True, calibrate_pca: bool = False, forgetfac: float = 1.0, nonlinearity: str = 'tanh', random_state: int | None = None) None[source]#
Initialise an Online Recursive ICA (ORICA) instance.
ORICA is a streaming, adaptive ICA algorithm that updates its unmixing matrix incrementally as each new EEG block arrives — without ever storing the full recording. It is the preferred real-time alternative to offline ICA when the signal statistics change over time (non-stationarity).
- Parameters:
- n_channels
int Number of EEG/MEG channels. Must match the channel count of the data passed to each subsequent
partial_fit()orfit_transform()call.- learning_rate
float, default 0.1 Step size for the online natural-gradient update of the unmixing matrix W. Larger values adapt faster but may oscillate; values in [0.01, 0.2] are typically stable.
- block_size
int, default 256 Number of samples per update block. Smaller blocks give finer temporal resolution at the cost of noisier gradient estimates. Must be ≥
n_channels.- online_whiteningbool, default
True If
True, a recursive PCA whitening step is applied to each block before the ICA update, keeping the algorithm numerically stable as signal variance drifts.- calibrate_pcabool, default
False If
True, run a batch PCA on the first block to initialise the whitening matrix before switching to online updates. Recommended when starting cold with no prior covariance estimate.- forgetfac
float, default 1.0 Exponential forgetting factor for the online covariance estimate (1.0 = no forgetting; 0.99 → slowly decaying influence of older samples). Values < 1 help track gradual changes in the mixing matrix.
- nonlinearity
str, default “tanh” Score function used in the natural-gradient ICA update.
"tanh"works well for super-Gaussian sources (spikes, blinks);"logcosh"is a smooth approximation with similar properties.- random_state
int|None, defaultNone Seed for reproducible initialisation of the unmixing matrix W.
Noneuses a random seed.
- n_channels
See also
ant.tools.ORICAThe underlying ORICA implementation.
RTStream.get_blink_templateCompute a blink spatial template to guide component identification.
Notes
run_orica()is called automatically insiderecord_baseline()whenartifact_correction="orica"is set on theRTStreaminstance. Call it manually only if you need to tune the ORICA hyperparameters.
- fit_gedai(band: tuple[float, float] = (8.0, 13.0), shrinkage: float = 0.01, use_leadfield: bool = True, verbose: bool | str | None = None) None[source]#
Fit a
GEDAIDenoiserfrom the recorded baseline.Must be called after
record_baseline()and beforerecord_main()whenartifact_correction="gedai".- Parameters:
- band
tupleoffloat, default (8.0, 13.0) Target frequency band
(low_Hz, high_Hz)used when fitting in band-filter mode (use_leadfield=False). Ignored whenuse_leadfield=True.- shrinkage
float, default 0.01 Tikhonov regularisation strength applied to the reference covariance before solving the generalised eigenvalue problem. Larger values improve numerical stability at the cost of slightly less discriminative spatial filters.
- use_leadfieldbool, default
True If
Trueand a forward solution is available (i.e.,compute_inv_operator()has been called), fit in leadfield mode — the true GEDAI algorithm (Ros et al., 2025). The forward gain matrix \(\mathbf{L}\) is used as the reference covariance \(\mathbf{R} = \mathbf{L}\mathbf{L}^\top\), so components that best explain the theoretical brain-source model are kept and non-leadfield-aligned components (artifacts) are removed.If
False, or if no forward solution is available, falls back to band-filter mode: the GEP is solved between the band-filtered and broadband EEG covariances (Cohen, 2022).- verbosebool |
str|None, defaultNone Override the instance-level verbosity for this call.
- band
- Raises:
RuntimeErrorIf
record_baseline()has not been called yet.
See also
ant.tools.GEDAIDenoiserThe underlying GED denoiser class.
RTStream.compute_inv_operatorComputes the forward solution required for leadfield mode.
References
Ros, T., Férat, V., Huang, Y., et al. (2025). Return of the GEDAI: Unsupervised EEG Denoising based on Leadfield Filtering. bioRxiv. https://doi.org/10.1101/2025.10.04.680449
Examples
Leadfield mode (recommended — requires forward solution):
>>> nf.record_baseline(baseline_duration=120) >>> nf.compute_inv_operator() # builds the forward model >>> nf.fit_gedai(use_leadfield=True) >>> nf.record_main(duration=600, artifact_correction="gedai")
Band-filter mode (no MRI required):
>>> nf.record_baseline(baseline_duration=120) >>> nf.fit_gedai(band=(8, 13), use_leadfield=False) >>> nf.record_main(duration=600, artifact_correction="gedai")
- fit_asr(cutoff: float = 5.0, window_len: float = 1.0, max_dropout_fraction: float = 0.1, verbose: bool | str | None = None) None[source]#
Fit an
ASRDenoiserfrom the recorded baseline.Must be called after
record_baseline()and beforerecord_main()whenartifact_correction="asr".- Parameters:
- cutoff
float, default 5.0 Rejection threshold in standard deviations above the clean-data RMS per component. Lower values (e.g. 3) are more aggressive; higher values (e.g. 10) are more conservative. The Mullen et al. (2015) default is 5.0.
- window_len
float, default 1.0 Calibration window length in seconds. Shorter windows give more estimates but with higher variance. Recommend 0.5–1.0 s.
- max_dropout_fraction
float, default 0.1 Fraction of calibration windows with the highest total power to discard before estimating clean statistics.
0.1keeps the 90 % cleanest windows.- verbosebool |
str|None, defaultNone Override the instance-level verbosity for this call.
- cutoff
- Raises:
RuntimeErrorIf
record_baseline()has not been called yet.
See also
ant.tools.ASRDenoiserThe underlying ASR implementation.
RTStream.record_baselineRecords and stores the baseline segment.
References
Mullen, T. R., et al. (2015). Real-Time Neuroimaging and Cognitive Monitoring Using Wearable Dry EEG. IEEE Trans. Biomed. Eng., 62(11), 2553–2567.
Examples
>>> nf.record_baseline(baseline_duration=120) >>> nf.fit_asr(cutoff=5.0) >>> nf.record_main(duration=600, artifact_correction="asr")
- fit_maxwell(int_order: int = 8, ext_order: int = 3, origin: str | tuple = 'auto', st_duration: float | None = None, st_correlation: float = 0.98, st_update_interval: int = 1, calibration: str | None = None, cross_talk: str | None = None, coord_frame: str = 'head', regularize: str | None = 'in', mag_scale: float = 100.0, empty_room_raw: Any | None = None, verbose: bool | str | None = None) None[source]#
Prepare real-time Maxwell filtering (SSS / tSSS) for MEG data.
Computes the Signal Space Separation (SSS) projection operator once from sensor geometry. No baseline recording is required — the SSS basis is entirely geometric. Call this method at any point before
record_main()whenartifact_correction="maxwell".- Parameters:
- int_order
int, default 8 Internal spherical-harmonic expansion order.
8→ 80 internal moments (MNE default, adequate for all standard MEG systems).- ext_order
int, default 3 External expansion order (
3→ 16 external moments).- originarray_like
ofshape(3,) | “auto”, default “auto” SSS expansion origin in metres (head frame).
"auto"places it at the geometric centre of the sensor array.- st_duration
float|None, defaultNone Temporal SSS (tSSS) buffer duration in seconds.
None— spatial SSS only. The pre-computed projector is applied per chunk via a single matrix multiply.float— tSSS mode. A rolling buffer ofst_durationseconds feeds MNE’s full tSSS everyst_update_intervalchunks for temporal interference suppression. The spatial SSS projector is always applied first (zero-latency stage 1).
Typical values: 10 s for persistent shielding leakage, 1–4 s for moving subjects.
- st_correlation
float, default 0.98 Minimum inside-outside correlation for tSSS suppression.
- st_update_interval
int, default 1 Apply tSSS every N chunks. Increase to reduce CPU load when
winsizeis small.- calibration
str|None, defaultNone Path to the fine-calibration
.datfile. Strongly recommended for Elekta/MEGIN systems — it corrects sensor position and orientation errors.- cross_talk
str|None, defaultNone Path to the cross-talk
.fiffile. Compensates for flux leakage between adjacent sensors.- coord_frame{“head”, “meg”}, default “head”
Coordinate frame for the spherical-harmonic expansion.
- regularize{“in”,
None}, default “in” Internal-moment regularisation passed to MNE.
"in"(Tikhonov) is recommended for most datasets.- mag_scale
float, default 100.0 Magnetometer/gradiometer balance factor in the SSS decomposition.
- empty_room_raw
mne.io.Raw|None, defaultNone Empty-room recording (shielded room, no subject). When provided, the SSS operator is extracted via system identification so that noise-informed regularisation from the empty room is incorporated into the cached matrix. This improves suppression of spatially correlated sensor noise and is equivalent to passing
noise_covtomaxwell_filter().When
None, geometric regularisation only is used viacompute_maxwell_basis()(faster, adequate when shielding is good).- verbosebool |
str|None, defaultNone Override instance-level verbosity.
- int_order
- Raises:
RuntimeErrorIf no LSL stream has been connected yet (
connect_to_lsl()must be called first so thatself.rec_infois populated).ValueErrorIf this instance was initialised with
data_type="eeg"(Maxwell filtering is MEG-only).
Notes
See
RTMaxwellFilterfor the underlying filter class andfit_asr()for the EEG alternative (ASR).Unlike
fit_asr()andfit_gedai(), no baseline recording is needed. The SSS operator depends only on sensor positions, not on brain signal statistics. Simply call:nf.connect_to_lsl() nf.fit_maxwell() # operator ready in seconds nf.record_main(duration=600, artifact_correction="maxwell")
If a fine-calibration file is available, passing it via
calibrationtypically reduces the RMS noise floor by 10–20 % compared to the uncalibrated SSS.References
Taulu, S., Kajola, M., & Simola, J. (2004). Suppression of interference and artifacts by the Signal Space Separation Method. Brain Topogr., 16(4), 269–275.
Taulu, S., & Simola, J. (2006). Spatiotemporal signal space separation method for rejecting nearby interference in MEG measurements. Phys. Med. Biol., 51(7), 1759–1768.
Examples
SSS-only (fastest, no latency):
>>> nf.connect_to_lsl() >>> nf.fit_maxwell() >>> nf.record_main(duration=600, artifact_correction="maxwell")
tSSS with fine calibration and empty room:
>>> import mne >>> er_raw = mne.io.read_raw_fif("empty_room.fif", preload=True) >>> nf.connect_to_lsl() >>> nf.fit_maxwell( ... st_duration=10.0, ... calibration="sss_cal.dat", ... cross_talk="ct_sparse.fif", ... empty_room_raw=er_raw, ... ) >>> nf.record_main(duration=600, artifact_correction="maxwell")
- compute_inv_operator(loose: float = 0.2, depth: float = 0.8, noise_cov_method: str = 'ad_hoc', reg: float = 0.1, *, src_type: str | None = None, volume_labels: list | None = None, data_cov: Any | None = None, data_cov_method: str = 'empirical', make_inverse: bool = True) None[source]#
Compute and save the inverse operator for source localisation.
Wraps MNE’s forward-solution and inverse-operator pipeline. Results are saved to
<subjects_dir>/<subject_id>/inv/.- Parameters:
- loose
float, default 0.2 Orientation constraint for cortical source dipoles.
0= fixed (normal to surface),1= fully free,0.2= loose (recommended — allows slight tangential component while favouring surface-normal currents).- depth
float|None, default 0.8 Depth-weighting exponent to compensate for the MNE bias towards superficial sources.
Nonedisables depth weighting;0.8is the MNE default. Higher values suppress surface bias more aggressively.- noise_cov_method
str, default “ad_hoc” How to estimate the noise covariance used by the inverse operator.
"ad_hoc"(default) —mne.make_ad_hoc_cov()creates a diagonal covariance from standard sensor-noise floors (1 µV for EEG, 20 fT for MEG grads, 200 fT/cm for MEG mags). Recommended when no dedicated noise recording is available: the resting-state baseline contains brain signal, not pure noise, so fitting an empirical covariance on it conflates signal and noise."empirical"— sample covariance from the baseline raw."shrunk"— Ledoit-Wolf shrinkage; more stable when n_channels ≈ n_samples."diagonal_fixed"— diagonal regularisation.
- reg
float, default 0.1 Per-channel-type regularisation added to the noise covariance diagonal via
mne.cov.regularize(). Applied only whennoise_cov_method != "ad_hoc". Set to0to skip. Helps numerical stability when the baseline recording is short relative to the number of channels.- src_type{“surface”, “volume”} |
None, defaultNone Overrides the session’s
source_spacefor this call.- volume_labels
listofstr|None, defaultNone Restrict a volumetric source space to these atlas labels. Strongly recommended: a whole-brain 5 mm grid is ~14 600 source points, while a typical ROI set totals a few hundred — the difference between a real-time-capable pipeline and one that is not.
- data_cov
instanceofCovariance|None, defaultNone Data covariance for beamforming.
Noneestimates it from the baseline recording. This is a separate quantity fromnoise_cov:mne.beamformer.make_lcmv()adapts its spatial filter to the data covariance, and passing a noise covariance in its place yields a filter that is not adaptive at all.- data_cov_method
str, default “empirical” Estimator for the data covariance when it is computed here.
- make_inversebool, default
True Build a minimum-norm inverse operator. Set
Falsefor a beamformer-only session.
- loose
- Raises:
RuntimeErrorIf
record_baseline()has not been called yet.
See also
mne.make_inverse_operatorUnderlying MNE function.
mne.compute_raw_covarianceNoise covariance estimation.
- open_stream_viewer(bufsize: float = 0.2) None[source]#
Open the mne-lsl StreamViewer for raw M/EEG monitoring.
- Parameters:
- bufsize
float, default 0.2 Display window size (s).
- bufsize
- Raises:
RuntimeErrorIf the session was connected via
connect_to_array()— the StreamViewer connects over real LSL, which an array-backed session never publishes to.
- save(nf_data: bool = True, acq_delay: bool = True, artifact_delay: bool = True, method_delay: bool = True, raw_data: bool = False, bids_tsv: bool = False, format: str = 'json', delay_include_trace: bool = False, disconnect: bool = True) dict[str, Path][source]#
Save session outputs and, by default, disconnect the LSL stream.
All output files share the stem set at the start of
record_main()— see Notes for its shape. Two sessions saved without arunindex do overwrite each other; passrun=torecord_main(), asrun_blocks()does.- Parameters:
- nf_databool, default
True Save feature time-series as
beh/<stem>_task-neurofeedback_beh.json. The JSON contains a"meta"block (subject, modalities, sfreq, duration, artifact correction, artifact rate, SNR, start/end timestamps) and a"data"block with per-modality value lists. Whentrack_snr=Truewas passed torecord_main(), the per-window SNR series is included in"data"under the key"snr_db". Reward magnitudes delivered by the protocol are saved under"reward_<modality>"keys (one per modality).- acq_delaybool, default
True Include acquisition-loop timing in the delays file (only written when
estimate_delays=Truewas set inrecord_main()).- artifact_delaybool, default
True Include artifact-correction timing in the delays file.
- method_delaybool, default
True Include per-modality feature-extraction timing in the delays file.
- raw_databool, default
False Save the pre-correction M/EEG acquired during the main session as
eeg/<stem>_task-neurofeedback_eeg.fif.- bids_tsvbool, default
False Additionally write a BIDS-compliant tab-separated values file
beh/<stem>_task-neurofeedback_beh.tsvalongside the JSON. Columns are: one per modality,reward_<modality>per modality, andsnr_dbwhen available. Each row is one analysis window. This file passes a BIDS validator and can be loaded directly by EEGLAB, Fieldtrip, or any TSV reader.- format
str, default “json” Serialisation format for NF data and delays. Currently only
"json"is supported.- delay_include_tracebool, default
False Embed the full per-window delay trace (ms) in the delays JSON alongside the summary statistics. Can be large for long sessions.
- nf_databool, default
- Returns:
Notes
Output filenames are built from the BIDS stem
sub-<ID>_ses-<session>_task-neurofeedback, plus a_run-<NN>entity whenrecord_main()was given one.Disconnects the LSL stream as a side effect, because the stream is not normally needed once the session ends. Pass
disconnect=Falseto keep it —run_blocks()does, since a block is not the end of the session. Note the mock player is stopped rather than paused, andconnect_to_lsl()builds a new one, so a disconnected mock session cannot be resumed by reconnecting the stream alone.
- classmethod load_nf_data(path: str | Path) dict[source]#
Load a saved NF data file produced by
save().- Parameters:
- path
str|Path Path to a
*_nf.jsonfile.
- path
- Returns:
- payload
dict Dict with two keys:
"meta"— session metadata: subject ID, session type, modalities, sfreq, winsize, duration, n_windows, artifact correction, start/end timestamps."data"—{modality: [values, …]}per-modality lists.
- payload
Examples
>>> d = RTStream.load_nf_data("subjects/sub-sub01/ses-01/beh/sub-sub01_ses-01_task-neurofeedback_beh.json") >>> import numpy as np >>> alpha = np.array(d["data"]["sensor_power"]) >>> print(f"Mean alpha power: {alpha.mean():.3e}") >>> print(f"Session sfreq: {d['meta']['sfreq_hz']} Hz")
- create_report(overwrite: bool = True, include_psd: bool = True, include_nf_signal: bool = True, open_browser: bool = False, *, run: int | None = None, include_markers: bool = True, include_quality: bool = True, include_source: bool = True, include_columns: bool = True) Path[source]#
Generate a self-contained HTML report for the session.
The report is organised into sections: a session summary, the neurofeedback traces, markers and gating, data quality, the source-space configuration, the baseline recording, and the column dictionary. Sections whose data the session does not have are omitted, so a plain sensor-space run produces a shorter report rather than empty panels.
The summary is rendered from the same
metablocksave()writes into the JSON, so the two cannot disagree.- Parameters:
- overwritebool, default
True Overwrite an existing report file with the same name.
- include_psdbool, default
True Add a baseline power-spectral-density plot (1–40 Hz).
- include_nf_signalbool, default
True Add the feature time-series, with task-condition shading and the windows that were rewarded.
- open_browserbool, default
False Open the saved report in the default web browser.
- run
int|None BIDS
run-entity. Defaults to the runrecord_main()recorded, so the blocks of arun_blocks()session each get their own report instead of overwriting one another.- include_markersbool, default
True Add the marker raster and the gating summary, if a marker stream was attached.
- include_qualitybool, default
True Add SNR, artifact rate, dropped windows, and the latency summary.
- include_sourcebool, default
True Add the ROIs, atlas and inverse method of each source-space modality.
- include_columnsbool, default
True Add the data dictionary describing every saved column.
- overwritebool, default
- Returns:
- report_path
pathlib.Path Full path of the saved HTML report file.
- report_path
- Raises:
RuntimeErrorIf
record_baseline()has not been called yet.