Repository: mne-tools/mne-python

Issue:
AttributeError during reading the raw MEG fif file using read_raw_fif
Hello!

I tried to read my raw MEG .fif file using the `mne.io.read_raw_fif` function and the next error apperas:

```
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "Y:\LevchenkoE\ISC\Experiment3_videos_MEG\MCCA-MEG\venv\lib\site-packages\mne\io\fiff\raw.py", line 453, in read_raw_fif preload=preload, verbose=verbose)

  File "<decorator-gen-169>", line 21, in __init__

  File "Y:\LevchenkoE\ISC\Experiment3_videos_MEG\MCCA-MEG\venv\lib\site-packages\mne\io\fiff\raw.py", line 117, in __init__ self._preload_data(preload)
  File "<decorator-gen-150>", line 21, in _preload_data

  File "Y:\LevchenkoE\ISC\Experiment3_videos_MEG\MCCA-MEG\venv\lib\site-packages\mne\io\base.py", line 488, in _preload_data self._data = self._read_segment(data_buffer=data_buffer)

  File "<decorator-gen-148>", line 21, in _read_segment

  File "Y:\LevchenkoE\ISC\Experiment3_videos_MEG\MCCA-MEG\venv\lib\site-packages\mne\io\base.py", line 387, in _read_segment cals, mult)

  File "Y:\LevchenkoE\ISC\Experiment3_videos_MEG\MCCA-MEG\venv\lib\site-packages\mne\io\fiff\raw.py", line 355, in _read_segment_file one.shape = (picksamp, self.info['nchan'])

AttributeError: 'NoneType' object has no attribute 'shape'
```

I tried to read the same file with BrainStrom and it is reading successfully, but without events. At the same time if I open the raw file without `preload=True` parameter I can see the events:

```
[{'channels': array([309]), 
'list': array([358353,      0,      1, 358358,      
1,      0, 388651,      0,
2, 388657,      2,      0])}]
```

For me this is very strange because I can read other files of the same subject. The parameters of recording were exactly the same. The record is splitted to several files just because our MEG system cannot record more than 2Gb in one file and that is why, during the break, we need to push stop (and save the file) and start recording to a new file, but the acquistion parameters weren't changed at all.

I have attached the [link](https://drive.google.com/drive/folders/1pevV17gegHZHjvVxKpkO6CjdH24gzAJD?usp=sharing) to files: one readable (good) and one with an error (bad).

I am using:
mne '0.20.0'
python '3.7.3'

Do you have any ideas?



Discussion:
Was this file produced by the standard acquisition system? The FIF `tag` header in one of the raw buffers says that there is no data:
```
<Tag | kind 0 - type 0 - size 0 - next 0 - pos 0 - data None>
```
This is despite the entry in the FIF directory tree indicating that it should be non-zero:
```
<Tag | kind 300 - type 3 - size 1292000 - next 842462849 - pos 842462849 - data None>
```
We can turn this into a series of zeros, but to properly mark it as an acquisition skip will be a bit harder...

It would be better to fix this at the acquisition end if possible instead.

Resolving patch:
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
@@ -334,6 +334,7 @@ def _dtype(self):
 
     def _read_segment_file(self, data, idx, fi, start, stop, cals, mult):
         """Read a segment of data from a file."""
+        n_bad = 0
         with _fiff_get_fid(self._filenames[fi]) as fid:
             bounds = self._raw_extras[fi]['bounds']
             ents = self._raw_extras[fi]['ent']
@@ -353,10 +354,17 @@ def _read_segment_file(self, data, idx, fi, start, stop, cals, mult):
                     one = read_tag(fid, ent.pos,
                                    shape=(nsamp, nchan),
                                    rlims=(first_pick, last_pick)).data
-                    one.shape = (picksamp, nchan)
-                    _mult_cal_one(data[:, offset:(offset + picksamp)],
-                                  one.T, idx, cals, mult)
+                    try:
+                        one.shape = (picksamp, nchan)
+                    except AttributeError:  # one is None
+                        n_bad += picksamp
+                    else:
+                        _mult_cal_one(data[:, offset:(offset + picksamp)],
+                                      one.T, idx, cals, mult)
                 offset += picksamp
+            if n_bad:
+                warn(f'FIF raw buffer could not be read, acquisition error '
+                     f'likely: {n_bad} samples set to zero')
             assert offset == stop - start
 
     def fix_mag_coil_types(self):
diff --git a/mne/io/tag.py b/mne/io/tag.py
--- a/mne/io/tag.py
+++ b/mne/io/tag.py
@@ -50,7 +50,7 @@ def __repr__(self):  # noqa: D105
         out += ">"
         return out
 
-    def __cmp__(self, tag):  # noqa: D105
+    def __eq__(self, tag):  # noqa: D105
         return int(self.kind == tag.kind and
                    self.type == tag.type and
                    self.size == tag.size and
@@ -447,11 +447,11 @@ def read_tag(fid, pos=None, shape=None, rlims=None):
             tag.data = _read_matrix(fid, tag, shape, rlims, matrix_coding)
         else:
             #   All other data types
-            fun = _call_dict.get(tag.type)
-            if fun is not None:
-                tag.data = fun(fid, tag, shape, rlims)
-            else:
+            try:
+                fun = _call_dict[tag.type]
+            except KeyError:
                 raise Exception('Unimplemented tag data type %s' % tag.type)
+            tag.data = fun(fid, tag, shape, rlims)
     if tag.next != FIFF.FIFFV_NEXT_SEQ:
         # f.seek(tag.next,0)
         fid.seek(tag.next, 1)  # XXX : fix? pb when tag.next < 0


Test patch:
diff --git a/mne/datasets/testing/_testing.py b/mne/datasets/testing/_testing.py
--- a/mne/datasets/testing/_testing.py
+++ b/mne/datasets/testing/_testing.py
@@ -52,9 +52,12 @@ def requires_testing_data(func):
                               reason='Requires testing dataset')(func)
 
 
-def _pytest_param():
+def _pytest_param(*args, **kwargs):
+    if len(args) == len(kwargs) == 0:
+        args = ('testing_data',)
     import pytest
     # turn anything that uses testing data into an auto-skipper by
-    # setting params=[testing._pytest_param()]
-    return pytest.param('testing_data', marks=pytest.mark.skipif(
+    # setting params=[testing._pytest_param()], or by parametrizing functions
+    # with testing._pytest_param(whatever)
+    return pytest.param(*args, **kwargs, marks=pytest.mark.skipif(
         _skip_testing_data(), reason='Requires testing dataset'))
diff --git a/mne/io/fiff/tests/test_raw_fiff.py b/mne/io/fiff/tests/test_raw_fiff.py
--- a/mne/io/fiff/tests/test_raw_fiff.py
+++ b/mne/io/fiff/tests/test_raw_fiff.py
@@ -21,6 +21,7 @@
 from mne.filter import filter_data
 from mne.io.constants import FIFF
 from mne.io import RawArray, concatenate_raws, read_raw_fif
+from mne.io.tag import _read_tag_header
 from mne.io.tests.test_raw import _test_concat, _test_raw_reader
 from mne import (concatenate_events, find_events, equalize_channels,
                  compute_proj_raw, pick_types, pick_channels, create_info,
@@ -1588,4 +1589,22 @@ def test_str_like():
     assert_allclose(raw_path._data, raw_str._data)
 
 
+@pytest.mark.parametrize('fname', [
+    test_fif_fname,
+    testing._pytest_param(fif_fname),
+    testing._pytest_param(ms_fname),
+])
+def test_bad_acq(fname):
+    """Test handling of acquisition errors."""
+    # see gh-7844
+    raw = read_raw_fif(fname, allow_maxshield='yes').load_data()
+    with open(fname, 'rb') as fid:
+        for ent in raw._raw_extras[0]['ent']:
+            fid.seek(ent.pos, 0)
+            tag = _read_tag_header(fid)
+            # hack these, others (kind, type) should be correct
+            tag.pos, tag.next = ent.pos, ent.next
+            assert tag == ent
+
+
 run_tests_if_main()