File: test_nedf.py

package info (click to toggle)
python-mne 1.9.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 131,492 kB
  • sloc: python: 213,302; javascript: 12,910; sh: 447; makefile: 144
file content (127 lines) | stat: -rw-r--r-- 4,534 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
"""Test reading of NEDF format."""

# Authors: The MNE-Python contributors.
# License: BSD-3-Clause
# Copyright the MNE-Python contributors.

import pytest
from numpy.testing import assert_allclose, assert_array_equal

from mne import find_events
from mne._fiff.constants import FIFF
from mne.datasets import testing
from mne.io.nedf import _parse_nedf_header, read_raw_nedf
from mne.io.tests.test_raw import _test_raw_reader

eeg_path = testing.data_path(download=False, verbose=True)
eegfile = eeg_path / "nedf" / "testdata.nedf"

stimhdr = b"""
<nedf>
    <NEDFversion>1.3</NEDFversion>
    <NumberOfChannelsOfAccelerometer>%d</NumberOfChannelsOfAccelerometer>
    <EEGSettings>
        <TotalNumberOfChannels>4</TotalNumberOfChannels>
        <EEGSamplingRate>500</EEGSamplingRate>
        <EEGMontage><C>A</C><C>B</C><C>C</C><C>D</C></EEGMontage>
        <NumberOfRecordsOfEEG>11</NumberOfRecordsOfEEG>
    </EEGSettings>
    <STIMSettings/>
</nedf>\x00"""

pytest.importorskip("defusedxml")


@pytest.mark.parametrize("nacc", (0, 3))
def test_nedf_header_parser(nacc):
    """Test NEDF header parsing and dtype extraction."""
    with pytest.warns(RuntimeWarning, match="stim channels.*ignored"):
        info, dt, dt_last, n_samples, n_full = _parse_nedf_header(stimhdr % nacc)
    assert n_samples == 11
    assert n_full == 2
    nchan = 4
    assert info["nchan"] == nchan
    assert dt.itemsize == 200 + nacc * 2
    if nacc:
        assert dt.names[0] == "acc"
        assert dt["acc"].shape == (nacc,)

    assert dt["data"].shape == (5,)  # blocks of 5 EEG samples each
    assert dt_last["data"].shape == (1,)  # plus one last extra one

    eegsampledt = dt["data"].subdtype[0]
    assert eegsampledt.names == ("eeg", "stim", "trig")
    assert eegsampledt["eeg"].shape == (nchan, 3)
    assert eegsampledt["stim"].shape == (2, nchan, 3)


def test_invalid_headers():
    """Test that invalid headers raise exceptions."""
    tpl = b"""<nedf>
        <NEDFversion>1.3</NEDFversion>
        <EEGSettings>
            %s
            <EEGMontage><C>A</C><C>B</C><C>C</C><C>D</C></EEGMontage>
        </EEGSettings>
    </nedf>\x00"""
    nchan = b"<TotalNumberOfChannels>4</TotalNumberOfChannels>"
    sr = b"<EEGSamplingRate>500</EEGSamplingRate>"
    hdr = {
        "null": b"No null terminator",
        "Unknown additional": (
            b"<a><NEDFversion>1.3</NEDFversion>"
            + b"<AdditionalChannelStatus>???</AdditionalChannelStatus></a>\x00"
        ),  # noqa: E501
        "No EEG channels found": b"<a><NEDFversion>1.3</NEDFversion></a>\x00",
        "TotalNumberOfChannels not found": tpl % b"No nchan.",
        "!= channel count": tpl
        % (sr + b"<TotalNumberOfChannels>52</TotalNumberOfChannels>"),
        "EEGSamplingRate not found": tpl % nchan,
        "NumberOfRecordsOfEEG not found": tpl % (sr + nchan),
    }
    for match, invalid_hdr in hdr.items():
        with pytest.raises(RuntimeError, match=match):
            _parse_nedf_header(invalid_hdr)

    sus_hdrs = {
        "unsupported": b"<a><NEDFversion>25</NEDFversion></a>\x00",
        "tested": (
            b"<a><NEDFversion>1.3</NEDFversion><stepDetails>"
            + b"<DeviceClass>STARSTIM</DeviceClass></stepDetails></a>\x00"
        ),
    }
    for match, sus_hdr in sus_hdrs.items():
        with pytest.warns(RuntimeWarning, match=match):
            with pytest.raises(RuntimeError, match="No EEG channels found"):
                _parse_nedf_header(sus_hdr)


@testing.requires_testing_data
def test_nedf_data():
    """Test reading raw NEDF files."""
    raw = read_raw_nedf(eegfile)
    nsamples = len(raw)
    assert nsamples == 32538

    events = find_events(raw, shortest_event=1)
    assert len(events) == 4
    assert_array_equal(events[:, 2], [1, 1, 1, 1])
    onsets = events[:, 0] / raw.info["sfreq"]
    assert raw.info["sfreq"] == 500

    data_end = raw.get_data("Fp1", nsamples - 100, nsamples).mean()
    assert_allclose(data_end, 0.0176, atol=0.01)
    assert_allclose(raw.get_data("Fpz", 0, 100).mean(), 0.0185, atol=0.01)

    assert_allclose(onsets, [22.384, 38.238, 49.496, 63.15])
    assert raw.info["meas_date"].year == 2019
    assert raw.ch_names[2] == "AF7"

    for ch in raw.info["chs"][:-1]:
        assert ch["kind"] == FIFF.FIFFV_EEG_CH
        assert ch["unit"] == FIFF.FIFF_UNIT_V
    assert raw.info["chs"][-1]["kind"] == FIFF.FIFFV_STIM_CH
    assert raw.info["chs"][-1]["unit"] == FIFF.FIFF_UNIT_V

    # full tests
    _test_raw_reader(read_raw_nedf, filename=eegfile)