Repository: mne-tools/mne-python

Issue:
Get annotation descriptions from the stim name when reading snirf files
#### Describe the new feature or enhancement

When loading a SNIRF file via `mne.io.read_raw_snirf`, the descriptions for `Annotations` are currently extracted from the [keys of the SNIRF stim indexed groups](https://github.com/fNIRS/snirf/blob/master/snirf_specification.md#nirsistimj). According to the [SNIRF file format spec](https://github.com/fNIRS/snirf/blob/master/snirf_specification.md), what is currently being extracted as the Annotation description is the index of the stim rather than a descriptive string.

> group: a structure containing sub-fields (defined in the H5G object class). Arrays of groups, also known as the indexed-groups, are denoted with numbers at the end (e.g. /nirs/data1, /nirs/data2) starting with index 1. Array indices should be contiguious with no skipped values (an empty group with no sub-member is permitted).

A better field to get a description from is the [stimulus group `name` field](https://github.com/fNIRS/snirf/blob/master/snirf_specification.md#nirsistimjname) which is described as:

> This is a string describing the jth stimulus condition.

The proposed change is to get `Annotation` descriptions from the stimulus name rather than the index.

#### Describe your proposed implementation

Update [`RawSNIRF.__init__` lines 362:369](https://github.com/mne-tools/mne-python/blob/3843c36cec7310eb7b7c793e825be608f81af852/mne/io/snirf/_snirf.py#L362-L369
) to take the description field from the `name` field of the stim group rather than the stim group key.

#### Describe possible alternatives

The descriptions can be left as-is. Although the match between an `Annotation` description and a SNIRF stimulus name isn't perfect, it seems like an improvement over using the stimulus index as-is.

If the description is not changed, then tests for the MNE-NIRS SNIRF writer will need to be changed (see [Additional comments](#additional-comments) below).

#### Additional comments

This is related to https://github.com/mne-tools/mne-nirs/pull/326, where the MNE-NIRS SNIRF writer is being updated to be more consistent with the SNIRF spec.

MNE-NIRS currently has tests which use the MNE-Python SNIRF reader to make a round trip write/read test. Resolving this issue will ensure that the annotation descriptions remain the same after a round trip.



Discussion:
Hello! 👋 Thanks for opening your first issue here! ❤️ We will try to get back to you soon. 🚴🏽‍♂️

I am willing to work on this issue.

Resolving patch:
diff --git a/mne/io/snirf/_snirf.py b/mne/io/snirf/_snirf.py
--- a/mne/io/snirf/_snirf.py
+++ b/mne/io/snirf/_snirf.py
@@ -365,7 +365,8 @@ def natural_keys(text):
                     data = np.atleast_2d(np.array(
                         dat.get('/nirs/' + key + '/data')))
                     if data.size > 0:
-                        annot.append(data[:, 0], 1.0, key[4:])
+                        desc = dat.get('/nirs/' + key + '/name')[0]
+                        annot.append(data[:, 0], 1.0, desc.decode('UTF-8'))
             self.set_annotations(annot)
 
             # Reorder channels to match expected ordering in MNE


Test patch:
diff --git a/mne/io/snirf/tests/test_snirf.py b/mne/io/snirf/tests/test_snirf.py
--- a/mne/io/snirf/tests/test_snirf.py
+++ b/mne/io/snirf/tests/test_snirf.py
@@ -3,7 +3,7 @@
 #          simplified BSD-3 license
 
 import os.path as op
-from numpy.testing import assert_allclose, assert_almost_equal
+from numpy.testing import assert_allclose, assert_almost_equal, assert_equal
 import shutil
 import pytest
 
@@ -288,3 +288,12 @@ def test_snirf_standard():
     """Test standard operations."""
     _test_raw_reader(read_raw_snirf, fname=sfnirs_homer_103_wShort,
                      boundary_decimal=0)  # low fs
+
+
+@requires_testing_data
+@requires_h5py
+def test_annotation_description_from_stim_groups():
+    """Test annotation descriptions parsed from stim group names."""
+    raw = read_raw_snirf(nirx_nirsport2_103_2, preload=True)
+    expected_descriptions = ['1', '2', '6']
+    assert_equal(expected_descriptions, raw.annotations.description)