Repository: mne-tools/mne-bids

Issue:
Fixing channels tsv
So as it stands the channels.tsv file doesn't quite produce the expected results.
As a comment in function that produces it indicates, the `MEGGRAD` value isn't even in the bids specification, with `MEGGRADAXIAL` and `MEGGRADPLANAR` being required to specify the actual gradiometer type.
Further, as I mentioned in the issue I raised for mne-python (https://github.com/mne-tools/mne-python/issues/5311), the units of the KIT gradiometer channels are set to be T, which mne then uses to decide that it is a magnetometer channel, even though it isn't.
My initial thought of how to fix this would be to change the `map_chs` dictionary to be one that maps the `coil_type` value in the channel (eg. `'coil_type': 6001` in the KIT data I have) to the channel type.
Ie. something like
`map_chs.update(meggradaxial=(FIFF.FIFFV_COIL_KIT_GRAD), ... etc)`
The issue with this may be that it may be a bit cumbersome, and also we will need to know exactly what type of gradiometer etc all the various manufacturers use.
Again, I may well be overlooking some other value that is more suitable for correctly identifying the channel type.


Discussion:
perhaps we could start with the ones that are present in the [mne constants.py file](https://github.com/mne-tools/mne-python/blob/master/mne/io/constants.py#L786-L844)? you could make a list of these. And if it's not a recognized type, then we may have to default to something that BIDS does not complain about.

want to give it a shot @monkeyman192 ?
Yeah, there are quite a few, and I realised that there are some missing (eg [the KIT channel types](https://github.com/mne-tools/mne-python/blob/master/mne/io/constants.py#L820-L823) have only two values here, where there are far more in the [KIT constants file](https://github.com/mne-tools/mne-python/blob/master/mne/io/kit/constants.py#L52-L59))
While these may be the more commonly used ones, one of the MEG systems we have recently had the axial gradiometer reference channels modified so that they are realised as such in the con file, and as it stands, these will not be picked up correctly by mne I don't think (modification was only last week, so I haven't had a chance to test it yet...)
I will give it a go when I get to work on Monday.
okay sounds great, thanks!

Resolving patch:
diff --git a/mne_bids/mne_bids.py b/mne_bids/mne_bids.py
--- a/mne_bids/mne_bids.py
+++ b/mne_bids/mne_bids.py
@@ -4,6 +4,7 @@
 #          Teon Brooks <teon.brooks@gmail.com>
 #          Chris Holdgraf <choldgraf@berkeley.edu>
 #          Stefan Appelhoff <stefan.appelhoff@mailbox.org>
+#          Matt Sanderson <matt.sanderson@mq.edu.au>
 #
 # License: BSD (3-clause)
 
@@ -22,6 +23,7 @@
 from datetime import datetime
 from warnings import warn
 
+from .pick import coil_type
 from .utils import (make_bids_filename, make_bids_folders,
                     make_dataset_description, _write_json,
                     _read_events, _mkdir_p, age_on_date)
@@ -57,18 +59,25 @@ def _channels_tsv(raw, fname, verbose):
 
     """
     map_chs = defaultdict(lambda: 'OTHER')
-    map_chs.update(grad='MEGGRAD', mag='MEGMAG', stim='TRIG', eeg='EEG',
-                   ecog='ECOG', seeg='SEEG', eog='EOG', ecg='ECG', misc='MISC',
-                   resp='RESPONSE', ref_meg='REFMEG')
+    map_chs.update(meggradaxial='MEGGRADAXIAL',
+                   megrefgradaxial='MEGREFGRADAXIAL',
+                   meggradplanar='MEGGRADPLANAR',
+                   megmag='MEGMAG', megrefmag='MEGREFMAG',
+                   eeg='EEG', misc='MISC', stim='TRIG',
+                   ecog='ECOG', seeg='SEEG', eog='EOG', ecg='ECG')
     map_desc = defaultdict(lambda: 'Other type of channel')
-    map_desc.update(grad='Gradiometer', mag='Magnetometer',
-                    stim='Trigger',
-                    eeg='ElectroEncephaloGram',
+    map_desc.update(meggradaxial='Axial Gradiometer',
+                    megrefgradaxial='Axial Gradiometer Reference',
+                    meggradplanar='Planar Gradiometer',
+                    megmag='Magnetometer',
+                    megrefmag='Magnetometer Reference',
+                    stim='Trigger', eeg='ElectroEncephaloGram',
                     ecog='Electrocorticography',
                     seeg='StereoEEG',
                     ecg='ElectroCardioGram',
-                    eog='ElectrOculoGram', misc='Miscellaneous',
-                    ref_meg='Reference channel')
+                    eog='ElectrOculoGram',
+                    misc='Miscellaneous')
+    get_specific = ('mag', 'ref_meg', 'grad')
 
     # get the manufacturer from the file in the Raw object
     manufacturer = None
@@ -83,8 +92,11 @@ def _channels_tsv(raw, fname, verbose):
     status, ch_type, description = list(), list(), list()
     for idx, ch in enumerate(raw.info['ch_names']):
         status.append('bad' if ch in raw.info['bads'] else 'good')
-        ch_type.append(map_chs[channel_type(raw.info, idx)])
-        description.append(map_desc[channel_type(raw.info, idx)])
+        _channel_type = channel_type(raw.info, idx)
+        if _channel_type in get_specific:
+            _channel_type = coil_type(raw.info, idx)
+        ch_type.append(map_chs[_channel_type])
+        description.append(map_desc[_channel_type])
     low_cutoff, high_cutoff = (raw.info['highpass'], raw.info['lowpass'])
     units = [_unit2human.get(ch_i['unit'], 'n/a') for ch_i in raw.info['chs']]
     units = [u if u not in ['NA'] else 'n/a' for u in units]
diff --git a/mne_bids/pick.py b/mne_bids/pick.py
new file mode 100644
--- /dev/null
+++ b/mne_bids/pick.py
@@ -0,0 +1,70 @@
+# Authors: Matt Sanderson <matt.sanderson@mq.edu.au>
+#
+# License: BSD (3-clause)
+
+from mne.io.constants import FIFF
+
+
+def get_coil_types():
+    """Return all known coil types.
+
+    Returns
+    -------
+    coil_types : dict
+        The keys contain the channel types, and the values contain the
+        corresponding values in the info['chs'][idx]['kind']
+    """
+
+    return dict(meggradaxial=(FIFF.FIFFV_COIL_KIT_GRAD,
+                              FIFF.FIFFV_COIL_CTF_GRAD,
+                              FIFF.FIFFV_COIL_AXIAL_GRAD_5CM,
+                              FIFF.FIFFV_COIL_BABY_GRAD),
+                megrefgradaxial=(FIFF.FIFFV_COIL_CTF_REF_GRAD,
+                                 FIFF.FIFFV_COIL_CTF_OFFDIAG_REF_GRAD,
+                                 FIFF.FIFFV_COIL_MAGNES_REF_GRAD,
+                                 FIFF.FIFFV_COIL_MAGNES_OFFDIAG_REF_GRAD),
+                meggradplanar=(FIFF.FIFFV_COIL_VV_PLANAR_T1,
+                               FIFF.FIFFV_COIL_VV_PLANAR_T2,
+                               FIFF.FIFFV_COIL_VV_PLANAR_T3),
+                megmag=(FIFF.FIFFV_COIL_POINT_MAGNETOMETER,
+                        FIFF.FIFFV_COIL_POINT_MAGNETOMETER_X,
+                        FIFF.FIFFV_COIL_POINT_MAGNETOMETER_Y,
+                        FIFF.FIFFV_COIL_VV_MAG_W,
+                        FIFF.FIFFV_COIL_VV_MAG_T1,
+                        FIFF.FIFFV_COIL_VV_MAG_T2,
+                        FIFF.FIFFV_COIL_VV_MAG_T3,
+                        FIFF.FIFFV_COIL_MAGNES_MAG,
+                        FIFF.FIFFV_COIL_BABY_MAG),
+                megrefmag=(FIFF.FIFFV_COIL_KIT_REF_MAG,
+                           FIFF.FIFFV_COIL_CTF_REF_MAG,
+                           FIFF.FIFFV_COIL_MAGNES_REF_MAG,
+                           FIFF.FIFFV_COIL_BABY_REF_MAG,
+                           FIFF.FIFFV_COIL_BABY_REF_MAG2,
+                           FIFF.FIFFV_COIL_ARTEMIS123_REF_MAG,
+                           FIFF.FIFFV_COIL_MAGNES_REF_MAG),
+                eeg=(FIFF.FIFFV_COIL_EEG,),
+                misc=(FIFF.FIFFV_COIL_NONE,))
+
+
+def coil_type(info, idx):
+    """Get coil type.
+
+    Parameters
+    ----------
+    info : dict
+        Measurement info
+    idx : int
+        Index of channel
+
+    Returns
+    -------
+    type : 'meggradaxial' | 'megrefgradaxial' | 'meggradplanar'
+           'megmag' | 'megrefmag' | 'eeg' | 'misc'
+        Type of coil
+    """
+    ch = info['chs'][idx]
+    for key, values in get_coil_types().items():
+        if ch['coil_type'] in values:
+            return key
+    raise ValueError('Unknown coil of type {0} '
+                     'for channel {1}'.format(ch['coil_type'], ch["ch_name"]))


Test patch:
diff --git a/mne_bids/tests/test_pick.py b/mne_bids/tests/test_pick.py
new file mode 100644
--- /dev/null
+++ b/mne_bids/tests/test_pick.py
@@ -0,0 +1,23 @@
+"""Test for the coil type picking function"""
+
+import os.path as op
+import pytest
+
+from mne.datasets import testing
+from mne.io import read_raw_fif
+from mne_bids.pick import coil_type
+
+
+def test_coil_type():
+    """Test the correct coil type is retrieved."""
+    data_path = testing.data_path()
+    raw_fname = op.join(data_path, 'MEG', 'sample',
+                        'sample_audvis_trunc_raw.fif')
+    raw = read_raw_fif(raw_fname)
+    assert coil_type(raw.info, 0) == 'meggradplanar'
+    assert coil_type(raw.info, 2) == 'megmag'
+    assert coil_type(raw.info, 306) == 'misc'
+    assert coil_type(raw.info, 315) == 'eeg'
+    raw.info['chs'][0]['coil_type'] = 1234
+    with pytest.raises(ValueError):
+        coil_type(raw.info, 0)