Repository: mne-tools/mne-python

Issue:
ENH: add function to return sensor connectivity based on sensor pos for clustering in sensor space
Spatio temporal clustering in sensor space ... 



Discussion:

Resolving patch:
diff --git a/mne/io/channels.py b/mne/io/channels.py
--- a/mne/io/channels.py
+++ b/mne/io/channels.py
@@ -5,6 +5,8 @@
 # License: BSD (3-clause)
 
 import numpy as np
+from scipy.io import loadmat
+from scipy import sparse
 
 from ..externals.six import string_types
 
@@ -284,3 +286,82 @@ def rename_channels(info, mapping):
     # reference magic, please don't change (with the local binding
     # it doesn't work)
     info['ch_names'] = [c['ch_name'] for c in chs]
+
+
+def _recursive_flatten(cell, dtype):
+    """Helper to unpack mat files in Python"""
+    while not isinstance(cell[0], dtype):
+        cell = [c for d in cell for c in d]
+    return cell
+
+
+def read_ch_connectivity(fname, picks=None):
+    """Parse FieldTrip neighbors .mat file
+
+    Parameters
+    ----------
+    fname : str
+        The file name.
+    picks : array-like of int, shape (n channels)
+        The indices of the channels to include. Must match the template.
+        Defaults to None.
+
+    Returns
+    -------
+    ch_connectivity : scipy.sparse matrix
+        The connectivity matrix.
+    """
+    nb = loadmat(fname)['neighbours']
+    ch_names = _recursive_flatten(nb['label'], string_types)
+    neighbors = [_recursive_flatten(c, string_types) for c in
+                 nb['neighblabel'].flatten()]
+    assert len(ch_names) == len(neighbors)
+    if picks is not None:
+        if max(picks) >= len(ch_names):
+            raise ValueError('The picks must be compatible with '
+                             'channels. Found a pick ({}) which exceeds '
+                             'the channel range ({})'
+                             .format(max(picks), len(ch_names)))
+    connectivity = ch_neighbor_connectivity(ch_names, neighbors)
+    if picks is not None:
+        # picking before constructing matrix is buggy
+        connectivity = connectivity[np.ix_(picks, picks)]
+    return connectivity
+
+
+def ch_neighbor_connectivity(ch_names, neighbors):
+    """Compute sensor connectivity matrix
+
+    Parameters
+    ----------
+    ch_names : list of str
+        The channel names.
+    neighbors : list of list
+        A list of list of channel names. The neighbors to
+        which the channels in ch_names are connected with.
+        Must be of the same length as ch_names.
+    Returns
+    -------
+    ch_connectivity : scipy.sparse matrix
+        The connectivity matrix.
+    """
+    if len(ch_names) != len(neighbors):
+        raise ValueError('`ch_names` and `neighbors` must '
+                         'have the same length')
+    set_neighbors = set([c for d in neighbors for c in d])
+    rest = set(ch_names) - set_neighbors
+    if len(rest) > 0:
+        raise ValueError('Some of your neighbors are not present in the '
+                         'list of channel names')
+
+    for neigh in neighbors:
+        if (not isinstance(neigh, list) and
+           not all(isinstance(c, string_types) for c in neigh)):
+            raise ValueError('`neighbors` must be a list of lists of str')
+
+    ch_connectivity = np.eye(len(ch_names), dtype=bool)
+    for ii, neigbs in enumerate(neighbors):
+        ch_connectivity[ii, [ch_names.index(i) for i in neigbs]] = True
+
+    ch_connectivity = sparse.csr_matrix(ch_connectivity)
+    return ch_connectivity


Test patch:
diff --git a/mne/io/tests/test_channels.py b/mne/io/tests/test_channels.py
--- a/mne/io/tests/test_channels.py
+++ b/mne/io/tests/test_channels.py
@@ -1,4 +1,5 @@
 # Author: Daniel G Wakeman <dwakeman@nmr.mgh.harvard.edu>
+#         Denis A. Engemann <denis.engemann@gmail.com>
 #
 # License: BSD (3-clause)
 
@@ -6,11 +7,19 @@
 
 from copy import deepcopy
 
-from nose.tools import assert_raises, assert_true
+import numpy as np
+from nose.tools import assert_raises, assert_true, assert_equal
 
 from mne import io
-from mne.io.channels import rename_channels
+from mne.io.channels import (rename_channels, read_ch_connectivity,
+                             ch_neighbor_connectivity)
 from mne.constants import FIFF
+from mne.fixes import partial
+from mne.utils import _TempDir
+from scipy.io import savemat
+
+tempdir = _TempDir()
+
 
 base_dir = op.join(op.dirname(__file__), 'data')
 raw_fname = op.join(base_dir, 'test_raw.fif')
@@ -64,3 +73,37 @@ def test_rename_channels():
     assert_true(info2['ch_names'][373] == 'EOG 059')
     assert_true('EOG 059' in info2['bads'])
     assert_true(info2['chs'][373]['kind'] is FIFF.FIFFV_EOG_CH)
+
+
+def test_read_ch_connectivity():
+    "Test reading channel connectivity templates"
+    a = partial(np.array, dtype='<U7')
+    # no pep8
+    nbh = np.array([[(['MEG0111'], [[a(['MEG0131'])]]),
+                     (['MEG0121'], [[a(['MEG0111'])],
+                                    [a(['MEG0131'])]]),
+                     (['MEG0131'], [[a(['MEG0111'])],
+                                    [a(['MEG0121'])]])]],
+                   dtype=[('label', 'O'), ('neighblabel', 'O')])
+    mat = dict(neighbours=nbh)
+    mat_fname = op.join(tempdir, 'test_mat.mat')
+    savemat(mat_fname, mat)
+
+    ch_connectivity = read_ch_connectivity(mat_fname)
+    x = ch_connectivity
+    assert_equal(x.shape, (3, 3))
+    assert_true(x[0, 1] == False)
+    assert_true(x[0, 2] == True)
+    assert_true(np.all(x.diagonal() == True))
+    assert_raises(ValueError, read_ch_connectivity, mat_fname, [0, 3])
+    ch_connectivity = read_ch_connectivity(mat_fname, picks=[0, 2])
+    assert_equal(ch_connectivity.shape[0], 2)
+
+    ch_names = ['EEG01', 'EEG02', 'EEG03']
+    neighbors = [['EEG02'], ['EEG04'], ['EEG02']]
+    assert_raises(ValueError, ch_neighbor_connectivity, ch_names, neighbors)
+    neighbors = [['EEG02'], ['EEG01', 'EEG03'], ['EEG 02']]
+    assert_raises(ValueError, ch_neighbor_connectivity, ch_names[:2],
+                  neighbors)
+    neighbors = [['EEG02'], 'EEG01', ['EEG 02']]
+    assert_raises(ValueError, ch_neighbor_connectivity, ch_names, neighbors)