Repository: mne-tools/mne-bids

Issue:
`write_raw_bids` throws an error with mne >= 0.20
#### Describe the bug
Running `write_raw_bids` on a test recording results in an error with mne 0.21.dev0 and mne 0.20.*. Same code works fine with mne 0.19.2


#### Steps to reproduce
```python
from tempfile import mkdtemp
from pathlib import Path

import mne
import mne_bids

test_elekta = Path(mne.datasets.testing.data_path(verbose=True)) / 'misc' / 'test_elekta_3ch_raw.fif'
raw = mne.io.read_raw_fif(test_elekta, verbose=False)
bids_root = mkdtemp()
mne_bids.write_raw_bids(raw, 'sub-01_ses-01_run-05', bids_root=bids_root, verbose=False)
```


#### Expected results
A file is written to a bids dataset.


#### Actual results
The code raises the following error:

```
Traceback (most recent call last):
  File "<input>", line 10, in <module>
  File "c:\users\evgen\onedrive\projects\mne-bids\mne-bids\mne_bids\write.py", line 1092, in write_raw_bids
    overwrite, verbose)
  File "c:\users\evgen\onedrive\projects\mne-bids\mne-bids\mne_bids\dig.py", line 216, in _coordsystem_json
    coords = _extract_landmarks(dig)
  File "c:\users\evgen\onedrive\projects\mne-bids\mne-bids\mne_bids\utils.py", line 577, in _extract_landmarks
    landmarks = {d['ident']: d for d in dig
TypeError: 'NoneType' object is not iterable
```


#### Additional information
`mne.sys_info()`:

```sh
Platform:      Windows-10-10.0.18362-SP0
Python:        3.7.7 (default, May  6 2020, 11:45:54) [MSC v.1916 64 bit (AMD64)]
Executable:    D:\software_windows\miniconda3\envs\mne-bids\python.exe
CPU:           Intel64 Family 6 Model 69 Stepping 1, GenuineIntel: 4 cores
Memory:        Unavailable (requires "psutil" package)
mne:           0.21.dev0
numpy:         1.18.5 {blas=C:\\projects\\numpy-wheels\\numpy\\build\\openblas_info, lapack=C:\\projects\\numpy-wheels\\numpy\\build\\openblas_lapack_info}
scipy:         1.4.1
matplotlib:    Not found
sklearn:       0.23.1
numba:         Not found
nibabel:       3.1.0
cupy:          Not found
pandas:        Not found
dipy:          Not found
mayavi:        Not found
pyvista:       Not found
vtk:           Not found
```

`mne_bids.__version__`:
```'0.5.dev0'```



Discussion:
can you share the file?

>

It is a file from the mne testing datasets:

```python
test_elekta = Path(mne.datasets.testing.data_path(verbose=True)) / 'misc' / 'test_elekta_3ch_raw.fif'
```
I can confirm this bug on Linux as well ...
I guess it's because we don't confirm that `raw.info['dig']` is actually defined (i.e., it can be `None`, and that raises an error):

https://github.com/mne-tools/mne-bids/blob/6c4d6f21d044ea7de135fe7b5ebb25b579f37022/mne_bids/dig.py#L215-L216
so, this bug would occur for all datasets, where `raw.info['dig']` is `None`:

```
In [9]: test_elekta                                                                                                                                                                                           
Out[9]: PosixPath('/home/stefanappelhoff/mne_data/MNE-testing-data/misc/test_elekta_3ch_raw.fif')

In [10]: raw = mne.io.read_raw_fif(test_elekta)                                                                                                                                                               
Opening raw data file /home/stefanappelhoff/mne_data/MNE-testing-data/misc/test_elekta_3ch_raw.fif...
Isotrak not found
    Range : 3000 ... 82999 =      3.000 ...    82.999 secs
Ready.
Current compensation grade : 0

In [11]: raw                                                                                                                                                                                                  
Out[11]: <Raw | test_elekta_3ch_raw.fif, 4 x 80000 (80.0 s), ~56 kB, data not loaded>

In [12]: raw.info['dig']                                                                                                                                                                                      

In [13]: 
```
arfff ok. Can you send a fix? We should just skip if dig is not present.

> arfff ok. Can you send a fix? We should just skip if dig is not present.

yes, but maybe @kalenkovich wants to give it a shot first? :-)

@kalenkovich this would mean:

1. adding your current issue as a test: showing that mne-bids breaks with raw files without `raw.info['dig']`
1. adjusting the `_coordsystem_json` function to skip all further steps if no `dig` is found (and perhaps writing a log message about that for the user to be aware, depending on `verbose`) (this should then fix the tests from step 1)
1. updating `whats_new.rst` with your bugfix and celebrate


> yes, but maybe @kalenkovich wants to give it a shot first? :-)

I can do that some time next week.

> 1. adding your current issue as a test: showing that mne-bids breaks with raw files without `raw.info['dig']`

Should the test break on any mne version? Put it another way, can I use the test dataset assuming that mne will produce `raw.info['dig']` that is `None` and the test will break or should I construct a new raw dataset from scratch and set `raw.info['dig']` to `None` myself so that the test breaks on any version of mne?
> Should the test break on any mne version?

ah, good point. We don't know yet why the test datasets only breaks on mne>0.19 :thinking: 

```
test_elekta = Path(mne.datasets.testing.data_path(verbose=True)) / 'misc' / 'test_elekta_3ch_raw.fif'
```

Did something change in MNE with the `dig` handling that could be the reason for this @agramfort @jasmainak ?
I have 1000 unread emails on MNE github :) not able to keep up these days. Might be good check the code.

> can I use the test dataset assuming that mne will produce raw.info['dig'] that is None and the test will break

no don't assume this

> should I construct a new raw dataset from scratch and set raw.info['dig'] to None myself so that the test breaks on any version of mne?

yes go this route. It's not a "new" dataset, just a dataset you update in the test to check the corner case

Resolving patch:
diff --git a/mne_bids/dig.py b/mne_bids/dig.py
--- a/mne_bids/dig.py
+++ b/mne_bids/dig.py
@@ -213,6 +213,8 @@ def _coordsystem_json(raw, unit, orient, coordsystem_name, fname,
 
     """
     dig = raw.info['dig']
+    if dig is None:
+        dig = []
     coords = _extract_landmarks(dig)
     hpi = {d['ident']: d for d in dig if d['kind'] == FIFF.FIFFV_POINT_HPI}
     if hpi:


Test patch:
diff --git a/mne_bids/tests/test_write.py b/mne_bids/tests/test_write.py
--- a/mne_bids/tests/test_write.py
+++ b/mne_bids/tests/test_write.py
@@ -1232,6 +1232,23 @@ def test_write_raw_pathlike():
     assert bids_root_ == str(bids_root)
 
 
+def test_write_raw_no_dig():
+    data_path = testing.data_path()
+    raw_fname = op.join(data_path, 'MEG', 'sample',
+                        'sample_audvis_trunc_raw.fif')
+    raw = mne.io.read_raw_fif(raw_fname)
+    bids_root = Path(_TempDir())
+    bids_root_ = write_raw_bids(raw=raw, bids_basename=bids_basename,
+                                bids_root=bids_root, overwrite=True)
+    assert bids_root_ == str(bids_root)
+    raw.info['dig'] = None
+    raw.save(str(bids_root / 'tmp_raw.fif'))
+    raw = mne.io.read_raw_fif(bids_root / 'tmp_raw.fif')
+    bids_root_ = write_raw_bids(raw=raw, bids_basename=bids_basename,
+                                bids_root=bids_root, overwrite=True)
+    assert bids_root_ == str(bids_root)
+
+
 @requires_nibabel()
 def test_write_anat_pathlike():
     """Test writing anatomical data with pathlib.Paths."""