Skip to content

Cross-system transfer tutorial: masked-mean MDM with groups="device" (route metadata columns to pipelines) - #1305

Merged
bruAristimunha merged 6 commits into
NeuroTechX:developfrom
qbarthelemy:cross_system_evaluation
Oct 26, 2026
Merged

bruAristimunha merged 6 commits into
NeuroTechX:developfrom
qbarthelemy:cross_system_evaluation

Conversation

@qbarthelemy

@qbarthelemy qbarthelemy commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Solve #1221 (as a tutorial, after review)

Cross-system transfer, where each system is an EEG device with its own montage and sampling rate, is handled by the existing API instead of a new evaluation class:

This PR now contains

  • the tutorial examples/how_to_benchmark/plot_cross_system_transfer_maskedmean.py: union of channels, mask matrices, minimum distance to masked Riemannian mean vs. zero-filled and channel-intersection MDM. The pipeline steps read the device of each training trial and infer the held-out device, so every device is the target in turn;
  • generic metadata routing in _evaluate_fold: any metadata column of the training trials (device, dataset, subject...) is passed to the steps that request it with set_fit_request(<column>=True), only when requested;
  • a device argument for FakeDataset;
  • a fix of process() returning results several times when datasets share a processing pipeline (found here, with its test).

CrossSystemEvaluation and its cross-system-only routing are removed.

Tests: metadata-column routing (requested / not requested), leave-one-device-out routing only the training devices, duplicated results; the tutorial runs end to end. test_evaluations.py, test_paradigms.py and test_splits.py pass locally.

@bruAristimunha @sylvchev @plcrodrigues

@bruAristimunha bruAristimunha left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @qbarthelemy, the evaluation fits #1221 well, and the metadata routing of systems and the per-dataset result filtering are clean. A review round before it can go in (the branch also needs develop merged; it conflicts now):

Should fix

  1. __init__: self.datasets = list(target_datasets) replaces the list after BaseEvaluation.__init__ has filtered it, so targets skip the paradigm check, and a target that is not in datasets produces no fold and returns an empty result without any message. Suggest validating target_datasets against the filtered self.systems (same code) and raising otherwise.
  2. _load_data loads every system in full on each call, ignoring subjects, and is called once per target: leave-one-system-out on k systems does k full loads of all k datasets. Loading the systems once per process() (e.g. keep the concatenated X, y, metadata on the instance for the duration of the run) avoids the k² reads.
  3. _load_data also ignores run_pipes / process_pipeline: a pipeline that needs Epochs (TRCA, braindecode, ...) silently receives arrays, and a filter-bank paradigm gets a single band. Raising a clear ValueError for epoch-requiring pipelines (as done for postprocess_pipeline) is enough for now.

Minor

  1. With datasets=None, every dataset of the paradigm becomes a system and is loaded for each target; worth requiring explicit datasets here.
  2. The _process_parallel change (keep only the current dataset's rows) also fixes duplicated rows for the other evaluations when datasets share a pipeline digest; it deserves its own Bugs entry in whats_new.rst.

Happy to help with the develop merge once these are settled.

# Conflicts:
#	docs/source/whats_new.rst
#	moabb/tests/test_evaluations.py
…and epoch pipelines

Review follow-ups: the systems were reloaded in full for every target (k^2
loads), a target_datasets entry outside datasets gave an empty result, and
pipelines needing mne.Epochs silently received arrays.
@bruAristimunha

Copy link
Copy Markdown
Collaborator

@qbarthelemy I pushed two commits to your branch so this can go in: a merge of develop (keep-both on whats_new.rst and the tests) and 9f04831, the smallest fix for review points 1-3: the systems are loaded once per evaluation instead of once per target, a target_datasets entry outside datasets raises, and pipelines that need mne.Epochs raise instead of silently getting arrays (one new test). Points 4-5 stay optional. Shout if you'd rather do it differently.

@bruAristimunha

Copy link
Copy Markdown
Collaborator

@qbarthelemy following the review here, we went the other way round: instead of a new evaluation, the existing API now covers cross-system transfer, and your example becomes a tutorial.

Once #1328 is in, I'd like to turn this PR into the tutorial: keep your example (union of channels, masks, masked-mean classifier vs. channel intersection), run it through CompoundDataset + groups="device", and drop CrossSystemEvaluation and its evaluation changes. Authorship stays yours. If you'd rather do it yourself or keep a different shape, say so before I push.

# Conflicts:
#	docs/source/whats_new.rst
#	moabb/evaluations/utils.py
#	moabb/tests/test_evaluations.py
Review of NeuroTechX#1305 (Bruno): no new evaluation. The example now pools the
devices in a CompoundDataset and holds out one device at a time with
CrossSubjectEvaluation(cv_class=LeaveOneGroupOut, groups='device'), on the
nested trials of NeuroTechX#1327 and the device column of NeuroTechX#1328.

- remove CrossSystemEvaluation, its tests and extra_fit_params;
- _evaluate_fold routes any metadata column of the training trials to the
  steps requesting it (set_fit_request(device=True)), replacing the
  cross-system-only 'systems' routing;
- FakeDataset takes a device;
- the example's ChannelUnion and MDMM read the device of each training
  trial and infer the held-out device, so every device can be the target;
- keep the duplicated-results fix of process() and its test.
@bruAristimunha bruAristimunha changed the title Add CrossSystemEvaluation Cross-system transfer tutorial: masked-mean MDM with groups="device" (route metadata columns to pipelines) Oct 25, 2026
@bruAristimunha

Copy link
Copy Markdown
Collaborator

@qbarthelemy as announced above (no objection after a day), I pushed bf93c3d to turn this into the tutorial: CrossSystemEvaluation is gone, your example runs on CompoundDataset + CrossSubjectEvaluation(cv_class=LeaveOneGroupOut, groups="device"), and your pipeline steps request the device metadata column (any column can now be requested that way) instead of systems. Your process() duplicate-results fix stays, credited to you in the changelog. Description updated. If you'd rather adjust anything, push on top; nothing was force-pushed.

# Conflicts:
#	docs/source/whats_new.rst
@bruAristimunha
bruAristimunha merged commit 12c7ecb into NeuroTechX:develop Oct 26, 2026
14 of 15 checks passed
@qbarthelemy

Copy link
Copy Markdown
Contributor Author

Sorry, I’m often AFK on the weekends.
The way you’ve reworked the PR is great—thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants