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
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
22 changes: 15 additions & 7 deletions mne/io/nihon/nihon.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,14 @@ def _read_21e_file(fname):
break
else:
warn(
f"Could not decode 21E file as one of {_encodings}; "
f"Could not decode {e_fname} as one of {_encodings}; "
f"Default channel names are chosen."
)
else:
warn(
f"Could not find {e_fname} containing channel definitions; "
f"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, 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())