Repository: mne-tools/mne-python

Issue:
BUG: Annotated BAD_ACQ_SKIP times are skipped/lost when loaded from fif
Hi guys, I think there is a bug with the behaviour of ``BAD_ACQ_SKIP``.

#### Describe the bug
``times`` and ``n_times`` parameters of ``mne.io.Raw`` seem to be inconsistent when loading the object that has ``BAD_ACQ_SKIP`` annotation(s) : the part that is annotated ``BAD_ACQ_SKIP`` is not included in ``times``.


#### Steps to reproduce
``` python
import mne
import numpy as np

# create a fake raw instance based on 5-sec sin and cos signals
info = mne.create_info(ch_names=["cos", "sin"], sfreq=10)
x = np.linspace(0, 5, 50)
raw = mne.io.RawArray(
    np.array([np.cos(x), np.sin(x)]),
    info
)

# add BAD_ACQ_SKIP annotation on the first 2 seconds
raw.annotations.append(onset=0, duration=2, description="BAD_ACQ_SKIP")
# save to fif
raw.save("raw_ieeg.fif")

# load the fif
raw_loaded = mne.io.read_raw_fif("raw_ieeg.fif")

# compare
print(raw.times)
print(raw_loaded.times)
```
compare output:
``` python
[0.  0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.  1.1 1.2 1.3 1.4 1.5 1.6 1.7
 1.8 1.9 2.  2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 3.  3.1 3.2 3.3 3.4 3.5
 3.6 3.7 3.8 3.9 4.  4.1 4.2 4.3 4.4 4.5 4.6 4.7 4.8 4.9]
[0.  0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.  1.1 1.2 1.3 1.4 1.5 1.6 1.7
 1.8 1.9 2.  2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9]
```

#### Expected results
I would except ``times`` to take also the ``BAD_ACQ_SKIP`` into account, so in my example ``raw_loaded.times`` should be the same as ``raw.times``.

What do you think ?




Discussion:
Hello! 👋 Thanks for opening your first issue here! ❤️ We will try to get back to you soon. 🚴🏽‍♂️

Can you check that this only happens when it's the first N seconds? For example of you annotate starting at 1 sec rather than 0 sec, there is no issue?

If so, it's because skipped data at the very beginning gets rolled into the raw.first_time offset value. One fix might be too add a initial_skip='first_samp' (default, current behavior) and make a second option 'annotate', which would behave like you expect here.

Another more trivial option is just to document that an initial acq skip gets rolled into raw.first_time, maybe it's already enough?
You're right, I thought it was applying on all ``BAD_ACQ_SKIP`` but it is only when it's the first N seconds. I think adding the proper doc would be enough as one can easily find a workaround if necessary.
Thanks for your reply !
@etiennedemontalivet would you be up for making a PR to add this to the `Annotations` docstring in the `Notes` section? Would you have seen it there? Or maybe we need a new short section in the Annotations tutorial https://mne.tools/dev/auto_tutorials/raw/30_annotate_raw.html / https://github.com/mne-tools/mne-python/blob/main/tutorials/raw/30_annotate_raw.py ?
I can do that yes. Do you want me to close this issue and open a new one labeled as doc enhancement ?
I think the ``Annotations`` docstring in the ``Notes`` is the right place, I would have seen it there.
Usually we leave issues open until the PR that fixes them is merged. And if you put this line somewhere in your PR description on GitHub:
```

Closes #10037.
```
By GitHub magic, us merging your PR automatically closes this issue.
@larsoner what do you think about:
- adding in ``Notes`` section of ``mne.io.read_raw_fif`` the specific behaviour of the ``BAD_ACQ_SKIP`` annotation (first samples that are annotated bad_acq_skip are skipped). I think this is the right place because it only happens when loading a ``mne.io.Raw`` from fif file (right?)
- adding in ``Notes`` section of ``mne.Annotations`` the different behaviours of specific annotations (such as ``BAD_ACQ_SKIP``)

I would say this issue is related with the first point.
Yeah I think adding in both places makes sense. In the Annotations doc we can even cross-link to the read_raw_fif doc easily enough so the BAD_ACQ_SKIP details can be shorter there and longer in `read_raw_fif` for example

Resolving patch:
diff --git a/mne/annotations.py b/mne/annotations.py
--- a/mne/annotations.py
+++ b/mne/annotations.py
@@ -230,6 +230,12 @@ class Annotations(object):
              n                        |      |
              e                        +------+
          orig_time                 onset[0]'
+
+    **Specific annotation**
+
+    ``BAD_ACQ_SKIP`` annotation leads to specific reading/writing file
+    behaviours. See :meth:`mne.io.read_raw_fif` and
+    :meth:`Raw.save() <mne.io.Raw.save>` notes for details.
     """  # noqa: E501
 
     def __init__(self, onset, duration, description,
diff --git a/mne/io/base.py b/mne/io/base.py
--- a/mne/io/base.py
+++ b/mne/io/base.py
@@ -1416,6 +1416,9 @@ def save(self, fname, picks=None, tmin=0, tmax=None, buffer_size_sec=None,
         work properly on a saved concatenated file (e.g., probably some
         or all forms of SSS). It is recommended not to concatenate and
         then save raw files for this reason.
+
+        Samples annotated ``BAD_ACQ_SKIP`` are not stored in order to optimize
+        memory. Whatever values, they will be loaded as 0s when reading file.
         """
         endings = ('raw.fif', 'raw_sss.fif', 'raw_tsss.fif',
                    '_meg.fif', '_eeg.fif', '_ieeg.fif')
diff --git a/mne/io/fiff/raw.py b/mne/io/fiff/raw.py
--- a/mne/io/fiff/raw.py
+++ b/mne/io/fiff/raw.py
@@ -469,6 +469,11 @@ def read_raw_fif(fname, allow_maxshield=False, preload=False,
     Notes
     -----
     .. versionadded:: 0.9.0
+
+    When reading a FIF file, note that the first N seconds annotated
+    ``BAD_ACQ_SKIP`` are **skipped**. They are removed from ``raw.times`` and
+    ``raw.n_times`` parameters but ``raw.first_samp`` and ``raw.first_time``
+    are updated accordingly.
     """
     return Raw(fname=fname, allow_maxshield=allow_maxshield,
                preload=preload, verbose=verbose,


Test patch: