-
Notifications
You must be signed in to change notification settings - Fork 1.4k
FIX: Read Nihon Kohden annotation file accurately #13251
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
The tests failed because:
I saw a note here:
There are only two events in the .LOG file, but four annotations in the .EDF file. So I wrote a test script: # encoding: utf-8
import os.path
import urllib.request
import edfio
from mne.io.nihon import read_raw_nihon
def download_file(url: str, output_path: str):
try:
urllib.request.urlretrieve(url, output_path)
return True
except Exception as e:
print(e)
return False
def test_edf():
file = r"MB0400FU.EDF"
if not os.path.exists(file):
if not download_file("https://raw.githubusercontent.com/mne-tools/mne-testing-data/refs/heads/master/NihonKohden/MB0400FU.EDF",
file):
return
edf = edfio.read_edf(file)
for annotation in edf.get_annotations():
print(annotation)
def test_eeg():
eeg_file = "MB0400FU.EEG"
elec_file = "MB0400FU.21E"
pnt_file = "MB0400FU.PNT"
log_file = "MB0400FU.LOG"
if not os.path.exists(eeg_file):
if not download_file("https://raw.githubusercontent.com/mne-tools/mne-testing-data/refs/heads/master/NihonKohden/MB0400FU.EEG",
eeg_file):
return
if not os.path.exists(elec_file):
if not download_file("https://raw.githubusercontent.com/mne-tools/mne-testing-data/refs/heads/master/NihonKohden/MB0400FU.21E",
elec_file):
return
if not os.path.exists(pnt_file):
if not download_file("https://raw.githubusercontent.com/mne-tools/mne-testing-data/refs/heads/master/NihonKohden/MB0400FU.PNT",
pnt_file):
return
if not os.path.exists(log_file):
if not download_file("https://raw.githubusercontent.com/mne-tools/mne-testing-data/refs/heads/master/NihonKohden/MB0400FU.LOG",
log_file):
return
raw = read_raw_nihon(eeg_file)
for onset, duration, description in zip(
raw.annotations.onset,
raw.annotations.duration,
raw.annotations.description,
):
print(onset, description)
if __name__ == "__main__":
test_edf()
test_eeg() Output:
|
It appears that MB0400FU.EDF is suspicious. Therefore, I used nk2edf to convert By the way, the https://gitlab.com/Teuniz/EDFbrowser/-/blob/master/edf_annotations.cpp?ref_type=heads#L157 When reading MB0400FU_1-1+.zip using |
I also noticed that Nihon Kohden's software supports a type of annotation called |
Therefore, I believe this test failure should be addressed by updating MB0400FU.EDF in the following way:
|
011fe40
to
fe60298
Compare
Reference issue (if any)
Fix #11267.
What does this implement/fix?
This PR adds support for reading sub event log blocks in Nihon Kohden EEG annotation files (.LOG).
In certain versions of the .LOG files, in addition to the standard event log blocks, there are sub event log blocks.
HHMMSS
format.cccuuu
.Additional information
I noticed that @jacobshaw42 also attempted something similar in #11431, but for some reason, the PR was closed.
This PR differs from #11431 in the following ways:
Sub event log blocks are not limited to 'EEG-1200A V01.00'
For example, in MB0400FU.EEG, the device type is
EEG-1100C V01.00
, yet it does contain sub event log blocks.Nihon Kohden does not clearly specify which device types or software versions generate .LOG files that include sub event log blocks. I previously tried to implement a function to determine whether sub event log blocks are present, based on the device type:
However, I found this approach overly complicated, so I switched to a more general strategy.
In Nihon Kohden .LOG files, the control block can define up to 43 event log blocks. When sub event blocks are present:
Therefore, this PR assumes that sub event log blocks are present when the number of log blocks (n_logblocks) parsed from the control block does not exceed 21.
BTW, in nk2edf, the presence of sub event log blocks is assumed.
Since the logic for reading event blocks and sub event blocks is largely similar, I refactored the relevant code in
_read_nihon_annotations
into a helper function_read_event_log_block
. Two conditions are used to ensure a sub event block is valid:Decode event description at last
Because event text can be split across the event log block and the sub event log block, this PR concatenates the byte strings from both blocks before decoding. This affects the following logic:
strptime
method can no longer be used to parse theHHMMSS
time(which is a byte string, not astr
). Instead, the time is parsed using int for each component.