Repository: mne-tools/mne-python

Issue:
read_ch_connectivity fails when channel has no neighbours defined.
I computed a neighbours structure for an EGI 256 electrodes layout using fieldtrip and tried to read that mat file with `read_ch_connectivity`.

Electrode 31 in my montage has no neighbours, so the entire function failed, at two places.

1) `_recursive_flatten` assumes the `cell` parameter passed has at least one element.

This is a possible fix (just added the if condition):
``` Python
def _recursive_flatten(cell, dtype):
    """Unpack mat files in Python."""
    if len(cell) > 0:
        while not isinstance(cell[0], dtype):
            cell = [c for d in cell for c in d]
    return cell
```

2) `_ch_neighbor_connectivity` computes the set subtraction to raise an error:
`rest = set(ch_names) - set_neighbors` with the error "Some of your neighbors are not present in the list of channel names". In this case the error message and the operation do not match. The way I see it, it is an error only if the neighbours list has at least one electrode that it is not present in `ch_names`. If this is the point, then the operation should be `rest = set_neighbors - set(ch_names)`
read_ch_connectivity fails when channel has no neighbours defined.
I computed a neighbours structure for an EGI 256 electrodes layout using fieldtrip and tried to read that mat file with `read_ch_connectivity`.

Electrode 31 in my montage has no neighbours, so the entire function failed, at two places.

1) `_recursive_flatten` assumes the `cell` parameter passed has at least one element.

This is a possible fix (just added the if condition):
``` Python
def _recursive_flatten(cell, dtype):
    """Unpack mat files in Python."""
    if len(cell) > 0:
        while not isinstance(cell[0], dtype):
            cell = [c for d in cell for c in d]
    return cell
```

2) `_ch_neighbor_connectivity` computes the set subtraction to raise an error:
`rest = set(ch_names) - set_neighbors` with the error "Some of your neighbors are not present in the list of channel names". In this case the error message and the operation do not match. The way I see it, it is an error only if the neighbours list has at least one electrode that it is not present in `ch_names`. If this is the point, then the operation should be `rest = set_neighbors - set(ch_names)`


Discussion:

Resolving patch:
diff --git a/mne/channels/channels.py b/mne/channels/channels.py
--- a/mne/channels/channels.py
+++ b/mne/channels/channels.py
@@ -908,8 +908,9 @@ def rename_channels(info, mapping):
 
 def _recursive_flatten(cell, dtype):
     """Unpack mat files in Python."""
-    while not isinstance(cell[0], dtype):
-        cell = [c for d in cell for c in d]
+    if len(cell) > 0:
+        while not isinstance(cell[0], dtype):
+            cell = [c for d in cell for c in d]
     return cell
 
 
@@ -993,7 +994,7 @@ def _ch_neighbor_connectivity(ch_names, 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
+    rest = set_neighbors - set(ch_names)
     if len(rest) > 0:
         raise ValueError('Some of your neighbors are not present in the '
                          'list of channel names')


Test patch:
diff --git a/mne/channels/tests/test_channels.py b/mne/channels/tests/test_channels.py
--- a/mne/channels/tests/test_channels.py
+++ b/mne/channels/tests/test_channels.py
@@ -12,7 +12,7 @@
 import numpy as np
 from scipy.io import savemat
 from numpy.testing import assert_array_equal
-from nose.tools import assert_raises, assert_true, assert_equal
+from nose.tools import assert_raises, assert_true, assert_equal, assert_false
 
 from mne.channels import rename_channels, read_ch_connectivity
 from mne.channels.channels import _ch_neighbor_connectivity
@@ -161,6 +161,43 @@ def test_read_ch_connectivity():
     assert_equal(len(ch_names), 102)
     assert_raises(ValueError, read_ch_connectivity, 'bananas!')
 
+    # In EGI 256, E31 sensor has no neighbour
+    a = partial(np.array)
+    nbh = np.array([[(['E31'], []),
+                     (['E1'], [[a(['E2'])],
+                               [a(['E3'])]]),
+                     (['E2'], [[a(['E1'])],
+                               [a(['E3'])]]),
+                     (['E3'], [[a(['E1'])],
+                               [a(['E2'])]])]],
+                   dtype=[('label', 'O'), ('neighblabel', 'O')])
+    mat = dict(neighbours=nbh)
+    mat_fname = op.join(tempdir, 'test_isolated_mat.mat')
+    savemat(mat_fname, mat, oned_as='row')
+    ch_connectivity, ch_names = read_ch_connectivity(mat_fname)
+    x = ch_connectivity.todense()
+    assert_equal(x.shape[0], len(ch_names))
+    assert_equal(x.shape, (4, 4))
+    assert_true(np.all(x.diagonal()))
+    assert_false(np.any(x[0, 1:]))
+    assert_false(np.any(x[1:, 0]))
+
+    # Check for neighbours consistency. If a sensor is marked as a neighbour,
+    # then it should also have its neighbours defined.
+    a = partial(np.array)
+    nbh = np.array([[(['E31'], []),
+                     (['E1'], [[a(['E8'])],
+                               [a(['E3'])]]),
+                     (['E2'], [[a(['E1'])],
+                               [a(['E3'])]]),
+                     (['E3'], [[a(['E1'])],
+                               [a(['E2'])]])]],
+                   dtype=[('label', 'O'), ('neighblabel', 'O')])
+    mat = dict(neighbours=nbh)
+    mat_fname = op.join(tempdir, 'test_error_mat.mat')
+    savemat(mat_fname, mat, oned_as='row')
+    assert_raises(ValueError, read_ch_connectivity, mat_fname)
+
 
 def test_get_set_sensor_positions():
     """Test get/set functions for sensor positions"""