Fixing BEM and head surfaces#

Sometimes when creating a BEM model the surfaces need manual correction because of a series of problems that can arise (e.g. intersection between surfaces). Here, we will see how this can be achieved by exporting the surfaces to the 3D modeling program Blender, editing them, and re-importing them. We will also give a simple example of how to use pymeshfix to fix topological problems.

Much of this tutorial is based on ezemikulan/blender_freesurfer by Ezequiel Mikulan.

# Authors: Marijn van Vliet <w.m.vanvliet@gmail.com>
#          Ezequiel Mikulan <e.mikulan@gmail.com>
#          Manorama Kadwani <manorama.kadwani@gmail.com>
#
# License: BSD-3-Clause
# Copyright the MNE-Python contributors.
import os
import shutil

import mne

data_path = mne.datasets.sample.data_path()
subjects_dir = data_path / "subjects"
bem_dir = subjects_dir / "sample" / "bem" / "flash"
surf_dir = subjects_dir / "sample" / "surf"

Exporting surfaces to Blender#

In this tutorial, we are working with the MNE-Sample set, for which the surfaces have no issues. To demonstrate how to fix problematic surfaces, we are going to manually place one of the inner-skull vertices outside the outer-skill mesh.

We then convert the surfaces to .obj files and create a new folder called conv inside the FreeSurfer subject folder to keep them in.

# Put the converted surfaces in a separate 'conv' folder
conv_dir = subjects_dir / "sample" / "conv"
os.makedirs(conv_dir, exist_ok=True)

# Load the inner skull surface and create a problem
# The metadata is empty in this example. In real study, we want to write the
# original metadata to the fixed surface file. Set read_metadata=True to do so.
coords, faces = mne.read_surface(bem_dir / "inner_skull.surf")
coords[0] *= 1.1  # Move the first vertex outside the skull

# Write the inner skull surface as an .obj file that can be imported by
# Blender.
mne.write_surface(conv_dir / "inner_skull.obj", coords, faces, overwrite=True)

# Also convert the outer skull surface.
coords, faces = mne.read_surface(bem_dir / "outer_skull.surf")
mne.write_surface(conv_dir / "outer_skull.obj", coords, faces, overwrite=True)
Overwriting existing file.
Overwriting existing file.

Editing in Blender#

See the following video tutorial for how to import, edit and export surfaces in Blender (step-by-step instructions are also below):

We can now open Blender and import the surfaces. Go to File > Import > Wavefront (.obj). Navigate to the conv folder and select the file you want to import. Make sure to select the Keep Vert Order option. You can also select the Y Forward option to load the axes in the correct direction (RAS):

Importing .obj files in Blender

For convenience, you can save these settings by pressing the + button next to Operator Presets.

Repeat the procedure for all surfaces you want to import (e.g. inner_skull and outer_skull).

You can now edit the surfaces any way you like. See the Beginner Blender Tutorial Series to learn how to use Blender. Specifically, part 2 will teach you how to use the basic editing tools you need to fix the surface.

Editing surfaces in Blender

Using the fixed surfaces in MNE-Python#

In Blender, you can export a surface as an .obj file by selecting it and go to File > Export > Wavefront (.obj). You need to again select the Y Forward option and check the Keep Vertex Order box.

Exporting .obj files in Blender

Each surface needs to be exported as a separate file. We recommend saving them in the conv folder and ending the file name with _fixed.obj, although this is not strictly necessary.

In order to be able to run this tutorial script top to bottom, we here simulate the edits you did manually in Blender using Python code:

coords, faces = mne.read_surface(conv_dir / "inner_skull.obj")
coords[0] /= 1.1  # Move the first vertex back inside the skull
mne.write_surface(conv_dir / "inner_skull_fixed.obj", coords, faces, overwrite=True)
Overwriting existing file.

Back in Python, you can read the fixed .obj files and save them as FreeSurfer .surf files. For the mne.make_bem_model() function to find them, they need to be saved using their original names in the surf folder, e.g. bem/inner_skull.surf. Be sure to first backup the original surfaces in case you make a mistake!

# Read the fixed surface
coords, faces = mne.read_surface(conv_dir / "inner_skull_fixed.obj")

# Backup the original surface
shutil.copy(bem_dir / "inner_skull.surf", bem_dir / "inner_skull_orig.surf")

# Overwrite the original surface with the fixed version
# In real study you should provide the correct metadata using ``volume_info=``
# This could be accomplished for example with:
#
# _, _, vol_info = mne.read_surface(bem_dir / 'inner_skull.surf',
#                                   read_metadata=True)
# mne.write_surface(bem_dir / 'inner_skull.surf', coords, faces,
#                   volume_info=vol_info, overwrite=True)

Editing the head surfaces#

Sometimes the head surfaces are faulty and require manual editing. We use mne.write_head_bem() to convert the fixed surfaces to .fif files.

Low-resolution head#

For EEG forward modeling, it is possible that outer_skin.surf would be manually edited. In that case, remember to save the fixed version of -head.fif from the edited surface file for coregistration.

# Load the fixed surface
coords, faces = mne.read_surface(bem_dir / "outer_skin.surf")

# Make sure we are in the correct directory
head_dir = bem_dir.parent

# Remember to backup the original head file in advance!
# Overwrite the original head file
#
# mne.write_head_bem(head_dir / 'sample-head.fif', coords, faces,
#                    overwrite=True)

High-resolution head#

We use mne.read_bem_surfaces() to read the head surface files. After editing, we again output the head file with mne.write_head_bem(). Here we use -head.fif for speed.

# If ``-head-dense.fif`` does not exist, you need to run
# ``mne make_scalp_surfaces`` first.
# [0] because a list of surfaces is returned
surf = mne.read_bem_surfaces(head_dir / "sample-head.fif")[0]

# For consistency only
coords = surf["rr"]
faces = surf["tris"]

# Write the head as an .obj file for editing
mne.write_surface(conv_dir / "sample-head.obj", coords, faces, overwrite=True)

# Usually here you would go and edit your meshes.
#
# Here we just use the same surface as if it were fixed
# Read in the .obj file
coords, faces = mne.read_surface(conv_dir / "sample-head.obj")

# Remember to backup the original head file in advance!
# Overwrite the original head file
#
# mne.write_head_bem(head_dir / 'sample-head.fif', coords, faces,
#                    overwrite=True)
    1 BEM surfaces found
    Reading a surface...
[done]
    1 BEM surfaces read
Overwriting existing file.

Note

See also Cleaning up a bad dense head surface by smoothing for a possible alternative high-resolution head fix using FreeSurfer smoothing instead of blender.

Blender editing tips#

A particularly useful operation is the Shrinkwrap functionality, that will restrict one surface inside another surface (for example the Skull inside the Outer Skin). Here is how to use it:

① Select surface

Select the surface that is creating the problem.

② Select vertices

In Edit Mode, press C to use the circle selection tool to select the vertices that are outside.

③-⑤ Group vertices

In the Object Data Properties tab use the + button to add a Vertex Group and click Assign to assign the current selection to the group.

⑥-⑧ Shrinkwrap

In the Modifiers tab go to Add Modifier add a Shrinkwrap modifier and set it to snap Inside with the outer surface as the Target and the Group that you created before as the Vertex Group. You can then use the Offset parameter to adjust the distance.

Note

If nothing happens, the normals of the inner skull surface may be inverted. To fix this select all the vertices or faces of the inner skull and go to Mesh>Normals>Flip in the toolbar.

⑨ Apply

In Object Mode click on the down-pointing arrow of the Shrinkwrap modifier and click on Apply.

Shrinkwrap functionality in Blender

That’s it! You are ready to continue with your analysis pipeline (e.g. running mne.make_bem_model()).

What if you still get an error?#

Blender operations#

When editing BEM surfaces/meshes in Blender, make sure to use tools that do not change the number or order of vertices, or the geometry of triangular faces. For example, avoid the extrusion tool, because it duplicates the extruded vertices.

Cleaning up a bad dense head surface by smoothing#

If you get a rough head surfaces when using mne make_scalp_surfaces, consider smoothing your T1 ahead of time with a Gaussian kernel with FreeSurfer using something like the following within the subject’s mri directory:

$ mri_convert --fwhm 3 T1.mgz T1_smoothed_3.mgz

Here the --fwhm argument determines how much smoothing (in mm) to apply. Then delete SUBJECTS_DIR/SUBJECT/surf/lh.seghead, and re-run mne make_scalp_surfaces with the additional arguments --mri="T1_smoothed_3.mgz" --overwrite, and you should get cleaner surfaces.

Topological errors#

MNE-Python requires that meshes satisfy some topological checks to ensure that subsequent processing like BEM solving and electrode projection can work properly.

Below are some examples of errors you might encounter when working with meshes in MNE-Python, and the likely causes of those errors.

  1. Cannot decimate to requested ico grade

    This error is caused by having too few or too many vertices. The full error is something like:

    RuntimeError: Cannot decimate to requested ico grade 4. The provided
    BEM surface has 20516 triangles, which cannot be isomorphic with a
    subdivided icosahedron. Consider manually decimating the surface to a
    suitable density and then use ico=None in make_bem_model.
    
  2. Surface inner skull has topological defects

    This error can occur when trying to match the original number of triangles by removing vertices. The full error looks like:

    RuntimeError: Surface inner skull has topological defects: 12 / 20484
    vertices have fewer than three neighboring triangles [733, 1014,
     2068, 7732, 8435, 8489, 10181, 11120, 11121, 11122, 11304, 11788]
    
  3. Surface inner skull is not complete

    This error (like the previous error) reflects a problem with the surface topology (i.e., the expected pattern of vertices/edges/faces is disrupted).

    RuntimeError: Surface inner skull is not complete (sum of solid
    angles yielded 0.999668, should be 1.)
    
  4. Triangle ordering is wrong

    This error reflects a mismatch between how the surface is represented in memory (the order of the vertex/face definitions) and what is expected by MNE-Python. The full error is:

    RuntimeError: The source surface has a matching number of triangles
    but ordering is wrong
    

Dealing with topology in blender#

For any of these errors, it is usually easiest to start over with the unedited BEM surface and try again, making sure to only move vertices and faces without adding or deleting any. For example, select a circle of vertices, then press G to drag them to the desired location. Smoothing a group of selected vertices in Blender (by right-clicking and selecting “Smooth Vertices”) can also be helpful.

Dealing with topology using pymeshfix#

pymeshfix is a GPL-licensed Python module designed to produce water-tight meshes that satisfy the topological checks listed above. For example, if your 'SUBJECTS_DIR/SUBJECT/surf/lh.seghead' has topological problems and Cleaning up a bad dense head surface by smoothing does not work, you can try fixing the mesh instead. After installing pymeshfix using conda or pip, from within the 'SUBJECTS_DIR/SUBJECT/surf' directory, you could try:

>>> import pymeshfix
>>> rr, tris = mne.read_surface('lh.seghead')
>>> rr, tris = pymeshfix.clean_from_arrays(rr, tris)
>>> mne.write_surface('lh.seghead', rr, tris)

In some cases this could fix the topology such that a subsequent call to mne make_scalp_surfaces will succeed without needing to use the --force parameter.

Total running time of the script: (0 minutes 11.747 seconds)

Estimated memory usage: 154 MB

Gallery generated by Sphinx-Gallery