mne-tools / mne-nirs

Process Near-Infrared Spectroscopy Data in MNE
https://mne.tools/mne-nirs/
BSD 3-Clause "New" or "Revised" License
79 stars 35 forks source link

Write optical density data in SNIRF format #511

Closed florin-pop closed 1 year ago

florin-pop commented 1 year ago

Fixes https://github.com/mne-tools/mne-nirs/issues/456

Write optical density data in SNIRF format.

Example usage:

# %%
import os
import mne
import snirf

from mne.io import read_raw_nirx, read_raw_snirf
from mne_nirs.io import write_raw_snirf
from mne.preprocessing.nirs import optical_density
from numpy.testing import assert_allclose

# %%
# Import raw NIRS data from vendor
# --------------------------------
#
# First we import some example data recorded with a NIRX device.

fnirs_data_folder = mne.datasets.fnirs_motor.data_path()
fnirs_raw_dir = os.path.join(fnirs_data_folder, 'Participant-1')
raw_intensity = read_raw_nirx(fnirs_raw_dir).load_data()

# %%
# Convert to optical density

raw_od = optical_density(raw_intensity)

# %%
# Write data as SNIRF
# -------------------
#
# Now we can write this data back to disk in the SNIRF format.

write_raw_snirf(raw_od, 'test_raw.snirf')

# %%
# Read back SNIRF file
# --------------------
# 
# Next we can read back the snirf file.

snirf_intensity = read_raw_snirf('test_raw.snirf')

# %%
# Compare files
# -------------
# 
# Finally we can compare the data of the original to the SNIRF format and
# ensure that the values are the same.

assert_allclose(raw_od.get_data(), snirf_intensity.get_data())

snirf_intensity.plot(n_channels=30, duration=300, show_scrollbars=False)

# %%
# Validate SNIRF File
# -------------------
#
# To validate that a file complies with the SNIRF standard you should use the
# official SNIRF validator from the Boston University Neurophotonics Center
# called ``snirf``. Detailed instructions for this program can be found at
# https://github.com/BUNPC/pysnirf2. Below we demonstrate that the files created
# by MNE-NIRS are compliant with the specification.

result = snirf.validateSnirf('test_raw.snirf')
assert result.is_valid()
result.display()

Contributed with ❤️ by AE Studio

codecov[bot] commented 1 year ago

Codecov Report

Merging #511 (6d4f625) into main (975b44a) will increase coverage by 0.02%. The diff coverage is 97.72%.

@@            Coverage Diff             @@
##             main     #511      +/-   ##
==========================================
+ Coverage   95.44%   95.47%   +0.02%     
==========================================
  Files          69       69              
  Lines        2791     2830      +39     
  Branches      400      403       +3     
==========================================
+ Hits         2664     2702      +38     
  Misses         63       63              
- Partials       64       65       +1     
Impacted Files Coverage Δ
mne_nirs/io/snirf/tests/test_snirf.py 97.60% <96.77%> (-0.21%) :arrow_down:
mne_nirs/io/snirf/_snirf.py 95.94% <100.00%> (+0.14%) :arrow_up:
mne_nirs/statistics/tests/test_glm_type.py 100.00% <100.00%> (ø)
rob-luke commented 1 year ago

Great PR @florin-pop. I made some suggestions that I think will simplify the code and slightly boost test coverage. Could you please check if they actually work? And add a changelog entry then ping us for a review/merge. Thanks!

florin-pop commented 1 year ago

@larsoner I noticed that after you merged https://github.com/mne-tools/mne-python/pull/11665 this line started failing in CI https://github.com/mne-tools/mne-nirs/blob/975b44a1888e9b8173df7a2f718035e409d79b06/mne_nirs/statistics/tests/test_glm_type.py#L114 Should we replace it with: with pytest.raises(ValueError, match='could not be picked'): ?

florin-pop commented 1 year ago

I was thinking of doing something like:

    if check_version(mne.__version__, '1.4.0'):
        expected_pick_failure = ValueError
    else:
        expected_pick_failure = Warning
    with pytest.raises(expected_pick_failure, match='could not be picked'):
        assert len(res.copy().pick(picks=["S1_D1 hbr", "S1_D1 XXX"])) == 1

but I'm not sure check_version works as expected

>>> import mne
>>> from mne.utils import check_version
>>> print(mne.__version__)
1.4.0.dev136+g263114e32
>>> print(check_version(mne.__version__, '1.4.0', return_version=True))
(False, None)
larsoner commented 1 year ago

I would use mne.fixes._compare_version since it behaves as you'd expect/need here, and the pattern I usually use is:

if _compare_version(mne.__version__, '>=', '1.4'):
    ctx = pytest.raises(ValueError, match='...')
else:
    ctx = pytest.warns(RuntimeWarning, match='...')
with ctx:
    ...
rob-luke commented 1 year ago

Tests are green. Thanks @florin-pop @larsoner