Repository: mne-tools/mne-python

Issue:
ENH: Write source spaces and BEMs to HDF5
Dear mne-python,

I am currently working at the Forschungszentrum Jülich on my master thesis. I perform an spatio-temporal clustering statistical test on volumetric source activities from an MFT calculation. 
Therefore I created a volume source space for all the volume labels, which were identified by FreeSurfer. Since I set the volume source space up for all the volume labels in my subject, mne-python encountered a problem, while saving the resulting source-space.fif-file. The common way to create the volume label wise source spaces is to create an entire volume source space for each label and save the significant vertice indices for the label in src['vertno'], while the remaining volume source space is exactly the same for any label and also saved. Since this is done, in my case for ~46 labels, I end up with a large source space file, which is, in the end, not able to be saved by mne-python, as the fif-file is larger than 2GB.
Despite this saving problem, which can be avoided with a _work around_, I encountered a more sincere problem. Multiple vertices (~3000 out of 17000) are considered to be part of multiple labels, so a large number of vertices is to be considered a vertice in (e.g.) 'Right-Cerebral-White-Matter' as well as 'Right-Cerebral-Cortex'.

This will result in subsequent faults , hence I want to ask for help for a solution, which will create a more suitable volume source space to store as well as referencing each vertice to just one specific label.

Since I am very interested in the solution in detail, I am open for / willing to participate in any discussion.

Yours sincerely,
Daniel van de Velden 

P.S.: My _work around_ to get all the labels vertices is looping over all labels, create the whole volume source space, grabbing the src['vertno'] info from it and store it separately in a dictionary. Then I delete the source space and go for the next with the next volume label. After this is done for all labels, I create an entire volume source space without any labels restrictions.


Discussion:
I am not sure to understand what happens.

can you share a code snippet using sample dataset to replicate this?

Of course I can. Sorry I thought I explained it detailed enough. My bad.
Here you go:

```
from mne.datasets import sample
import mne
import numpy as np

data_path = sample.data_path()
subjects_dir = data_path + '/subjects'
subject = 'sample'
aseg_fname = subjects_dir + '/sample/mri/aseg.mgz'
bem_fname = subjects_dir + '/sample/bem/sample-5120-5120-5120-bem-sol.fif'
grid_space = 5.

volume_labels = mne.get_volume_labels_from_aseg(aseg_fname)
  
print ' Making Volume Source Space...'
vsrc = mne.setup_volume_source_space(subject=subject, pos=grid_space,
                                     mri=aseg_fname,
                                     bem=bem_fname, 
                                     volume_label=volume_labels,
                                     subjects_dir=subjects_dir, 
                                     add_interpolator=True,
                                     verbose=None)

volume_source_space_label_dictionary = {}
for label, vol_src in zip(volume_labels, vsrc):
  vertnos = vol_src['vertno']
  volume_source_space_label_dictionary.update({label:vertnos})

vol_source_space_name = subjects_dir + subject + '/%s_vol-%.4f-src' %(subject, grid_space)
np.save( vol_source_space_name + '_vertno_labelwise', volume_source_space_label_dictionary)
mne.write_source_spaces(vol_source_space_name + '.fif', vsrc, overwrite=True)

#vsrc = mne.setup_volume_source_space(subject=subject, pos=grid_space,
#                                    mri=aseg_fname,
#                                    bem=bem_fname,
#                                    subjects_dir=subjects_dir, 
#                                     add_interpolator=True,
#                                     verbose=None)

#vol_source_space_name = subjects_dir + subject + '/%s_vol-%.4f-src' %(subject, grid_space)
#np.save( vol_source_space_name + '_vertno_labelwise', volume_source_space_label_dictionary)
#mne.write_source_spaces(vol_source_space_name + '.fif', vsrc, overwrite=True)
```

If you run this code snippet, it will create N numbers of volume source spaces. One for each label. Keeping the whole source space info in every source space, except the src['vertno'] array. This only contains the indices for the vertices, relevant to the chosen volume label. And will be too big to save. A work around is creating a volume source space without volume restrictions but keep the `volume_source_space_label_dictionary` to keep the information.

You can also check the `volume_source_space_label_dictionary `, that many vertices are tend to appear in multiple volume labels. 
your script (with tiny changes below) works fine for me:

```
from mne.datasets import sample
import mne
import numpy as np

data_path = sample.data_path()
subjects_dir = data_path + '/subjects'
subject = 'sample'
aseg_fname = subjects_dir + '/sample/mri/aseg.mgz'
bem_fname = subjects_dir + '/sample/bem/sample-5120-5120-5120-bem-sol.fif'
grid_space = 5.

volume_labels = mne.get_volume_labels_from_aseg(aseg_fname)

print(' Making Volume Source Space...')
vsrc = mne.setup_volume_source_space(subject=subject, pos=grid_space,
                                     mri=aseg_fname,
                                     bem=bem_fname,
                                     volume_label=volume_labels,
                                     subjects_dir=subjects_dir,
                                     add_interpolator=True,
                                     verbose=None)

volume_source_space_label_dictionary = {}

for label, vol_src in zip(volume_labels, vsrc):
    vertnos = vol_src['vertno']
    volume_source_space_label_dictionary.update({label: vertnos})

vsrc = mne.setup_volume_source_space(subject=subject, pos=grid_space,
                                     mri=aseg_fname,
                                     bem=bem_fname,
                                     subjects_dir=subjects_dir,
                                     add_interpolator=True,
                                     verbose=None)

vol_source_space_name = '%s_vol-%.4f-src' % (subject, grid_space)
mne.write_source_spaces(vol_source_space_name + '.fif', vsrc, overwrite=True)
```
Good morning Alexandre Gramfort,

at first I wish you a good start in this week. Respectively, to your tiny changes in this code example I agree with you, that it will run without any error. However this changes bypass the essential error I was talking about. Please try run following code snippet:
```
from mne.datasets import sample
import mne
import numpy as np

data_path = sample.data_path()
subjects_dir = data_path + '/subjects'
subject = 'sample'
aseg_fname = subjects_dir + '/sample/mri/aseg.mgz'
bem_fname = subjects_dir + '/sample/bem/sample-5120-5120-5120-bem-sol.fif'
grid_space = 5.

volume_labels = mne.get_volume_labels_from_aseg(aseg_fname)

print(' Making Volume Source Space...')
vsrc = mne.setup_volume_source_space(subject=subject, pos=grid_space,
                                     mri=aseg_fname,
                                     bem=bem_fname,
                                     volume_label=volume_labels,
                                     subjects_dir=subjects_dir,
                                     add_interpolator=True,
                                     verbose=None)

volume_source_space_label_dictionary = {}

for label, vol_src in zip(volume_labels, vsrc):
    vertnos = vol_src['vertno']
    volume_source_space_label_dictionary.update({label: vertnos})

vol_source_space_name = '%s_vol-%.4f-src' % (subject, grid_space)
mne.write_source_spaces(vol_source_space_name + '.fif', vsrc, overwrite=True)
```

Since the vol_source_space will be too big, it will not be able to be saved, hence the limited fif-file size of 2GB. You will receive the following IOError Message:

```
  File "/home/daniel/anaconda2/lib/python2.7/site-packages/mne/io/write.py", line 272, in check_fiff_length
    raise IOError('FIFF file exceeded 2GB limit, please split file or '

IOError: FIFF file exceeded 2GB limit, please split file or save to a different format
```

Additionally I'd like to mention, that for the subject 'fsaverage' and 'sample' there are no vertice duplicates across the labels, but in every subject I created a volume source space for (+24 subjects).
I also checked the duplicates for their correctness, in case of their allocation to the labels they appear in. After correcting their allocation with the following code snippet I got rid of all duplicates, without losing any loss of a vertice from the entire volume source space.

```
  from mne.source_space import _get_lut, _get_lut_id, _get_mgz_header
  import nibabel as nib
  from scipy import linalg
  from mne.transforms import apply_trans
  import numpy as np

  mgz = nib.load(fname_s_aseg)
  mgz_data = mgz.get_data()
  lut = _get_lut()
  vox2rastkr_trans = _get_mgz_header(fname_s_aseg)['vox2ras_tkr']
  vox2rastkr_trans[:3] /= 1000.
  inv_vox2rastkr_trans = linalg.inv(vox2rastkr_trans)
  all_volume_labels = []
  vol_lab = mne.get_volume_labels_from_aseg(fname_s_aseg)
  for lab in vol_lab: all_volume_labels.append( lab.encode() )
  print '#### Attempting to get rid of vertice duplicates in labels..\n'
  for p, label in enumerate(all_volume_labels):
    lab_arr = label_list_subject[label]
    lab_id = _get_lut_id(lut, label, True)[0]
    del_ver_idx_list = []
  
    for arr_id, i in enumerate(lab_arr, 0):
      lab_vert_coord = subj_src[0]['rr'][i]
      lab_vert_mgz_idx = apply_trans(inv_vox2rastkr_trans, lab_vert_coord)
      orig_idx = mgz_data[int(round(lab_vert_mgz_idx[0])), int(round(lab_vert_mgz_idx[1])), int(round(lab_vert_mgz_idx[2]))]
      
      if orig_idx != lab_id:
        del_ver_idx_list.append(arr_id)
        
    del_ver_idx = np.asarray(del_ver_idx_list)
    label_list_subject[label] = np.delete(label_list_subject[label], del_ver_idx)
  print '[done]'  
```

Adding this to the actual `_make_volume_source_space(surf, pos, exclude, mindist, mri, volume_label) `-function, after generously choosing vertices for the VOI, to check if the selected vertices are correct, would solve the duplicate problem.

Of course, this is just a friendly reference. If my suggestion is rude, false or doesn't work with the functions actual purpose, please let me know. I am very interested in learning and discussion about this subject. 

I also apologize for any trouble this 'issue thread' might cause.

Have a nice day,
Daniel 
ok I replicated the

OSError: FIFF file exceeded 2GB limit, please split file or save to a
different format

we should add writing support with hdf5...

Thank you very much. 
I will go with my work around, so the code with your tiny little changes. Thanks again and a support with hdf5 will be very useful for label-wise volume source space construction.

If you don't want discuss anything I will close this thread.

I wish you and your family and friends a merry Christmas,
Daniel van de Velden
 
keep it open. We'll close it when we add support for HDF5 files.

Hey, are there any updates on this issue? I'm trying to save ico5 BEM solution, and getting the same error (OSError: FIFF file exceeded 2GB limit, please split file or save to a different format)
Thanks!
Fortunately `ConductorModel` subclasses a standard class (dict) so for now you can probably hack it with:
```
mne.externals.h5io.write_hdf5('my-bem-sol.h5', my_bem_sol)
my_bem_sol_2 = mne.externals.h5io.read_hdf5('my-bem-sol.h5')
my_bem_sol_2 = mne.bem.ConductorModel(my_bem_sol_2)
```
(incidentally `SourceSpaces` subclasses the standard class `list`, too, so the same workaround will probably work for this volume source space)

Resolving patch:
diff --git a/mne/bem.py b/mne/bem.py
--- a/mne/bem.py
+++ b/mne/bem.py
@@ -32,8 +32,9 @@
 from .transforms import _ensure_trans, apply_trans, Transform
 from .utils import (verbose, logger, run_subprocess, get_subjects_dir, warn,
                     _pl, _validate_type, _TempDir, _check_freesurfer_home,
-                    _check_fname, has_nibabel)
+                    _check_fname, has_nibabel, _check_option)
 from .fixes import einsum
+from .externals.h5io import write_hdf5, read_hdf5
 
 
 # ############################################################################
@@ -1255,9 +1256,36 @@ def read_bem_surfaces(fname, patch_stats=False, s_id=None, verbose=None):
     --------
     write_bem_surfaces, write_bem_solution, make_bem_model
     """
+    # Open the file, create directory
+    _validate_type(s_id, ('int-like', None), 's_id')
+    fname = _check_fname(fname, 'read', True, 'fname')
+    if fname.endswith('.h5'):
+        surf = _read_bem_surfaces_h5(fname, s_id)
+    else:
+        surf = _read_bem_surfaces_fif(fname, s_id)
+    if s_id is not None and len(surf) != 1:
+        raise ValueError('surface with id %d not found' % s_id)
+    for this in surf:
+        if patch_stats or this['nn'] is None:
+            _check_complete_surface(this)
+    return surf[0] if s_id is not None else surf
+
+
+def _read_bem_surfaces_h5(fname, s_id):
+    bem = read_hdf5(fname)
+    try:
+        [s['id'] for s in bem['surfs']]
+    except Exception:  # not our format
+        raise ValueError('BEM data not found')
+    surf = bem['surfs']
+    if s_id is not None:
+        surf = [s for s in surf if s['id'] == s_id]
+    return surf
+
+
+def _read_bem_surfaces_fif(fname, s_id):
     # Default coordinate frame
     coord_frame = FIFF.FIFFV_COORD_MRI
-    # Open the file, create directory
     f, tree, _ = fiff_open(fname)
     with f as fid:
         # Find BEM
@@ -1281,8 +1309,6 @@ def read_bem_surfaces(fname, patch_stats=False, s_id=None, verbose=None):
             surf = [_read_bem_surface(fid, bsurf, coord_frame, s_id)
                     for bsurf in bemsurf]
             surf = [s for s in surf if s is not None]
-            if not len(surf) == 1:
-                raise ValueError('surface with id %d not found' % s_id)
         else:
             surf = list()
             for bsurf in bemsurf:
@@ -1291,10 +1317,7 @@ def read_bem_surfaces(fname, patch_stats=False, s_id=None, verbose=None):
                 surf.append(this)
                 logger.info('[done]')
             logger.info('    %d BEM surfaces read' % len(surf))
-        for this in surf:
-            if patch_stats or this['nn'] is None:
-                _check_complete_surface(this)
-    return surf[0] if s_id is not None else surf
+    return surf
 
 
 def _read_bem_surface(fid, this, def_coord_frame, s_id=None):
@@ -1388,28 +1411,66 @@ def read_bem_solution(fname, verbose=None):
     make_bem_solution
     write_bem_solution
     """
+    fname = _check_fname(fname, 'read', True, 'fname')
     # mirrors fwd_bem_load_surfaces from fwd_bem_model.c
-    logger.info('Loading surfaces...')
-    bem_surfs = read_bem_surfaces(fname, patch_stats=True, verbose=False)
-    if len(bem_surfs) == 3:
+    if fname.endswith('.h5'):
+        logger.info('Loading surfaces and solution...')
+        bem = read_hdf5(fname)
+    else:
+        bem = _read_bem_solution_fif(fname)
+
+    if len(bem['surfs']) == 3:
         logger.info('Three-layer model surfaces loaded.')
         needed = np.array([FIFF.FIFFV_BEM_SURF_ID_HEAD,
                            FIFF.FIFFV_BEM_SURF_ID_SKULL,
                            FIFF.FIFFV_BEM_SURF_ID_BRAIN])
-        if not all(x['id'] in needed for x in bem_surfs):
+        if not all(x['id'] in needed for x in bem['surfs']):
             raise RuntimeError('Could not find necessary BEM surfaces')
         # reorder surfaces as necessary (shouldn't need to?)
         reorder = [None] * 3
-        for x in bem_surfs:
+        for x in bem['surfs']:
             reorder[np.where(x['id'] == needed)[0][0]] = x
-        bem_surfs = reorder
-    elif len(bem_surfs) == 1:
-        if not bem_surfs[0]['id'] == FIFF.FIFFV_BEM_SURF_ID_BRAIN:
+        bem['surfs'] = reorder
+    elif len(bem['surfs']) == 1:
+        if not bem['surfs'][0]['id'] == FIFF.FIFFV_BEM_SURF_ID_BRAIN:
             raise RuntimeError('BEM Surfaces not found')
         logger.info('Homogeneous model surface loaded.')
 
+    assert set(bem.keys()) == set(('surfs', 'solution', 'bem_method'))
+    bem = ConductorModel(bem)
+    bem['is_sphere'] = False
+    # sanity checks and conversions
+    _check_option('BEM approximation method', bem['bem_method'],
+                  (FIFF.FIFFV_BEM_APPROX_LINEAR, FIFF.FIFFV_BEM_APPROX_CONST))
+    dim = 0
+    for surf in bem['surfs']:
+        if bem['bem_method'] == FIFF.FIFFV_BEM_APPROX_LINEAR:
+            dim += surf['np']
+        else:  # method == FIFF.FIFFV_BEM_APPROX_CONST
+            dim += surf['ntri']
+    dims = bem['solution'].shape
+    if len(dims) != 2:
+        raise RuntimeError('Expected a two-dimensional solution matrix '
+                           'instead of a %d dimensional one' % dims[0])
+    if dims[0] != dim or dims[1] != dim:
+        raise RuntimeError('Expected a %d x %d solution matrix instead of '
+                           'a %d x %d one' % (dim, dim, dims[1], dims[0]))
+    bem['nsol'] = bem['solution'].shape[0]
+    # Gamma factors and multipliers
+    _add_gamma_multipliers(bem)
+    kind = {
+        FIFF.FIFFV_BEM_APPROX_CONST: 'constant collocation',
+        FIFF.FIFFV_BEM_APPROX_LINEAR: 'linear_collocation',
+    }[bem['bem_method']]
+    logger.info('Loaded %s BEM solution from %s', kind, fname)
+    return bem
+
+
+def _read_bem_solution_fif(fname):
+    logger.info('Loading surfaces...')
+    surfs = read_bem_surfaces(fname, patch_stats=True, verbose=False)
+
     # convert from surfaces to solution
-    bem = ConductorModel(is_sphere=False, surfs=bem_surfs)
     logger.info('\nLoading the solution matrix...\n')
     f, tree, _ = fiff_open(fname)
     with f as fid:
@@ -1424,42 +1485,10 @@ def read_bem_solution(fname, verbose=None):
         if tag is None:
             raise RuntimeError('No BEM solution found in %s' % fname)
         method = tag.data[0]
-        if method not in (FIFF.FIFFV_BEM_APPROX_CONST,
-                          FIFF.FIFFV_BEM_APPROX_LINEAR):
-            raise RuntimeError('Cannot handle BEM approximation method : %d'
-                               % method)
-
         tag = find_tag(fid, bem_node, FIFF.FIFF_BEM_POT_SOLUTION)
-        dims = tag.data.shape
-        if len(dims) != 2:
-            raise RuntimeError('Expected a two-dimensional solution matrix '
-                               'instead of a %d dimensional one' % dims[0])
-
-        dim = 0
-        for surf in bem['surfs']:
-            if method == FIFF.FIFFV_BEM_APPROX_LINEAR:
-                dim += surf['np']
-            else:  # method == FIFF.FIFFV_BEM_APPROX_CONST
-                dim += surf['ntri']
-
-        if dims[0] != dim or dims[1] != dim:
-            raise RuntimeError('Expected a %d x %d solution matrix instead of '
-                               'a %d x %d one' % (dim, dim, dims[1], dims[0]))
         sol = tag.data
-        nsol = dims[0]
-
-    bem['solution'] = sol
-    bem['nsol'] = nsol
-    bem['bem_method'] = method
 
-    # Gamma factors and multipliers
-    _add_gamma_multipliers(bem)
-    kind = {
-        FIFF.FIFFV_BEM_APPROX_CONST: 'constant collocation',
-        FIFF.FIFFV_BEM_APPROX_LINEAR: 'linear_collocation',
-    }[bem['bem_method']]
-    logger.info('Loaded %s BEM solution from %s', kind, fname)
-    return bem
+    return dict(solution=sol, bem_method=method, surfs=surfs)
 
 
 def _add_gamma_multipliers(bem):
@@ -1497,24 +1526,44 @@ def _bem_find_surface(bem, id_):
 # ############################################################################
 # Write
 
-def write_bem_surfaces(fname, surfs):
+
+def _check_dep_overwrite(fname, overwrite):
+    fname = _check_fname(
+        fname, overwrite=True if overwrite is None else overwrite,
+        name='fname')
+    if op.isfile(fname) and overwrite is None:
+        warn(f'file {fname} exists; overwrite will default to True in 0.21 '
+             'and change to False in 0.22, set it explicitly to avoid this '
+             'warning', DeprecationWarning)
+    return fname
+
+
+@verbose
+def write_bem_surfaces(fname, surfs, overwrite=None, verbose=None):
     """Write BEM surfaces to a fiff file.
 
     Parameters
     ----------
     fname : str
-        Filename to write.
+        Filename to write. Can end with ``.h5`` to write using HDF5.
     surfs : dict | list of dict
         The surfaces, or a single surface.
+    overwrite : bool
+        If True (default in 0.21), overwrite the file.
+    %(verbose)s
     """
     if isinstance(surfs, dict):
         surfs = [surfs]
-    with start_file(fname) as fid:
-        start_block(fid, FIFF.FIFFB_BEM)
-        write_int(fid, FIFF.FIFF_BEM_COORD_FRAME, surfs[0]['coord_frame'])
-        _write_bem_surfaces_block(fid, surfs)
-        end_block(fid, FIFF.FIFFB_BEM)
-        end_file(fid)
+    fname = _check_dep_overwrite(fname, overwrite)
+    if fname.endswith('.h5'):
+        write_hdf5(fname, dict(surfs=surfs), overwrite=True)
+    else:
+        with start_file(fname) as fid:
+            start_block(fid, FIFF.FIFFB_BEM)
+            write_int(fid, FIFF.FIFF_BEM_COORD_FRAME, surfs[0]['coord_frame'])
+            _write_bem_surfaces_block(fid, surfs)
+            end_block(fid, FIFF.FIFFB_BEM)
+            end_file(fid)
 
 
 def _write_bem_surfaces_block(fid, surfs):
@@ -1535,20 +1584,33 @@ def _write_bem_surfaces_block(fid, surfs):
         end_block(fid, FIFF.FIFFB_BEM_SURF)
 
 
-def write_bem_solution(fname, bem):
+@verbose
+def write_bem_solution(fname, bem, overwrite=None, verbose=None):
     """Write a BEM model with solution.
 
     Parameters
     ----------
     fname : str
-        The filename to use.
+        The filename to use. Can end with ``.h5`` to write using HDF5.
     bem : instance of ConductorModel
         The BEM model with solution to save.
+    overwrite : bool
+        If True (default in 0.21), overwrite the file.
+    %(verbose)s
 
     See Also
     --------
     read_bem_solution
     """
+    fname = _check_dep_overwrite(fname, overwrite)
+    if fname.endswith('.h5'):
+        bem = {k: bem[k] for k in ('surfs', 'solution', 'bem_method')}
+        write_hdf5(fname, bem, overwrite=True)
+    else:
+        _write_bem_solution_fif(fname, bem)
+
+
+def _write_bem_solution_fif(fname, bem):
     _check_bem_size(bem['surfs'])
     with start_file(fname) as fid:
         start_block(fid, FIFF.FIFFB_BEM)


Test patch:
diff --git a/mne/tests/test_bem.py b/mne/tests/test_bem.py
--- a/mne/tests/test_bem.py
+++ b/mne/tests/test_bem.py
@@ -19,7 +19,7 @@
 from mne.io.constants import FIFF
 from mne.transforms import translation
 from mne.datasets import testing
-from mne.utils import (run_tests_if_main, catch_logging)
+from mne.utils import (run_tests_if_main, catch_logging, requires_h5py)
 from mne.bem import (_ico_downsample, _get_ico_map, _order_surfaces,
                      _assert_complete_surface, _assert_inside,
                      _check_surface_size, _bem_find_surface)
@@ -66,26 +66,40 @@ def _compare_bem_solutions(sol_a, sol_b):
 
 
 @testing.requires_testing_data
-def test_io_bem(tmpdir):
+@requires_h5py
+@pytest.mark.parametrize('ext', ('fif', 'h5'))
+def test_io_bem(tmpdir, ext):
     """Test reading and writing of bem surfaces and solutions."""
-    temp_bem = op.join(str(tmpdir), 'temp-bem.fif')
-    pytest.raises(ValueError, read_bem_surfaces, fname_raw)
-    pytest.raises(ValueError, read_bem_surfaces, fname_bem_3, s_id=10)
+    import h5py
+    temp_bem = op.join(str(tmpdir), f'temp-bem.{ext}')
+    # model
+    with pytest.raises(ValueError, match='BEM data not found'):
+        read_bem_surfaces(fname_raw)
+    with pytest.raises(ValueError, match='surface with id 10'):
+        read_bem_surfaces(fname_bem_3, s_id=10)
     surf = read_bem_surfaces(fname_bem_3, patch_stats=True)
     surf = read_bem_surfaces(fname_bem_3, patch_stats=False)
     write_bem_surfaces(temp_bem, surf[0])
+    with pytest.deprecated_call(match='overwrite'):
+        write_bem_surfaces(temp_bem, surf[0])
+    if ext == 'h5':
+        with h5py.File(temp_bem, 'r'):  # make sure it's valid
+            pass
     surf_read = read_bem_surfaces(temp_bem, patch_stats=False)
     _compare_bem_surfaces(surf, surf_read)
 
-    pytest.raises(RuntimeError, read_bem_solution, fname_bem_3)
-    temp_sol = op.join(str(tmpdir), 'temp-sol.fif')
+    # solution
+    with pytest.raises(RuntimeError, match='No BEM solution found'):
+        read_bem_solution(fname_bem_3)
+    temp_sol = op.join(str(tmpdir), f'temp-sol.{ext}')
     sol = read_bem_solution(fname_bem_sol_3)
     assert 'BEM' in repr(sol)
     write_bem_solution(temp_sol, sol)
     sol_read = read_bem_solution(temp_sol)
     _compare_bem_solutions(sol, sol_read)
     sol = read_bem_solution(fname_bem_sol_1)
-    pytest.raises(RuntimeError, _bem_find_surface, sol, 3)
+    with pytest.raises(RuntimeError, match='BEM model does not have'):
+        _bem_find_surface(sol, 3)
 
 
 def test_make_sphere_model():