Skip to content

Commit 7e698e2

Browse files
committed
ENH: Add support for reading embedded CT and FC
1 parent c6de8ab commit 7e698e2

9 files changed

Lines changed: 223 additions & 132 deletions

File tree

doc/changes/dev/newfeature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add support for reading cross-talk and fine-calibration embedded in FIF files during acquisition into ``raw.info["cross_talk"]`` and ``raw.info["fine_calibration"]``, respectively, by `Eric Larson`_.

mne/_fiff/meas_info.py

Lines changed: 57 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,12 @@
5858
get_channel_type_constants,
5959
pick_types,
6060
)
61-
from .proc_history import _read_proc_history, _write_proc_history
61+
from .proc_history import (
62+
_read_mf_data,
63+
_read_proc_history,
64+
_write_mf_data,
65+
_write_proc_history,
66+
)
6267
from .proj import (
6368
Projection,
6469
_normalize_proj,
@@ -1071,6 +1076,12 @@ class HeliumInfo(ValidatedDict):
10711076
types="numeric",
10721077
cast=float,
10731078
),
1079+
"gantry_angle": partial(
1080+
_check_types,
1081+
name='helium_info["gantry_angle"]',
1082+
types="numeric",
1083+
cast=int,
1084+
),
10741085
"helium_level": partial(
10751086
_check_types,
10761087
name='helium_info["helium_level"]',
@@ -1314,6 +1325,8 @@ class Info(ValidatedDict, SetChannelsMixin, MontageMixin, ContainsMixin):
13141325
comps : list of dict
13151326
CTF software gradient compensation data.
13161327
See Notes for more information.
1328+
cross_talk : dict | None
1329+
Cross-talk information added at acquisition time by MEGIN systems.
13171330
ctf_head_t : Transform | None
13181331
The transformation from 4D/CTF head coordinates to Neuromag head
13191332
coordinates. This is only present in 4D/CTF data.
@@ -1344,7 +1357,9 @@ class Info(ValidatedDict, SetChannelsMixin, MontageMixin, ContainsMixin):
13441357
Name of the person that ran the experiment.
13451358
file_id : dict | None
13461359
The FIF globally unique ID. See Notes for more information.
1347-
gantry_angle : float | None
1360+
fine_calibration : dict | None
1361+
Fine calibration information added at acquisition time by MEGIN systems.
1362+
gantry_angle : int | None
13481363
Tilt angle of the gantry in degrees.
13491364
helium_info : dict | None
13501365
Information about the device helium. See Notes for details.
@@ -1698,6 +1713,7 @@ class Info(ValidatedDict, SetChannelsMixin, MontageMixin, ContainsMixin):
16981713
"comps": "comps cannot be set directly. "
16991714
"Please use method Raw.apply_gradient_compensation() "
17001715
"instead.",
1716+
"cross_talk": "cross_talk cannot be set directly.",
17011717
"ctf_head_t": "ctf_head_t cannot be set directly.",
17021718
"custom_ref_applied": "custom_ref_applied cannot be set directly. "
17031719
"Please use method inst.set_eeg_reference() "
@@ -1711,6 +1727,7 @@ class Info(ValidatedDict, SetChannelsMixin, MontageMixin, ContainsMixin):
17111727
"events": "events cannot be set directly.",
17121728
"experimenter": partial(_check_types, name="experimenter", types=(str, None)),
17131729
"file_id": "file_id cannot be set directly.",
1730+
"fine_calibration": "fine_calibration cannot be set directly.",
17141731
"gantry_angle": "gantry_angle cannot be set directly.",
17151732
"helium_info": partial(
17161733
_check_types, name="helium_info", types=(dict, None), cast=HeliumInfo
@@ -2319,7 +2336,7 @@ def _write_bad_channels(fid, bads, ch_names_mapping):
23192336
ch_names_mapping = {} if ch_names_mapping is None else ch_names_mapping
23202337
bads = _rename_list(bads, ch_names_mapping)
23212338
start_block(fid, FIFF.FIFFB_MNE_BAD_CHANNELS)
2322-
write_name_list_sanitized(fid, FIFF.FIFF_MNE_CH_NAME_LIST, bads, "bads")
2339+
write_name_list_sanitized(fid, FIFF.FIFF_MNE_CH_NAME_LIST, bads, name="bads")
23232340
end_block(fid, FIFF.FIFFB_MNE_BAD_CHANNELS)
23242341

23252342

@@ -2452,7 +2469,7 @@ def read_meas_info(fid, tree, clean_bads=False, verbose=None):
24522469
line_freq = float(tag.data.item())
24532470
elif kind == FIFF.FIFF_GANTRY_ANGLE:
24542471
tag = read_tag(fid, pos)
2455-
gantry_angle = float(tag.data.item())
2472+
gantry_angle = int(tag.data.item())
24562473
elif kind in [FIFF.FIFF_MNE_CUSTOM_REF, 236]: # 236 used before v0.11
24572474
tag = read_tag(fid, pos)
24582475
custom_ref_applied = int(tag.data.item())
@@ -2587,6 +2604,10 @@ def read_meas_info(fid, tree, clean_bads=False, verbose=None):
25872604
for k in range(hpi_meas["nent"]):
25882605
kind = hpi_meas["directory"][k].kind
25892606
pos = hpi_meas["directory"][k].pos
2607+
if kind == FIFF.FIFF_BLOCK_ID:
2608+
hm["block_id"] = read_tag(fid, pos).data
2609+
if kind == FIFF.FIFF_PARENT_BLOCK_ID:
2610+
hm["parent_id"] = read_tag(fid, pos).data
25902611
if kind == FIFF.FIFF_CREATOR:
25912612
hm["creator"] = str(read_tag(fid, pos).data)
25922613
elif kind == FIFF.FIFF_SFREQ:
@@ -2711,6 +2732,9 @@ def read_meas_info(fid, tree, clean_bads=False, verbose=None):
27112732
if kind == FIFF.FIFF_HE_LEVEL_RAW:
27122733
tag = read_tag(fid, pos)
27132734
hi["he_level_raw"] = float(tag.data.item())
2735+
elif kind == FIFF.FIFF_GANTRY_ANGLE:
2736+
tag = read_tag(fid, pos)
2737+
hi["gantry_angle"] = int(tag.data.item())
27142738
elif kind == FIFF.FIFF_HELIUM_LEVEL:
27152739
tag = read_tag(fid, pos)
27162740
hi["helium_level"] = float(tag.data.item())
@@ -2755,6 +2779,14 @@ def read_meas_info(fid, tree, clean_bads=False, verbose=None):
27552779
hs["hpi_coils"] = hc
27562780
info["hpi_subsystem"] = hs
27572781

2782+
# Read cross-talk and fine cal
2783+
cross_talk = _read_mf_data(fid, tree, kind="cross_talk")
2784+
if len(cross_talk):
2785+
info["cross_talk"] = cross_talk
2786+
fine_calibration = _read_mf_data(fid, tree, kind="fine_calibration")
2787+
if len(fine_calibration):
2788+
info["fine_calibration"] = fine_calibration
2789+
27582790
# Read processing history
27592791
info["proc_history"] = _read_proc_history(fid, tree)
27602792

@@ -2970,6 +3002,10 @@ def write_meas_info(fid, info, data_type=None, reset_range=True):
29703002
# HPI Measurement
29713003
for hpi_meas in info["hpi_meas"]:
29723004
start_block(fid, FIFF.FIFFB_HPI_MEAS)
3005+
if hpi_meas.get("block_id") is not None:
3006+
write_id(fid, FIFF.FIFF_BLOCK_ID, hpi_meas["block_id"])
3007+
if hpi_meas.get("parent_id") is not None:
3008+
write_id(fid, FIFF.FIFF_PARENT_BLOCK_ID, hpi_meas["parent_id"])
29733009
if hpi_meas.get("creator") is not None:
29743010
write_string(fid, FIFF.FIFF_CREATOR, hpi_meas["creator"])
29753011
if hpi_meas.get("sfreq") is not None:
@@ -3023,13 +3059,6 @@ def write_meas_info(fid, info, data_type=None, reset_range=True):
30233059
if info["dev_ctf_t"] is not None:
30243060
write_coord_trans(fid, info["dev_ctf_t"])
30253061

3026-
# Projectors
3027-
ch_names_mapping = _make_ch_names_mapping(info["chs"])
3028-
_write_proj(fid, info["projs"], ch_names_mapping=ch_names_mapping)
3029-
3030-
# Bad channels
3031-
_write_bad_channels(fid, info["bads"], ch_names_mapping=ch_names_mapping)
3032-
30333062
# General
30343063
if info.get("experimenter") is not None:
30353064
write_string(fid, FIFF.FIFF_EXPERIMENTER, info["experimenter"])
@@ -3051,18 +3080,15 @@ def write_meas_info(fid, info, data_type=None, reset_range=True):
30513080
write_float(fid, FIFF.FIFF_HIGHPASS, info["highpass"])
30523081
if info.get("line_freq") is not None:
30533082
write_float(fid, FIFF.FIFF_LINE_FREQ, info["line_freq"])
3054-
if info.get("gantry_angle") is not None:
3055-
write_float(fid, FIFF.FIFF_GANTRY_ANGLE, info["gantry_angle"])
30563083
if data_type is not None:
30573084
write_int(fid, FIFF.FIFF_DATA_PACK, data_type)
3085+
if info.get("gantry_angle") is not None:
3086+
write_int(fid, FIFF.FIFF_GANTRY_ANGLE, info["gantry_angle"])
30583087
if info.get("custom_ref_applied"):
30593088
write_int(fid, FIFF.FIFF_MNE_CUSTOM_REF, info["custom_ref_applied"])
30603089
if info.get("xplotter_layout"):
30613090
write_string(fid, FIFF.FIFF_XPLOTTER_LAYOUT, info["xplotter_layout"])
30623091

3063-
# Channel information
3064-
_write_ch_infos(fid, info["chs"], reset_range, ch_names_mapping)
3065-
30663092
# Subject information
30673093
if info.get("subject_info") is not None:
30683094
start_block(fid, FIFF.FIFFB_SUBJECT)
@@ -3090,6 +3116,16 @@ def write_meas_info(fid, info, data_type=None, reset_range=True):
30903116
end_block(fid, FIFF.FIFFB_SUBJECT)
30913117
del si
30923118

3119+
# Projectors
3120+
ch_names_mapping = _make_ch_names_mapping(info["chs"])
3121+
_write_proj(fid, info["projs"], ch_names_mapping=ch_names_mapping)
3122+
3123+
# Channel information
3124+
_write_ch_infos(fid, info["chs"], reset_range, ch_names_mapping)
3125+
3126+
_write_mf_data(fid, info, kind="cross_talk")
3127+
_write_mf_data(fid, info, kind="fine_calibration")
3128+
30933129
if info.get("device_info") is not None:
30943130
start_block(fid, FIFF.FIFFB_DEVICE)
30953131
di = info["device_info"]
@@ -3106,6 +3142,8 @@ def write_meas_info(fid, info, data_type=None, reset_range=True):
31063142
hi = info["helium_info"]
31073143
if hi.get("he_level_raw") is not None:
31083144
write_float(fid, FIFF.FIFF_HE_LEVEL_RAW, hi["he_level_raw"])
3145+
if hi.get("gantry_angle") is not None:
3146+
write_int(fid, FIFF.FIFF_GANTRY_ANGLE, hi["gantry_angle"])
31093147
if hi.get("helium_level") is not None:
31103148
write_float(fid, FIFF.FIFF_HELIUM_LEVEL, hi["helium_level"])
31113149
if hi.get("orig_file_guid") is not None:
@@ -3131,6 +3169,9 @@ def write_meas_info(fid, info, data_type=None, reset_range=True):
31313169
end_block(fid, FIFF.FIFFB_HPI_SUBSYSTEM)
31323170
del hs
31333171

3172+
# Bad channels
3173+
_write_bad_channels(fid, info["bads"], ch_names_mapping=ch_names_mapping)
3174+
31343175
# CTF compensation info
31353176
comps = info["comps"]
31363177
if ch_names_mapping:

0 commit comments

Comments
 (0)