Skip to content

Commit 67eccfb

Browse files
rob-lukelarsoner
andauthored
Add support for reading SNIRF files with optical density data (#10408)
* Add support for reading SNIRF files with optical density data * Remove debug code * Add tests * Flake * Increase testing release * Increase testing release * Apply suggestions from code review * Update latest.inc * FIX: Dec Co-authored-by: Eric Larson <larson.eric.d@gmail.com>
1 parent 8f46d3e commit 67eccfb

File tree

5 files changed

+46
-7
lines changed

5 files changed

+46
-7
lines changed

Diff for: doc/changes/latest.inc

+2
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ Enhancements
124124

125125
- Add support for passing time-frequency data to :func:`mne.stats.spatio_temporal_cluster_test` and :func:`mne.stats.spatio_temporal_cluster_1samp_test` and added an example to :ref:`tut-cluster-spatiotemporal-sensor` (:gh:`10384` by `Alex Rockhill`_)
126126

127+
- Add support for reading optical density fNIRS data to :func:`mne.io.read_raw_snirf` (:gh:`10408` by `Robert Luke`_)
128+
127129
Bugs
128130
~~~~
129131

Diff for: mne/datasets/config.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@
8787
# respective repos, and make a new release of the dataset on GitHub. Then
8888
# update the checksum in the MNE_DATASETS dict below, and change version
8989
# here: ↓↓↓↓↓ ↓↓↓
90-
RELEASES = dict(testing='0.130', misc='0.23')
90+
RELEASES = dict(testing='0.132', misc='0.23')
9191
TESTING_VERSIONED = f'mne-testing-data-{RELEASES["testing"]}'
9292
MISC_VERSIONED = f'mne-misc-data-{RELEASES["misc"]}'
9393

@@ -111,7 +111,7 @@
111111
# Testing and misc are at the top as they're updated most often
112112
MNE_DATASETS['testing'] = dict(
113113
archive_name=f'{TESTING_VERSIONED}.tar.gz', # 'mne-testing-data',
114-
hash='md5:b2bf6517c3d457b70cb5519ce8ab70a7',
114+
hash='md5:2ff8bcd18053af3ee0587dce9d6ab516',
115115
url=('https://codeload.github.com/mne-tools/mne-testing-data/'
116116
f'tar.gz/{RELEASES["testing"]}'),
117117
folder_name='MNE-testing-data',

Diff for: mne/io/snirf/_snirf.py

+20-5
Original file line numberDiff line numberDiff line change
@@ -230,14 +230,27 @@ def natural_keys(text):
230230
ch_types.append('fnirs_cw_amplitude')
231231

232232
elif snirf_data_type == 99999:
233-
hb_id = _correct_shape(
233+
dt_id = _correct_shape(
234234
np.array(dat.get('nirs/data1/' + chan +
235235
'/dataTypeLabel')))[0].decode('UTF-8')
236+
237+
# Convert between SNIRF processed names and MNE type names
238+
dt_id = dt_id.lower().replace("dod", "fnirs_od")
239+
236240
ch_name = sources[src_idx - 1] + '_' + \
237-
detectors[det_idx - 1] + ' ' + \
238-
hb_id.lower()
241+
detectors[det_idx - 1]
242+
243+
if dt_id == "fnirs_od":
244+
wve_idx = int(_correct_shape(np.array(
245+
dat.get('nirs/data1/' + chan +
246+
'/wavelengthIndex')))[0])
247+
suffix = ' ' + str(fnirs_wavelengths[wve_idx - 1])
248+
else:
249+
suffix = ' ' + dt_id.lower()
250+
ch_name = ch_name + suffix
251+
239252
chnames.append(ch_name)
240-
ch_types.append(hb_id.lower())
253+
ch_types.append(dt_id)
241254

242255
# Create mne structure
243256
info = create_info(chnames,
@@ -309,7 +322,9 @@ def natural_keys(text):
309322
info['chs'][idx]['loc'][0:3] = midpoint
310323
info['chs'][idx]['coord_frame'] = coord_frame
311324

312-
if snirf_data_type in [1]:
325+
if (snirf_data_type in [1]) or \
326+
((snirf_data_type == 99999) and
327+
(ch_types[idx] == "fnirs_od")):
313328
wve_idx = int(_correct_shape(np.array(dat.get(
314329
'nirs/data1/' + chan + '/wavelengthIndex')))[0])
315330
info['chs'][idx]['loc'][9] = fnirs_wavelengths[wve_idx - 1]

Diff for: mne/io/snirf/tests/test_snirf.py

+21
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@
4646

4747
h5py = pytest.importorskip('h5py') # module-level
4848

49+
# Fieldtrip
50+
ft_od = op.join(testing_path, 'SNIRF', 'FieldTrip',
51+
'220307_opticaldensity.snirf')
52+
4953

5054
@requires_testing_data
5155
@pytest.mark.filterwarnings('ignore:.*contains 2D location.*:')
@@ -286,6 +290,23 @@ def test_snirf_nirsport2_w_positions():
286290
assert len(mon.dig) == 43
287291

288292

293+
@requires_testing_data
294+
def test_snirf_fieldtrip_od():
295+
"""Test reading FieldTrip SNIRF files with optical density data."""
296+
raw = read_raw_snirf(ft_od, preload=True)
297+
298+
# Test data import
299+
assert raw._data.shape == (72, 500)
300+
assert raw.copy().pick('fnirs')._data.shape == (72, 500)
301+
assert raw.copy().pick('fnirs_od')._data.shape == (72, 500)
302+
with pytest.raises(ValueError, match='not be interpreted as channel'):
303+
raw.copy().pick('hbo')
304+
with pytest.raises(ValueError, match='not be interpreted as channel'):
305+
raw.copy().pick('hbr')
306+
307+
assert_allclose(raw.info['sfreq'], 50)
308+
309+
289310
@requires_testing_data
290311
def test_snirf_kernel_hb():
291312
"""Test reading Kernel SNIRF files with haemoglobin data."""

Diff for: mne/tests/test_surface.py

+1
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ def test_marching_cubes(dtype, value, smooth):
261261

262262

263263
@requires_nibabel()
264+
@testing.requires_testing_data
264265
def test_get_montage_volume_labels():
265266
"""Test finding ROI labels near montage channel locations."""
266267
ch_coords = np.array([[-8.7040273, 17.99938754, 10.29604017],

0 commit comments

Comments
 (0)