Skip to content

Add EEGBCI dataset and tasks#1177

Open
vihaan101 wants to merge 21 commits into
sunlabuiuc:masterfrom
vihaan101:eegbci-pattern-discovery
Open

Add EEGBCI dataset and tasks#1177
vihaan101 wants to merge 21 commits into
sunlabuiuc:masterfrom
vihaan101:eegbci-pattern-discovery

Conversation

@vihaan101

@vihaan101 vihaan101 commented Jul 8, 2026

Copy link
Copy Markdown

Contributor: Vihaan Agrawal

Contribution Type: Dataset + Task + Example + Documentation

Description:
Adds support for the PhysioNet EEG Motor Movement/Imagery (EEGBCI) dataset as a PyHealth EEG pipeline.

  • Dataset: Adds EEGBCIDataset, dataset configuration, local EDF discovery, optional MNE-based download, and metadata generation for selected subjects and runs.
  • Task: Adds EEGBCI motor execution and motor imagery label mapping, channel selection, normalization, band-power features, and pattern discovery sample generation.
  • Example: Adds an EEGBCI pattern discovery example and README for running the pipeline.
  • Documentation: Registers the new dataset and task in the API docs.
  • Tests: Adds core coverage for dataset metadata preparation, download handling, label mapping, signal helpers, feature extraction, and task integration.

Data Access:
EEGBCI files are loaded from local EDF files when available, or fetched through MNE with download=True.

Files to Review:

File Description
pyhealth/datasets/eegbci.py EEGBCI dataset loader and metadata preparation
pyhealth/datasets/configs/eegbci.yaml Dataset table configuration
pyhealth/tasks/eegbci.py EEGBCI task labels, signal helpers, feature extraction, and pattern discovery task
examples/eeg/eegbci/eegbci_pattern_discovery.py End-to-end pattern discovery example
examples/eeg/eegbci/README.md Example usage notes
docs/api/datasets/pyhealth.datasets.EEGBCIDataset.rst Dataset API documentation
docs/api/tasks/pyhealth.tasks.eegbci.rst Task API documentation
tests/core/test_eegbci.py Dataset and task test coverage

Testing:

  • make testall
    • core: 1179 tests passed, 73 skipped
    • NLP: 2 tests passed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds a full EEGBCI (PhysioNet EEG Motor Movement/Imagery) pipeline to PyHealth, including a dataset loader, task implementations, a pattern-discovery example/reporting utilities, API documentation registration, and comprehensive core tests.

Changes:

  • Introduces EEGBCIDataset with local EDF discovery, optional MNE download, and metadata CSV generation.
  • Adds EEGBCI motor execution/imagery task logic, channel selection, normalization, bandpower feature extraction, and a pattern-discovery task.
  • Adds an end-to-end pattern discovery example + README, updates API docs, and adds extensive tests.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/core/test_eegbci.py Adds unit tests covering dataset metadata, download path, label mapping, signal helpers, features, and example integration.
pyhealth/tasks/eegbci.py Implements EEGBCI label/run helpers, channel selection, normalization, bandpower features, and two EEGBCI tasks.
pyhealth/tasks/init.py Exposes the new EEGBCI tasks from the tasks package.
pyhealth/datasets/eegbci.py Implements EEGBCI metadata preparation and dataset wiring to BaseDataset.
pyhealth/datasets/configs/eegbci.yaml Registers the records table schema for EEGBCI metadata CSV.
pyhealth/datasets/init.py Exposes EEGBCIDataset from the datasets package.
examples/eeg/eegbci/eegbci_pattern_discovery.py Adds the pattern discovery CLI, CSV export, and markdown report rendering.
examples/eeg/eegbci/README.md Documents running the EEGBCI pattern discovery pipeline and output semantics.
docs/api/datasets/pyhealth.datasets.EEGBCIDataset.rst Adds dataset API doc entry for EEGBCI.
docs/api/tasks/pyhealth.tasks.eegbci.rst Adds task API doc entry for EEGBCI tasks.
docs/api/tasks.rst Registers the EEGBCI tasks page in the tasks index.
docs/api/datasets.rst Registers the EEGBCI dataset page in the datasets index.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +43 to +46
self.root = root
self.subjects = list(subjects) if subjects is not None else [1, 2, 3]
self.runs = list(runs) if runs is not None else list(range(3, 15))
self.download = download

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Addressed in 712619a. EEGBCIDataset now normalizes subjects and runs to sorted unique integers before building selection_key, dataset_name, or metadata, with focused regression coverage for duplicate/reordered selections.

Comment on lines +70 to +74
def _find_local_edf(self, subject: int, run: int) -> Path | None:
root = Path(self.root)
pattern = f"S{subject:03d}R{run:02d}.edf"
matches = sorted(root.rglob(pattern))
return matches[0] if matches else None

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Addressed in 712619a. _find_local_edf() now checks the canonical MNE EEGBCI path first and only falls back to recursive lookup for nonstandard cache layouts.

Comment on lines +94 to +97
root = Path(self.root)
csv_path = root / "eegbci-pyhealth.csv"
if csv_path.exists() and self._metadata_matches_request(csv_path):
return

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Addressed in 712619a. Metadata is now written to a selection-specific CSV filename derived from selection_key, and the loaded dataset config points at that file so separate selections do not overwrite each other.

Comment on lines +29 to +37
def parse_int_list(value: str) -> list[int]:
items: list[int] = []
for part in value.split(","):
if "-" in part:
start, end = part.split("-", 1)
items.extend(range(int(start), int(end) + 1))
else:
items.append(int(part))
return items

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Addressed in 712619a. parse_int_list() now strips each comma-separated part, rejects empty entries, and raises a clear ValueError for descending ranges like 5-3.

"subjects": parse_int_list(args.subjects),
"runs": parse_int_list(args.runs),
"max_windows": args.max_windows,
"baseline_row_count": len(all_rows),

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Addressed in 712619a. The report now computes baseline_row_count from rows with task_label == "rest", so “Baseline source rows” reflects the rest windows used for baseline construction instead of all windows.

@vihaan101 vihaan101 marked this pull request as ready for review July 9, 2026 16:02
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