Skip to content

Commit 79206e5

Browse files
committed
FIX: Read Nihon Kohden annotation file accurately
Fix #11267.
1 parent 68b5523 commit 79206e5

File tree

2 files changed

+54
-12
lines changed

2 files changed

+54
-12
lines changed

doc/changes/devel/13251.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Read Nihon Kohden annotation file accurately, by `Tom Ma`_.

mne/io/nihon/nihon.py

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,41 @@ def _read_nihon_header(fname):
276276
return header
277277

278278

279+
def _read_event_log_block(fid, t_block, device_type):
280+
fid.seek(0x92 + t_block * 20)
281+
t_blk_address = np.fromfile(fid, np.uint32, 1)[0]
282+
if t_blk_address == 0:
283+
return
284+
285+
fid.seek(t_blk_address + 0x1)
286+
data_name = np.fromfile(fid, "|S16", 1).astype("U16")[0]
287+
if data_name != device_type:
288+
return
289+
290+
fid.seek(t_blk_address + 0x12)
291+
n_logs = np.fromfile(fid, np.uint8, 1)[0]
292+
fid.seek(t_blk_address + 0x14)
293+
t_logs = np.fromfile(fid, "|S45", n_logs)
294+
return t_logs
295+
296+
297+
def _parse_event_log(event_log):
298+
t_desc = event_log[:20]
299+
hour, minute, second = (
300+
int(event_log[20:22]),
301+
int(event_log[22:24]),
302+
int(event_log[24:26]),
303+
)
304+
t_onset = hour * 3600 + minute * 60 + second
305+
return t_desc, t_onset
306+
307+
308+
def _parse_sub_event_log(sub_event_log):
309+
t_sub_desc = sub_event_log[:20]
310+
t_sub_onset = int(sub_event_log[24:30]) / 1e6
311+
return t_sub_desc, t_sub_onset
312+
313+
279314
def _read_nihon_annotations(fname):
280315
fname = _ensure_path(fname)
281316
log_fname = fname.with_suffix(".LOG")
@@ -292,27 +327,33 @@ def _read_nihon_annotations(fname):
292327
n_logblocks = np.fromfile(fid, np.uint8, 1)[0]
293328
all_onsets = []
294329
all_descriptions = []
330+
may_have_sub_blocks = n_logblocks <= 21
295331
for t_block in range(n_logblocks):
296-
fid.seek(0x92 + t_block * 20)
297-
t_blk_address = np.fromfile(fid, np.uint32, 1)[0]
298-
fid.seek(t_blk_address + 0x12)
299-
n_logs = np.fromfile(fid, np.uint8, 1)[0]
300-
fid.seek(t_blk_address + 0x14)
301-
t_logs = np.fromfile(fid, "|S45", n_logs)
302-
for t_log in t_logs:
332+
t_logs = _read_event_log_block(fid, t_block, version)
333+
t_sub_logs = None
334+
if may_have_sub_blocks:
335+
t_sub_logs = _read_event_log_block(fid, t_block + 22, version)
336+
assert t_sub_logs is None or len(t_logs) == len(t_sub_logs)
337+
338+
for i, t_log in enumerate(t_logs):
339+
t_desc, t_onset = _parse_event_log(t_log)
340+
if t_sub_logs is not None:
341+
t_sub_desc, t_sub_onset = _parse_sub_event_log(t_sub_logs[i])
342+
t_desc += t_sub_desc
343+
t_onset += t_sub_onset
344+
345+
t_desc = t_desc.rstrip(b"\x00")
303346
for enc in _encodings:
304347
try:
305-
t_log = t_log.decode(enc)
348+
t_desc = t_desc.decode(enc)
306349
except UnicodeDecodeError:
307350
pass
308351
else:
309352
break
310353
else:
311-
warn(f"Could not decode log as one of {_encodings}")
354+
warn(f"Could not decode log {t_desc} as one of {_encodings}")
312355
continue
313-
t_desc = t_log[:20].strip("\x00")
314-
t_onset = datetime.strptime(t_log[20:26], "%H%M%S")
315-
t_onset = t_onset.hour * 3600 + t_onset.minute * 60 + t_onset.second
356+
316357
all_onsets.append(t_onset)
317358
all_descriptions.append(t_desc)
318359

0 commit comments

Comments
 (0)