Repository: mne-tools/mne-python

Issue:
Need a way to show/hide annotations in raw.plot
With the latest changes in how MNE handles discrete events (it now creates annotations), manually adding annotations in `raw.plot` can get impossible. For example:

```
import mne
from mne.datasets import eegbci

fpath = eegbci.load_data(subject=1, runs=4)
raw = mne.io.read_raw_edf(fpath[0], preload=True)
raw.plot()
```

This results in the following raw browser window:

<img width="1663" alt="screen shot 2019-02-08 at 12 32 40" src="https://user-images.githubusercontent.com/4377312/52476031-acc59300-2b9d-11e9-8a7f-3c7c09c111e7.png">

Annotations present in the EDF+ file fill up the whole signal. If I want to create new annotations (called e.g. "BAD" to mark artifact segments), I cannot create new annotations by dragging with my mouse, because this apparently requires me to start in an empty portion (i.e. a portion without any annotation).  Even though I somehow managed to add a new annotation segment once, I think we need some way to show/hide existing annotations. Maybe the current annotations dialog window can be used, which already contains a list of annotations present in the file.

Furthermore, the current position as indicated by a gray rectangle inside the navigation bar at the bottom of the window is barely visible because of the annotations. Maybe darkening the rectangle would solve this (minor) problem.


Discussion:
good point. Yes we need offer a way to toggle certain annotations.

>

I think I found a way to fix this problem plus also fix #4769: we simply need to disable the current auto-snap to annotation start/end behavior. I somehow managed to do this once, but I don't know what exactly I changed. @larsoner do you know a quick way to disable the behavior? If disabled, I can create annotations within other annotations, which means that there is no need to show/hide annotations anymore.

I think this will be a useful change, because annotations of the same type are still merged (just drag one into the other and they will get merged), but you can create different annotations within other annotations.
No idea, you'll have to dig into the code :(
OK, I've already started anyway, but was hoping that you'd have a fresh memory of this rather convoluted code. Anyway, assuming that I'll be able to disable this behavior, would you be +1 on that change?
Maybe holding some modifier key (control?) could disable it?
Yes, good idea. Maybe Shift+Click would work best. We could also do it the other way around, don't snap by default and snap with the modifier key (because I believe snapping is rarely needed).
I found the code responsible for editing existing annotations: https://github.com/mne-tools/mne-python/blob/0679e350cc20a07f3d043fec0be9a2153aa99a2b/mne/viz/utils.py#L2039

The callback is assigned here: https://github.com/mne-tools/mne-python/blob/0679e350cc20a07f3d043fec0be9a2153aa99a2b/mne/viz/utils.py#L946-L948

Now if I remove this callback assignment (or change the `_on_hover` function so that it does nothing), I get the desired behavior (which means I can create annotations over existing annotations, and if they are of the same type they get merged). I cannot edit existing annotations though (other than removing completely by right-clicking and extending by adding a new annotation from outside, but making an existing segment smaller doesn't work anymore).

I don't need the edit on hover behavior, because I either delete an annotation and create a new one, or I can still extend by merging a new annotation. But ideally, holding down the Shift key should trigger the old behavior. The only problem is that I don't know how to do that - the `_on_hover` function would have to constantly check if the Shift key is pressed or not.
> But ideally, holding down the Shift key should trigger the old behavior.

I would do it the other way, keep the current behavior by default and disable it if shift is pressed. It's less disruptive in case people are currently using the snapping.

>  The only problem is that I don't know how to do that - the _on_hover function would have to constantly check if the Shift key is pressed or not.

Something like this should do it

https://stackoverflow.com/questions/18141069/matplotlib-how-to-pick-up-shift-click-on-figure

Resolving patch:
diff --git a/mne/viz/raw.py b/mne/viz/raw.py
--- a/mne/viz/raw.py
+++ b/mne/viz/raw.py
@@ -406,7 +406,7 @@ def plot_raw(raw, events=None, duration=10.0, start=0.0, n_channels=20,
                   group_by=group_by, orig_inds=inds.copy(), decim=decim,
                   data_picks=data_picks, event_id_rev=event_id_rev,
                   noise_cov=noise_cov, use_noise_cov=noise_cov is not None,
-                  filt_bounds=filt_bounds, units=units,
+                  filt_bounds=filt_bounds, units=units, snap_annotations=False,
                   unit_scalings=unit_scalings, use_scalebars=True)
 
     if group_by in ['selection', 'position']:
diff --git a/mne/viz/utils.py b/mne/viz/utils.py
--- a/mne/viz/utils.py
+++ b/mne/viz/utils.py
@@ -389,15 +389,19 @@ def _get_help_text(params):
             text2.insert(3, 'Navigate channels up\n')
             text.insert(6, u'a : \n')
             text2.insert(6, 'Toggle annotation mode\n')
-            text.insert(7, u'b : \n')
-            text2.insert(7, 'Toggle butterfly plot on/off\n')
-            text.insert(8, u'd : \n')
-            text2.insert(8, 'Toggle remove DC on/off\n')
-            text.insert(9, u's : \n')
-            text2.insert(9, 'Toggle scale bars\n')
+
+            text.insert(7, u'p : \n')
+            text2.insert(7, 'Toggle snap to annotations on/off\n')
+
+            text.insert(8, u'b : \n')
+            text2.insert(8, 'Toggle butterfly plot on/off\n')
+            text.insert(9, u'd : \n')
+            text2.insert(9, 'Toggle remove DC on/off\n')
+            text.insert(10, u's : \n')
+            text2.insert(10, 'Toggle scale bars\n')
             if 'fig_selection' not in params:
-                text2.insert(12, 'Reduce the number of channels per view\n')
-                text2.insert(13, 'Increase the number of channels per view\n')
+                text2.insert(13, 'Reduce the number of channels per view\n')
+                text2.insert(14, 'Increase the number of channels per view\n')
             text2.append('Mark bad channel\n')
             text2.append('Vertical line at a time instant\n')
             text2.append('Mark bad channel\n')
@@ -886,6 +890,12 @@ def _plot_raw_onkey(event, params):
     elif event.key == 's':
         params['use_scalebars'] = not params['use_scalebars']
         params['plot_fun']()
+    elif event.key == 'p':
+        params['snap_annotations'] = not params['snap_annotations']
+        # remove the line if present
+        if not params['snap_annotations']:
+            _on_hover(None, params)
+        params['plot_fun']()
 
 
 def _setup_annotation_fig(params):
@@ -2066,6 +2076,9 @@ def _annotations_closed(event, params):
 
 def _on_hover(event, params):
     """Handle hover event."""
+    if not params["snap_annotations"]:  # don't snap to annotations
+        _remove_segment_line(params)
+        return
     from matplotlib.patheffects import Stroke, Normal
     if (event.button is not None or
             event.inaxes != params['ax'] or event.xdata is None):


Test patch:
diff --git a/mne/viz/tests/test_raw.py b/mne/viz/tests/test_raw.py
--- a/mne/viz/tests/test_raw.py
+++ b/mne/viz/tests/test_raw.py
@@ -79,6 +79,7 @@ def _annotation_helper(raw, events=False):
     _annotation_radio_clicked('', ann_fig.radio, data_ax.selector)
 
     # draw annotation
+    fig.canvas.key_press_event('p')  # use snap mode
     _fake_click(fig, data_ax, [1., 1.], xform='data', button=1, kind='press')
     _fake_click(fig, data_ax, [5., 1.], xform='data', button=1, kind='motion')
     _fake_click(fig, data_ax, [5., 1.], xform='data', button=1, kind='release')