Repository: mne-tools/mne-python

Issue:
BUG: Plotting Cz fails
With my current setup, plotting a sensor at 0, 0, 0 (e.g., Cz) fails because Numpy returns `False` for an array of all-zeros.

```
~/tools/mne-python/mne/viz/topomap.py in _prepare_topomap(pos, ax)
   1972     _hide_frame(ax)
   1973     if any([not pos_y.any(), not pos_x.any()]):
-> 1974         raise RuntimeError('No position information found, cannot compute '
   1975                            'geometries for topomap.')
   1976     return pos_x, pos_y

RuntimeError: No position information found, cannot compute geometries for topomap.
```

I'll try to take care of it.


Discussion:
We should probably use nan for undefined locations instead of zero, but
that's a hard change to make

In the meantime you can change the check to also look at the z coordinate
earlier in some calling function

Uh ... what do you mean? The z coordinate is also 0 ...

I'm not sure how to deal with this, but I'd like to fix it quick, because plot_compare_evokeds with Cz is something I do quite frequently, and expect other ERP people to do frequently too.
> Uh ... what do you mean? The z coordinate is also 0 ...

In Neuromag head coordinates, Cz should not have a z coordinate of zero. It implies a completely flat (like a circle instead of a sphere) head
Hm, ok - but these are x,y,z coordinates? Sorry, I don't really get what you're proposing :)
> these are x,y,z coordinates

Yes they are X/Y/Z but in what coordinate frame? In MNE we assume that EEG electrodes are in Neuromag head coordinates (defined by LPA at (-a, 0, 0), RPA at (b, 0, 0) and nasion at (0, c, 0)) at various places. Thus why an electrode at 0, 0, 0 and also on the surface of the head implies that the head is flat.

In other words:

1. you should try to get your coordinates into this coordinate frame (hopefully easy)
2. we should make the coordinate checker also check the z coordinate (also hopefully easy)
3. we should stop using (0, 0, 0) to mean "no position" and use `np.nan` (harder)
Oh, ok. So the situation is this:

- the pos is extracted by `_auto_topomap_coords`
- it is then passed through `_check_outlines`
- the check happens inside `_plot_legend`

The problem is, `_auto_topomap_coords` already only returns x and y coordinates.

So when the coords are passed to `_prepare_topomap`, z is already missing.

`_prepare_topomap` does three things: check, split pos into pos_x and pos_y, and hide axes.

So I fear the z coordinate doesn't help us.

Thankfully, the problem is probably localised: it should only actually be a problem if there is only one channel, which probably only happens inside `plot_compare_evokeds`. It could be solved by any number of ways, but all I can think of are a bit hackish.

> _prepare_topomap does three things: check, split pos into pos_x and pos_y, and hide axes.

These are all private functions. How about we move the sanity check to `_auto_topomap_coords(..., check_positions=False)` by default with `check_positions=True` in this case, meaning "check to make sure there are are valid XYZ positions and raise an error if not". That way we catch the error before any conversion takes place, and `_prepare_topomap` no longer needs to do the check.
In principle yes, though we probably want to undo the `check_positions` options once nan channels are truly nan.

However, at that stage, _prepare_topomap is seemingly only doing 2 things:
- hide the axes by calling _hide_axes
- converting `pos` into `pos[0, :], pos_y[1, :]`
So the function might look a bit unnecessary at this stage :D

Either way, I'll hopefully make a PR like that soon.

Resolving patch:
diff --git a/mne/viz/evoked.py b/mne/viz/evoked.py
--- a/mne/viz/evoked.py
+++ b/mne/viz/evoked.py
@@ -167,7 +167,7 @@ def _plot_legend(pos, colors, axis, bads, outlines, loc, size=30):
     ratio = bbox.width / bbox.height
     ax = inset_axes(axis, width=str(size / ratio) + '%',
                     height=str(size) + '%', loc=loc)
-    pos_x, pos_y = _prepare_topomap(pos, ax)
+    pos_x, pos_y = _prepare_topomap(pos, ax, check_nonzero=False)
     ax.scatter(pos_x, pos_y, color=colors, s=size * .8, marker='.', zorder=1)
     if bads:
         bads = np.array(bads)
@@ -1810,8 +1810,8 @@ def plot_compare_evokeds(evokeds, picks=list(), gfp=False, colors=None,
 
     if show_sensors:
         try:
-            pos = _auto_topomap_coords(example.info, picks,
-                                       ignore_overlap=True, to_sphere=True)
+            pos = _auto_topomap_coords(
+                example.info, picks, ignore_overlap=True, to_sphere=True)
         except ValueError:
             warn("Cannot find channel coordinates in the supplied Evokeds. "
                  "Not showing channel locations.")
diff --git a/mne/viz/topomap.py b/mne/viz/topomap.py
--- a/mne/viz/topomap.py
+++ b/mne/viz/topomap.py
@@ -1991,12 +1991,12 @@ def _onselect(eclick, erelease, tfr, pos, ch_type, itmin, itmax, ifmin, ifmax,
     plt_show(True)
 
 
-def _prepare_topomap(pos, ax):
+def _prepare_topomap(pos, ax, check_nonzero=True):
     """Prepare the topomap."""
     pos_x = pos[:, 0]
     pos_y = pos[:, 1]
     _hide_frame(ax)
-    if any([not pos_y.any(), not pos_x.any()]):
+    if check_nonzero and any([not pos_y.any(), not pos_x.any()]):
         raise RuntimeError('No position information found, cannot compute '
                            'geometries for topomap.')
     return pos_x, pos_y


Test patch:
diff --git a/mne/viz/tests/test_evoked.py b/mne/viz/tests/test_evoked.py
--- a/mne/viz/tests/test_evoked.py
+++ b/mne/viz/tests/test_evoked.py
@@ -179,6 +179,8 @@ def test_plot_evoked():
         plot_compare_evokeds(contrast, picks=[0], colors=['r', 'b'],
                              ylim=dict(mag=(1, 10)), ci=_parametric_ci,
                              truncate_yaxis='max_ticks')
+        red.info["chs"][0]["loc"][:2] = 0  # test plotting channel at zero
+        plot_compare_evokeds(red, picks=[0])
 
         # Hack to test plotting of maxfiltered data
         evoked_sss = evoked.copy()