Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 1 addition & 8 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Unset header
# checkout@v2 adds a header that makes branch protection report errors
# because the Github action bot is not a collaborator on the repo
run: git config --local --unset http.https://github.com/.extraheader
- name: Fetch tags
run: git fetch --prune --unshallow
- name: Disable etelemetry
Expand Down Expand Up @@ -87,10 +83,7 @@ jobs:
with:
submodules: recursive
fetch-depth: 0
- name: Unset header
# checkout@v2 adds a header that makes branch protection report errors
# because the Github action bot is not a collaborator on the repo
run: git config --local --unset http.https://github.com/.extraheader

- name: Set up Python
uses: actions/setup-python@v6
with:
Expand Down
5 changes: 2 additions & 3 deletions extras/fileformats/extras/biosig/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from ._version import __version__
from . import eeg
from . import meg
from . import edf

__all__ = ["__version__", "eeg", "meg"]
__all__ = ["__version__", "edf"]
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import os
import configparser
import typing as ty
import tempfile
from pathlib import Path
Expand All @@ -8,7 +7,7 @@
import mne.export

from fileformats.core import extra_implementation, FileSet
from fileformats.biosig import Biosig, BrainVision, Edf, EdfPlus
from fileformats.biosig import Biosig, Edf, EdfPlus

from .utils import mne_deidentify

Expand All @@ -31,17 +30,6 @@ def edf_plus_read_metadata(edf: EdfPlus, **kwargs: ty.Any) -> ty.Mapping[str, ty
}


@extra_implementation(FileSet.read_metadata)
def brain_vision_read_metadata(
bv: BrainVision, **kwargs: ty.Any
) -> ty.Mapping[str, ty.Any]:
raw = mne.io.read_raw_brainvision(bv.header_file, preload=False, verbose=False)
return {
**raw.info.to_json_dict(),
**_parse_vhdr(bv.header_file),
}


@extra_implementation(Biosig.deidentify)
def edf_deidentify(
edf: Edf,
Expand All @@ -58,22 +46,6 @@ def edf_deidentify(
return type(edf)(deid_fspath), reid


@extra_implementation(Biosig.deidentify)
def brain_vision_deidentify(
bv: BrainVision,
spec: ty.Any = None,
out_dir: os.PathLike[str] | None = None,
) -> tuple[BrainVision, dict[str, ty.Any]]:
out_dir = Path(tempfile.mkdtemp() if out_dir is None else out_dir)
out_dir.mkdir(parents=True, exist_ok=True)
raw = mne.io.read_raw_brainvision(bv.header_file, preload=True, verbose=False)
deidentified_info, reid = mne_deidentify(raw, spec)
raw.info = deidentified_info
deid_vhdr = out_dir / "eeg.vhdr"
mne.export.export_raw(deid_vhdr, raw, fmt="brainvision", overwrite=True)
return BrainVision(out_dir / "eeg.eeg"), reid


def _parse_edf_header(path: os.PathLike[str]) -> dict[str, ty.Any]:
"""
Parse EDF/EDF+ header bytes directly for patient and recording fields
Expand Down Expand Up @@ -112,42 +84,3 @@ def _parse_edf_header(path: os.PathLike[str]) -> dict[str, ty.Any]:
"edf_start_time": start_time,
"edf_subtype": reserved if reserved.startswith("EDF+") else None,
}


def _parse_vhdr(path: os.PathLike[str]) -> dict[str, ty.Any]:
"""
Parse a BrainVision .vhdr file (INI format) for fields not exposed by MNE.
Extracts acquisition settings and the free-text Comment section.
"""
parser = configparser.RawConfigParser()
# vhdr files start with a magic line before the first INI section — skip it
with open(path, encoding="utf-8-sig", errors="replace") as f:
lines = f.readlines()
ini_lines = [line for line in lines if not line.startswith("Brain Vision")]
parser.read_string("".join(ini_lines))

def get(
section: ty.Any,
key: str,
fallback: ty.Any = None,
) -> ty.Any:
try:
return parser.get(section, key)
except (configparser.NoSectionError, configparser.NoOptionError):
return fallback

# [Common Infos] uses inconsistent casing across BrainVision versions
common = next((s for s in parser.sections() if s.lower() == "common infos"), None)

comment_lines = []
if parser.has_section("Comment"):
comment_lines = [v for _, v in parser.items("Comment") if v.strip()]

return {
"bv_data_format": get(common, "DataFormat") if common else None,
"bv_data_orientation": get(common, "DataOrientation") if common else None,
"bv_n_channels": get(common, "NumberOfChannels") if common else None,
"bv_sampling_interval_us": get(common, "SamplingInterval") if common else None,
"bv_binary_format": get("Binary Infos", "BinaryFormat"),
"bv_comment": "\n".join(comment_lines) if comment_lines else None,
}
43 changes: 0 additions & 43 deletions extras/fileformats/extras/biosig/meg.py

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@
- miaocao@swin.edu.au
"""

from fileformats.biosig import (
BrainVision,
EdfPlus,
)
from fileformats.biosig import EdfPlus

# ------------------------------
# EEG: EDF
Expand All @@ -25,15 +22,3 @@ def test_edf_plus_read_metadata(edf_plus_path):
assert isinstance(metadata, dict)
assert metadata["sfreq"] is not None
assert "edf_patient_code" in metadata


# ------------------------------
# EEG: BrainVision
# ------------------------------


def test_brainvision_read_metadata(bv_vhdr_path):
metadata = BrainVision(bv_vhdr_path.with_suffix(".eeg")).metadata
assert isinstance(metadata, dict)
assert metadata["sfreq"] is not None
assert "bv_n_channels" in metadata
71 changes: 0 additions & 71 deletions extras/fileformats/extras/biosig/tests/test_meg_extras.py

This file was deleted.

33 changes: 2 additions & 31 deletions fileformats/biosig/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,10 @@
- miaocao@swin.edu.au
"""

from .base import Biosig
from .eeg import (
Eeg,
from .base import Biosig, Eeg, Meg
from .edf import (
Edf,
EdfPlus,
BrainVisionHeader,
BrainVisionMarker,
BrainVision,
)
from .meg import (
Meg,
CtfMeg4,
CtfRes4,
CtfInfo,
Ctf,
Fif,
KitMark,
KitHeadPosition,
KitSensorInfo,
Kit,
)

from ._version import __version__
Expand All @@ -38,20 +22,7 @@
"__version__",
"Biosig",
"Eeg",
"Fif",
"FifGz",
"Edf",
"EdfPlus",
"BrainVisionHeader",
"BrainVisionMarker",
"BrainVision",
"Meg",
"CtfMeg4",
"CtfRes4",
"CtfInfo",
"Ctf",
"KitMark",
"KitHeadPosition",
"KitSensorInfo",
"Kit",
]
16 changes: 16 additions & 0 deletions fileformats/biosig/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,19 @@ def deidentify(
were stripped/modified during the deidentification process.
"""
raise NotImplementedError


class Eeg(Biosig):
"""Base class for all Electroencephalography recordings"""

pass


# ------------------------------
# Base MEG Type (Abstract Class)
# ------------------------------
class Meg(Biosig):
"""
Base class for MEG data formats
All specific MEG formats inherit from this class with unified validation logic
"""
Loading