Repository: mne-tools/mne-python

Issue:
`mne make_scalp_surfaces` : Surface outer skin  has topological defects
Unfortunately, I get errors when trying to run `mne make_scalp_surfaces`, see below:


# Versions
- mne-python master at `d75bce15717ee4cbf2abbdb5c6e5148959000a91` (23rd of July 2019)
- freesurfer `freesurfer-Linux-centos6_x86_64-stable-pub-v6.0.0-2beb96c`

# output of `mne.sys_info()` 
```Text
qt.qpa.screen: QXcbConnection: Could not connect to display 
Could not connect to any X display.
```
:smile: 

I run this on a Linux server via ssh ... but I also get this issue on my own linux machine (Ubuntu 18.04). Perhaps we can add a `try-except` to `mne.sys_info` for these cases.

# How to reproduce

The following commands are run from bash:
```bash
# make sure we have the sample dataset
python -c "import mne; mne.datasets.sample.data_path(verbose=True)"

# make a copy of the freesurfer dir of sample data
# assuming we have our SUBJECTS_DIR in ~/subjects
cp -r ~/mne_data/MNE-sample-data/subjects/sample ~/subjects 

# setup freesurfer
export FREESURFER_HOME=/usr/local/freesurfer
source $FREESURFER_HOME/SetUpFreeSurfer.sh
export SUBJECTS_DIR=~/subjects

# run mne binding for freesurfer
mne make_scalp_surfaces -s sample --overwrite
```




# Error message
```Text
1. Creating a dense scalp tessellation with mkheadsurf...
2. Creating /home/appelhoff/subjects/sample/bem/sample-head-dense.fif ...
outer skin  CM is  -0.03   8.83 -23.48 mm
Surfaces passed the basic topology checks.
3. Creating medium tessellation...
3.1 Decimating the dense tessellation...
3.2 Creating /home/appelhoff/subjects/sample/bem/sample-head-medium.fif
Traceback (most recent call last):
  File "/home/appelhoff/mne-python/mne/commands/mne_make_scalp_surfaces.py", line 136, in <module>
    run()
  File "/home/appelhoff/mne-python/mne/commands/mne_make_scalp_surfaces.py", line 63, in run
    options.no_decimate, options.verbose)
  File "</home/appelhoff/mne-python/mne/externals/decorator.py:decorator-gen-425>", line 2, in _run
  File "/home/appelhoff/mne-python/mne/utils/_logging.py", line 89, in wrapper
    return function(*args, **kwargs)
  File "/home/appelhoff/mne-python/mne/commands/mne_make_scalp_surfaces.py", line 131, in _run
    incomplete=incomplete)
  File "/home/appelhoff/mne-python/mne/bem.py", line 505, in _surfaces_to_bem
    _check_complete_surface(surf, copy=True, incomplete=incomplete)
  File "/home/appelhoff/mne-python/mne/bem.py", line 266, in _check_complete_surface
    raise RuntimeError(msg)
RuntimeError: Surface outer skin  has topological defects: 40 / 30000 vertices have fewer than three neighboring triangles [1079, 1402, 3668, 3786, 6523, 6524, 6598, 6866, 7317, 8067, 8301, 8347, 8422, 8817, 8867, 8940, 8941, 9039, 9197, 9222, 9341, 9433, 10947, 11039, 11163, 11622, 11725, 11880, 11952, 12030, 12420, 12783, 12897, 13225, 13528, 13737, 13937, 13995, 13996, 14305]
```







Discussion:
Use `--force`.

We can improve the error message to suggest this as an option if we want.

Resolving patch:
diff --git a/mne/bem.py b/mne/bem.py
--- a/mne/bem.py
+++ b/mne/bem.py
@@ -253,15 +253,14 @@ def _fwd_bem_ip_modify_solution(solution, ip_solution, ip_mult, n_tri):
     return
 
 
-def _check_complete_surface(surf, copy=False, incomplete='raise'):
+def _check_complete_surface(surf, copy=False, incomplete='raise', extra=''):
     surf = complete_surface_info(surf, copy=copy, verbose=False)
     fewer = np.where([len(t) < 3 for t in surf['neighbor_tri']])[0]
     if len(fewer) > 0:
-        msg = ('Surface %s has topological defects: %d / %d '
-               'vertices have fewer than three neighboring '
-               'triangles [%s]' % (_surf_name[surf['id']],
-                                   len(fewer), surf['ntri'],
-                                   ', '.join(str(f) for f in fewer)))
+        msg = ('Surface {} has topological defects: {:.0f} / {:.0f} vertices '
+               'have fewer than three neighboring triangles [{}]{}'
+               .format(_surf_name[surf['id']], len(fewer), surf['ntri'],
+                       ', '.join(str(f) for f in fewer), extra))
         if incomplete == 'raise':
             raise RuntimeError(msg)
         else:
@@ -484,7 +483,7 @@ def _check_thicknesses(surfs):
 
 
 def _surfaces_to_bem(surfs, ids, sigmas, ico=None, rescale=True,
-                     incomplete='raise'):
+                     incomplete='raise', extra=''):
     """Convert surfaces to a BEM."""
     # equivalent of mne_surf2bem
     # surfs can be strings (filenames) or surface dicts
@@ -502,7 +501,8 @@ def _surfaces_to_bem(surfs, ids, sigmas, ico=None, rescale=True,
     for surf, id_ in zip(surfs, ids):
         # Do topology checks (but don't save data) to fail early
         surf['id'] = id_
-        _check_complete_surface(surf, copy=True, incomplete=incomplete)
+        _check_complete_surface(surf, copy=True, incomplete=incomplete,
+                                extra=extra)
         surf['coord_frame'] = surf.get('coord_frame', FIFF.FIFFV_COORD_MRI)
         surf.update(np=len(surf['rr']), ntri=len(surf['tris']))
         if rescale:
diff --git a/mne/commands/mne_make_scalp_surfaces.py b/mne/commands/mne_make_scalp_surfaces.py
--- a/mne/commands/mne_make_scalp_surfaces.py
+++ b/mne/commands/mne_make_scalp_surfaces.py
@@ -107,9 +107,11 @@ def check_seghead(surf_path=op.join(subj_path, 'surf')):
     dense_fname = op.join(bem_dir, '%s-head-dense.fif' % subject)
     logger.info('2. Creating %s ...' % dense_fname)
     _check_file(dense_fname, overwrite)
+    # Helpful message if we get a topology error
+    msg = '\n\nConsider using --force as an additional input parameter.'
     surf = mne.bem._surfaces_to_bem(
         [surf], [mne.io.constants.FIFF.FIFFV_BEM_SURF_ID_HEAD], [1],
-        incomplete=incomplete)[0]
+        incomplete=incomplete, extra=msg)[0]
     mne.write_bem_surfaces(dense_fname, surf)
     levels = 'medium', 'sparse'
     tris = [] if no_decimate else [30000, 2500]
@@ -128,7 +130,7 @@ def check_seghead(surf_path=op.join(subj_path, 'surf')):
         dec_surf = mne.bem._surfaces_to_bem(
             [dict(rr=points, tris=tris)],
             [mne.io.constants.FIFF.FIFFV_BEM_SURF_ID_HEAD], [1], rescale=False,
-            incomplete=incomplete)
+            incomplete=incomplete, extra=msg)
         mne.write_bem_surfaces(dec_fname, dec_surf)
 
 


Test patch: