Morphing subject's hippocampus source space to fsaverage template

Hi all,
I’m trying to morph source estimates from a subject’s custom hippocampus surface to the fsaverage template using mne.compute_source_morph. However, the morphing appears misaligned — the activity does not map properly to the corresponding neighboring vertices on fsaverage.

To test this, I created a synthetic STC with some activity at some known vertices. After morphing, the peak appears at a distant or incorrect location on fsaverage. This suggests that the surface registration (?h.sphere.reg) might not be accurate or aligned.

Here’s a summary of what I did:

  • Created hippocampus-only surface and sphere (mris_sphere)
  • Registered to a custom template (mris_register)
  • Built a source space (mne.setup_source_space)
  • Applied compute_source_morph and tested STC projection

Is there a reliable way to ensure correct morphing when using non-standard surfaces (like hippocampus-only)?

Any advice would be appreciated!

here is my test code:

import mne
import pdb  # Importing the debugger

subjects_dir = '/media/dip_linux/Elements/Visual_p3_data/'
subject_trans = '/media/dip_linux/Elements/Visual_p3_data/3847/3847-trans1.fif'
fs_trans = '/media/dip_linux/Elements/Visual_p3_data/fsaverage/fsaverage-trans1.fif'
subject = '3847'
fs_subject = 'fsaverage'

# Compute source spaces
hippocampal_src = mne.setup_source_space(subject=subject, surface='smoothwm', spacing=3,
                                         subjects_dir=subjects_dir, n_jobs=3)

fs_hippocampal_src = mne.setup_source_space(subject=fs_subject, surface='smoothwm', spacing=3,
                                             subjects_dir=subjects_dir, n_jobs=3)

# Create a single 3D visualization with both subjects
fig = mne.viz.plot_alignment(subject=subject, subjects_dir=subjects_dir, surfaces="smoothwm", 
                             src=hippocampal_src, trans=subject_trans)

# Add fsaverage to the same figure
mne.viz.plot_alignment(subject=fs_subject, subjects_dir=subjects_dir, surfaces="smoothwm", 
                       src=fs_hippocampal_src, trans=fs_trans, fig=fig)  

# Adjust the 3D view
mne.viz.set_3d_view(fig, azimuth=180, elevation=90, distance=0.30, focalpoint=(-0.03, -0.01, 0.03))


# testing
# Morph subject source space to fsaverage
morph = mne.compute_source_morph(src=hippocampal_src, subject_from=subject, subject_to='fsaverage',src_to=fs_hippocampal_src,
                                         subjects_dir=subjects_dir)

import numpy as np

# Create synthetic STC
n_verts = sum(s['nuse'] for s in hippocampal_src)
vertices = [s['vertno'] for s in hippocampal_src]

n_times = 500
tmin = 0
tstep = 1e-3  # 1 ms per sample

# Simulate peak at some random vertex and time 300 ms (index 300)
stc_data = np.zeros((n_verts, n_times))

peak_vertices = [50, 200, 250, 300, 450, 500, 650, 800]
for v in peak_vertices:
    stc_data[v, 300] = 25.0

stc = mne.SourceEstimate(stc_data, vertices=vertices,
                         tmin=tmin, tstep=tstep,
                         subject=subject)

# Morph to fsaverage
stc_fs = morph.apply(stc)

# Find peak vertex in morphed stc
peak_vertex, peak_time = stc_fs.get_peak()
print(f"Morphed peak at vertex {peak_vertex}, time {peak_time:.3f}s")

# Plot morph result
stc.plot(initial_time=0.3, surface='smoothwm', hemi='both', smoothing_steps=5)
stc_fs.plot(initial_time=0.3, surface='smoothwm', hemi='both',smoothing_steps=5)

n_verts = sum(s['nuse'] for s in hippocampal_src)
vertices = [s['vertno'] for s in hippocampal_src]

# Create a source estimate
stc = mne.SourceEstimate(np.eye(n_verts), vertices, 0, 1e-3, subject=subject)
stc_morphed = morph.apply(stc)
stc_morphed.plot(initial_time=0.3, surface='smoothwm')

results:

best,
Dip

Hmm… I would first try seeing if you can take some .w file data, which FreeSurfer should be able to understand, and get FreeSurfer to morph that data correctly. Once that works we should hopefully have the machinery in MNE-Python to match what it does. Can you see if you can get some FreeSurfer commands to morph the data correctly?

@larsoner Hi Eric, thanks for getting back to this.

Here are some tests results using two subjects with spherical alignment from Freesurfer :

sub1:

Subject vertex 50 → fsaverage vertices: [ 5 13 16 23 25 28 37 39 52 65]
Subject vertex 200 → fsaverage vertices: [270 279 306 315]
Subject vertex 250 → fsaverage vertices: [299 323]
Subject vertex 300 → fsaverage vertices: [378 392 393 412]
Subject vertex 450 → fsaverage vertices: [582 591 599 608 609]

sub2:

Subject vertex 50 → fsaverage vertices: [108 116 127 140]
Subject vertex 200 → fsaverage vertices: [243 253 263 264]
Subject vertex 250 → fsaverage vertices: [279 306 315 357]
Subject vertex 300 → fsaverage vertices: [381 395 396]
Subject vertex 450 → fsaverage vertices: [606]

Once I managed to remove overlapping intersecting faces from the spherical surface (?h.sphere), the quality of the resulting registration (?h.sphere.reg) improved significantly. However, the curvature-based spherical alignment is still not perfect. In my experience, the 2D surface of the hippocampus doesn’t unfold onto a sphere as cleanly as the neocortex does. Still, the current results seem usable to me. Perhaps other registration methods could provide more accurate morphing for the hippocampus?