Repository: mne-tools/mne-bids

Issue:
[BUG] Empty dig and line_freq cause errors
```
import os
import os.path as op
import numpy as np

import mne
import mne_bids
from mne.utils import _TempDir

info = mne.create_info(['ch1', 'ch2', 'ch3'], 512,
                       ['seeg'] * 3)
raw_tmp = \
    mne.io.RawArray(np.random.random((3, 1000)) * 1e-6,
                    info)
raw_tmp.info['dig'] = []  # solves issue: raw_tmp.info['dig'] = None
# solves other issue: raw_tmp.info['line_freq'] = 60
out_dir = _TempDir()
raw_tmp.save(op.join(out_dir, 'test.fif'))

raw = mne.io.read_raw_fif(op.join(out_dir, 'test.fif'))

mne_bids.write_raw_bids(raw, 'sub-1_task-mytask', op.join(out_dir, 'bids_dir'),
                            verbose=True, overwrite=True)
```

I got an uninformative index error when my `raw.info['dig']` was an empty list: 
```
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "/Users/alexrockhill/software/mne-bids/mne_bids/write.py", line 1175, in write_raw_bids
    raw, kind, overwrite, verbose)
  File "/Users/alexrockhill/software/mne-bids/mne_bids/dig.py", line 304, in _write_dig_bids
    digpoint = raw.info['dig'][0]
IndexError: list index out of range
```

and `PowerLineFrequency` in `bids_dir/sub-1/ieeg/sub-1_task-mytask_ieeg.json` is null which causes a BIDS validator error.

The dig error looks like it might be related to something @adam2392 was working on recently?
Not sure if anyone knows what happened with powerline and how it could possibly be null instead of 'n/a' but I can look into that later.


Discussion:
can you send a quick bug fix?

>


Resolving patch:
diff --git a/mne_bids/utils.py b/mne_bids/utils.py
--- a/mne_bids/utils.py
+++ b/mne_bids/utils.py
@@ -344,19 +344,16 @@ def _estimate_line_freq(raw, verbose=False):
     if sfreq < 100:
         return None
 
-    # setup picks of the data to get at least 5 channels
-    pick_dict = {"meg": True}
-    picks = list(pick_types(raw.info, exclude='bads', **pick_dict))
-    if len(picks) < 5:
-        pick_dict = {"eeg": True}
-        picks = pick_types(raw.info, exclude='bads', **pick_dict)
-    if len(picks) < 5:
-        pick_dict = {"ecog": True}
-        picks = pick_types(raw.info, exclude='bads', **pick_dict)
-    if len(picks) < 5:
-        pick_dict = {"seeg": True}
-        picks = pick_types(raw.info, exclude='bads', **pick_dict)
-    if len(picks) < 5:
+    # setup picks of the data to get at least one channel
+    for kind in ('meg', 'eeg', 'ecog', 'seeg'):
+        pick_dict = {kind: True}
+        picks = list(pick_types(raw.info, exclude='bads', **pick_dict))
+        if picks:
+            # get just five channels of data at the most to estimate on
+            picks = picks[:min([len(picks), 5])]
+            break
+
+    if not picks:
         warn("Estimation of line frequency only "
              "supports 'meg', 'eeg', 'ecog', or 'seeg'.")
         return None
@@ -365,9 +362,9 @@ def _estimate_line_freq(raw, verbose=False):
     tmin = 0
     tmax = int(min(len(raw.times), 10 * sfreq))
 
-    # get just five channels of data to estimate on
+    # get the data
     data = raw.get_data(start=tmin, stop=tmax,
-                        picks=picks, return_times=False)[0:5, :]
+                        picks=picks, return_times=False)
 
     # run a multi-taper FFT between Power Line Frequencies of interest
     psds, freqs = psd_array_welch(data, fmin=49, fmax=61,
@@ -375,7 +372,7 @@ def _estimate_line_freq(raw, verbose=False):
     usa_ind = np.where(freqs == min(freqs, key=lambda x: abs(x - 60)))[0]
     eu_ind = np.where(freqs == min(freqs, key=lambda x: abs(x - 50)))[0]
 
-    # get the average power within those frequency bands
+    # get the average and standard deviation power within those frequency bands
     usa_psd = np.mean((psds[..., usa_ind]))
     eu_psd = np.mean((psds[..., eu_ind]))
 
@@ -383,11 +380,7 @@ def _estimate_line_freq(raw, verbose=False):
         print("EU (i.e. 50 Hz) PSD is {} and "
               "USA (i.e. 60 Hz) PSD is {}".format(eu_psd, usa_psd))
 
-    if usa_psd > eu_psd:
-        line_freq = 60
-    else:
-        line_freq = 50
-    return line_freq
+    return 60 if usa_psd > eu_psd else 50
 
 
 def _scale_coord_to_meters(coord, unit):
diff --git a/mne_bids/write.py b/mne_bids/write.py
--- a/mne_bids/write.py
+++ b/mne_bids/write.py
@@ -492,9 +492,13 @@ def _sidecar_json(raw, task, manufacturer, fname, kind, overwrite=False,
     powerlinefrequency = raw.info.get('line_freq', None)
     if powerlinefrequency is None:
         powerlinefrequency = _estimate_line_freq(raw)
-        warn('No line frequency found, defaulting to {} Hz '
-             'estimated from multi-taper FFT '
-             'on 10 seconds of data.'.format(powerlinefrequency))
+        if powerlinefrequency is None:
+            powerlinefrequency = 'n/a'  # must be 'n/a' for validator
+            warn('Cannot determine line frequency')
+        else:
+            warn('No line frequency found, defaulting to {} Hz '
+                 'estimated from multi-taper FFT '
+                 'on 10 seconds of data.'.format(powerlinefrequency))
 
     if isinstance(raw, BaseRaw):
         rec_type = 'continuous'
@@ -1073,7 +1077,7 @@ def write_raw_bids(raw, bids_basename, bids_root, events_data=None,
     elif kind in ['eeg', 'ieeg']:
         # We only write electrodes.tsv and accompanying coordsystem.json
         # if we have an available DigMontage
-        if raw.info['dig'] is not None:
+        if raw.info['dig'] is not None and raw.info['dig']:
             _write_dig_bids(electrodes_fname, coordsystem_fname, data_path,
                             raw, kind, overwrite, verbose)
     else:


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
@@ -203,6 +203,28 @@ def test_get_anonymization_daysback():
             get_anonymization_daysback([raw, raw2, raw4])
 
 
+def test_create_fif(_bids_validate):
+    """Test functionality for very short raw file created from data."""
+    out_dir = _TempDir()
+    bids_root = _TempDir()
+    sfreq, n_points = 1024., int(1e6)
+    info = mne.create_info(['ch1', 'ch2', 'ch3', 'ch4', 'ch5'], sfreq,
+                           ['seeg'] * 5)
+    np.random.seed(99)
+    data = np.random.random((5, n_points)) * 1e-6
+    power_line_noise = \
+        np.sin(np.linspace(0, n_points / sfreq, n_points) * 2 * np.pi * 60)
+    raw = mne.io.RawArray(data + power_line_noise, info)
+    raw.save(op.join(out_dir, 'test-raw.fif'))
+    raw = mne.io.read_raw_fif(op.join(out_dir, 'test-raw.fif'))
+    write_raw_bids(raw, bids_basename, bids_root,
+                   verbose=False, overwrite=True)
+    raw2 = read_raw_bids(bids_basename=bids_basename, bids_root=bids_root,
+                         kind='ieeg')
+    assert raw2.info['line_freq'] == 60
+    _bids_validate(bids_root)
+
+
 @requires_version('pybv', '0.2.0')
 @pytest.mark.filterwarnings(warning_str['annotations_omitted'])
 @pytest.mark.filterwarnings(warning_str['channel_unit_changed'])