Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions doc/changes/dev/13468.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Correctly set the calibration factor in Nihon Kohden reader (which affects channel amplitudes), by `Tom Ma`_.
4 changes: 2 additions & 2 deletions mne/datasets/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
# update the checksum in the MNE_DATASETS dict below, and change version
# here: ↓↓↓↓↓↓↓↓
RELEASES = dict(
testing="0.166",
testing="0.167",
misc="0.27",
phantom_kit="0.2",
ucl_opm_auditory="0.2",
Expand Down Expand Up @@ -115,7 +115,7 @@
# Testing and misc are at the top as they're updated most often
MNE_DATASETS["testing"] = dict(
archive_name=f"{TESTING_VERSIONED}.tar.gz",
hash="md5:273c5919cf74198a39146e9cbc146ce0",
hash="md5:d82318a83b436ca2c7ca8420487c05c2",
url=(
"https://codeload.github.com/mne-tools/mne-testing-data/"
f"tar.gz/{RELEASES['testing']}"
Expand Down
20 changes: 14 additions & 6 deletions mne/io/nihon/nihon.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,11 @@ def _read_21e_file(fname):
f"Could not decode 21E file as one of {_encodings}; "
f"Default channel names are chosen."
)
else:
warn(
"Could not find the 21E file containing channel definitions; "
"Default channel names are chosen."
)

return _chan_labels

Expand Down Expand Up @@ -280,7 +285,7 @@ def _read_nihon_header(fname):
"Cannot read NK file with different sfreq in each datablock"
)

return header
return header, _chan_labels


def _read_event_log_block(fid, t_block, version):
Expand Down Expand Up @@ -380,13 +385,13 @@ def _map_ch_to_type(ch_name):
return "eeg"


def _map_ch_to_specs(ch_name):
def _map_ch_to_specs(ch_name, chan_labels_upper):
unit_mult = 1e-3
phys_min = -12002.9
phys_max = 12002.56
dig_min = -32768
if ch_name.upper() in _default_chan_labels:
idx = _default_chan_labels.index(ch_name.upper())
if ch_name.upper() in chan_labels_upper:
idx = chan_labels_upper.index(ch_name.upper())
if (idx < 42 or idx > 73) and idx not in [76, 77]:
unit_mult = 1e-6
phys_min = -3200
Expand Down Expand Up @@ -432,7 +437,9 @@ def __init__(self, fname, preload=False, *, encoding="utf-8", verbose=None):
data_name = fname.name
logger.info(f"Loading {data_name}")

header = _read_nihon_header(fname)
# chan_labels are electrode codes defined in the .21E file.
# It is not the same as header["ch_names"].
header, chan_labels = _read_nihon_header(fname)
metadata = _read_nihon_metadata(fname)

# n_chan = len(header['ch_names']) + 1
Expand All @@ -447,8 +454,9 @@ def __init__(self, fname, preload=False, *, encoding="utf-8", verbose=None):
if "meas_date" in metadata:
with info._unlock():
info["meas_date"] = metadata["meas_date"]
chs = {x: _map_ch_to_specs(x) for x in info["ch_names"]}

chan_labels_upper = [x.upper() for x in chan_labels]
chs = {x: _map_ch_to_specs(x, chan_labels_upper) for x in info["ch_names"]}
cal = np.array([chs[x]["cal"] for x in info["ch_names"]], float)[:, np.newaxis]
offsets = np.array([chs[x]["offset"] for x in info["ch_names"]], float)[
:, np.newaxis
Expand Down
39 changes: 37 additions & 2 deletions mne/io/nihon/tests/test_nihon.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def test_nihon_eeg():
raw_edf.drop_channels(["Events/Markers"])

assert raw._data.shape == raw_edf._data.shape
assert raw.info["sfreq"] == raw.info["sfreq"]
assert raw.info["sfreq"] == raw_edf.info["sfreq"]
# a couple of ch names differ in the EDF
edf_ch_names = {"EEG Mark1": "$A2", "EEG Mark2": "$A1"}
raw_edf.rename_channels(edf_ch_names)
Expand All @@ -48,7 +48,7 @@ def test_nihon_eeg():
raw = read_raw_nihon(fname_edf, preload=True)

with pytest.raises(ValueError, match="Not a valid Nihon Kohden EEG file"):
raw = _read_nihon_header(fname_edf)
header, _ = _read_nihon_header(fname_edf)

bad_fname = data_path / "eximia" / "text_eximia.nxe"

Expand Down Expand Up @@ -90,3 +90,38 @@ def return_channel_duplicates(fname):
)
with pytest.warns(RuntimeWarning, match=msg):
read_raw_nihon(fname)


@testing.requires_testing_data
def test_nihon_calibration():
"""Test handling of calibration factor and range in Nihon Kohden EEG files."""
fname = data_path / "NihonKohden" / "DA00100E.EEG"
raw = read_raw_nihon(fname.as_posix(), preload=True, encoding="cp936")

Fp1_idx = raw.ch_names.index("Fp1")
M1_idx = raw.ch_names.index("M1")
M2_idx = raw.ch_names.index("M2")

Fp1_info = raw.info["chs"][Fp1_idx]
M1_info = raw.info["chs"][M1_idx]
M2_info = raw.info["chs"][M2_idx]

# M1, M2 are EEG channels, just like Fp1.
# So they should have the same calibration factor and physical range.
assert_allclose(M1_info["cal"], Fp1_info["cal"])
assert_allclose(M2_info["cal"], Fp1_info["cal"])
assert_allclose(M1_info["range"], Fp1_info["range"])
assert_allclose(M2_info["range"], Fp1_info["range"])

fname_edf = data_path / "NihonKohden" / "DA00100E.EDF"
raw_edf = read_raw_edf(fname_edf, preload=True)
raw_edf.drop_channels(["Events/Markers"])
# a couple of ch names differ in the EDF
edf_ch_names = {"EEG Mark1": "$M1", "EEG Mark2": "$M2"}
raw_edf.rename_channels(edf_ch_names)

assert raw.ch_names == raw_edf.ch_names
assert raw._data.shape == raw_edf._data.shape
assert raw.info["sfreq"] == raw_edf.info["sfreq"]

assert_allclose(raw.get_data(), raw_edf.get_data())
Loading