# 'dev\_head\_t' and forward model computation(s)

**URL:** <https://mne.discourse.group/t/dev-head-t-and-forward-model-computation-s/11108>\
**Category:** Support & Discussions\
**Tags:** meg\
**Created:** [April 15, 2025, 3:31pm UTC](https://mne.discourse.group/t/dev-head-t-and-forward-model-computation-s/11108 "2025-04-15T15:31:29Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![vferat](https://yyz2.discourse-cdn.com/free1/user_avatar/mne.discourse.group/vferat/32/4292_2.png) [@vferat](https://mne.discourse.group/u/vferat)\
**Post date:** [April 15, 2025, 3:31pm UTC](https://mne.discourse.group/t/dev-head-t-and-forward-model-computation-s/11108/1 "2025-04-15T15:31:29Z")

</div>

Dear community,

I’m currently working on building an analysis pipeline for computing the MEG forward model, but I have some uncertainties regarding the role of `info["dev_head_t"]` in MNE.

In scenarios where MEG data are collected across multiple sessions for the same participant, each recording can have a different `info["dev_head_t"]` transformation (i.e., a different device-to-head transformation matrix).

If we use the default MEG-to-head transform in the `destination` parameter of `mne.preprocessing.maxwell_filter`, then — as I understand it — MNE will incorporate this transformation when computing the forward model.

Based on this, I assume that it would be **incorrect to reuse a forward model computed with one `info["dev_head_t"]` for another recording that has a different `dev_head_t`**, since the forward solution is dependent on the head position relative to the sensors. Is this assumption correct?

If so, does this mean we should compute **a separate forward model for each unique `info["dev_head_t"]`**? I’d appreciate confirmation or correction of this approach.

To illustrate my concern, here’s an example of code that shows a potentially incorrect usage: a forward model is computed from one head position but applied to data with a different `dev_head_t`:

```python
import numpy as np
from matplotlib import pyplot as plt
import mne

from mne.datasets import testing
from mne.io import read_info

data_dir = testing.data_path(download=True)
subjects_dir = data_dir / "subjects"
sample_meg_dir = data_dir / "MEG" / "sample"
raw_fname = sample_meg_dir / "sample_audvis_trunc_raw.fif"
fwd_fname = sample_meg_dir / "sample_audvis_trunc-meg-eeg-oct-6-fwd.fif"
bem_fname = subjects_dir / "sample" / "bem" / "sample-320-320-320-bem-sol.fif"

# Load data
raw = mne.io.read_raw_fif(raw_fname, allow_maxshield=True, verbose=False)
fwd = mne.read_forward_solution(fwd_fname)

# First head position
info_1 = raw.info.copy()
fwd_1 = mne.make_forward_solution(info_1, fwd["mri_head_t"], fwd["src"], bem_fname)

# Simulate a second recording with a modified head position
info_2 = raw.info.copy()
trans = info_2["dev_head_t"]['trans'].copy()
trans[0, 0] += 1e-1 # Modify the transform slightly
info_2["dev_head_t"] = mne.transforms.Transform(1, 4, trans=trans)
fwd_2 = mne.make_forward_solution(info_2, fwd["mri_head_t"], fwd["src"], bem_fname)

# Apply forward model with mismatched head position
noise_cov = mne.make_ad_hoc_cov(info_1)
inverse = mne.minimum_norm.make_inverse_operator(
    info_1, fwd_2, noise_cov=noise_cov, loose="auto", depth=None, fixed=True
)
mne.minimum_norm.apply_inverse_raw(raw, inverse, lambda2=1.0 / 9.0)

```

Does this kind of mismatch invalidate the results ? If yes, wouldn’t it make sense for MNE to emit a warning or even an error in such cases (assuming that the necessary metadata survives up to that point to allow the verification)?

Any clarification or best practices would be greatly appreciated!

Best regards,

* * *

Related to [this](https://mne.discourse.group/t/head-position-information-when-building-forward-model-for-different-runs/1789) topic

- Operating system: windows
- MNE version : ‘0.20.dev0’

---

<div class="post-metadata">

**Author:** ![mscheltienne](https://yyz2.discourse-cdn.com/free1/user_avatar/mne.discourse.group/mscheltienne/32/827_2.png) [@mscheltienne](https://mne.discourse.group/u/mscheltienne)\
**Post date:** [April 16, 2025, 4:46pm UTC](https://mne.discourse.group/t/dev-head-t-and-forward-model-computation-s/11108/2 "2025-04-16T16:46:27Z")

</div>

Based on this, I assume that it would be **incorrect to reuse a forward model computed with one `info["dev_head_t"]` for another recording that has a different `dev_head_t`**, since the forward solution is dependent on the head position relative to the sensors. Is this assumption correct?

I think so, to re-use the forward model you need the same `dev_head_t`. Which can be achieved by setting the `destination` parameter of MaxWell filter to the same value e.g. using `(0, 0, 0.04)` which corresponds to the default of the MEGIN software, or by providing a transform or the path to a transform file.  
You could also use an average head position between N recordings to get something more realistic that a fix value, using [mne.preprocessing.compute\_average\_dev\_head\_t — MNE 1.9.0 documentation](https://mne.tools/stable/generated/mne.preprocessing.compute_average_dev_head_t.html#mne.preprocessing.compute_average_dev_head_t)

For this mismatch:

```python
inverse = mne.minimum_norm.make_inverse_operator(
    info_1, fwd_2, noise_cov=noise_cov, loose="auto", depth=None, fixed=True
)

```

I don’t know how much it actually impacts the results, but I guess a warning would be a good addition, comparing the transform in `info` and in `fwd`. I don’t know if `fwd["info"]` still contains this information.

Mathieu

---

<div class="post-metadata">

**Author:** ![vferat](https://yyz2.discourse-cdn.com/free1/user_avatar/mne.discourse.group/vferat/32/4292_2.png) [@vferat](https://mne.discourse.group/u/vferat)\
**Post date:** [April 17, 2025, 9:26am UTC](https://mne.discourse.group/t/dev-head-t-and-forward-model-computation-s/11108/3 "2025-04-17T09:26:10Z")

</div>

Hey Mathieu,

Thanks for your response!

I’m already using `mne.preprocessing.compute_average_dev_head_t` to compute an average `["dev_head_t"]` across all recordings, which I then use as the `destination` parameter in the Maxwell filter. So at the moment, I’m computing a single forward solution based on this aligned head position.

That said, I’ve been wondering how well the Maxwell filter handles projecting data to a different head position. I imagine the effectiveness depends on how far the original positions are from the target destination. In more extreme cases, this projection might not be sufficient — and using separate destinations (and thus separate forward solutions) per recording might yield better results. I’m not sure if there are any quantitative analyses on this topic — would be curious to hear if someone come across any.

After a quick check, I noticed that `fwd["info"]["dev_head_t"]` is still present. I think a **warning** could be valuable here, since mismatched `dev_head_t` transforms between the raw data and the forward solution can significantly affect the results. Here’s an updated script illustrating the effect of different `dev_head_t` values in `inst['info']` and `fwd['info']`:

```python
import numpy as np
from matplotlib import pyplot as plt
import mne
from mne.datasets import testing
from mne.io import read_info
from mne.viz.backends.renderer import backend
from mne.viz._brain.view import views_dicts
from mne.viz import set_3d_view
import seaborn as sns

data_dir = testing.data_path(download=True)
subjects_dir = data_dir / "subjects"
sample_meg_dir = data_dir / "MEG" / "sample"
raw_fname = sample_meg_dir / "sample_audvis_trunc_raw.fif"
fwd_fname = sample_meg_dir / "sample_audvis_trunc-meg-eeg-oct-6-fwd.fif"
trans_fname = sample_meg_dir / "sample_audvis_trunc-trans.fif"
mri_fname = subjects_dir / "sample" / "mri" / "T1.mgz"
bem = subjects_dir / "sample" / "bem" / "sample-320-320-320-bem-sol.fif"

fwd = mne.read_forward_solution(fwd_fname)
raw = mne.io.read_raw_fif(raw_fname, allow_maxshield=True, verbose=False)

info_1 = raw.info.copy()
fwd_1 = mne.make_forward_solution(info_1, fwd["mri_head_t"], fwd["src"], bem)

info_2 = raw.info.copy()
trans = info_1["dev_head_t"]['trans'].copy()
trans[0,-1] += 1e-2 # Add translation
info_2["dev_head_t"] = mne.transforms.Transform(1, 4, trans=trans)
fwd_2 = mne.make_forward_solution(info_2, fwd["mri_head_t"], fwd["src"], bem)

# Plot forward solutions
fig_1 = mne.viz.plot_alignment(trans=fwd_1["mri_head_t"], info=fwd_1["info"], subject="sample", subjects_dir=subjects_dir)
fig_2 = mne.viz.plot_alignment(trans=fwd_2["mri_head_t"], info=fwd_2["info"], subject="sample", subjects_dir=subjects_dir)

view = {'azimuth': 90.0, 'elevation': 90.0, 'focalpoint': 'auto', 'distance': 'auto'}

fig, axes = plt.subplots(1, 2, figsize=(10, 5))
for f,ax in zip([fig_1, fig_2], axes):
    set_3d_view(f, **view)
    im = backend._take_3d_screenshot(figure=f)
    ax.imshow(im)

```

 ![image](https://global.discourse-cdn.com/free1/uploads/mne/original/2X/2/2c2219b198e11c0acdf05cb5028f0f365d504392.jpeg)

```python
# plot the effect on source time course
noise_cov = mne.make_ad_hoc_cov(info_1)
inverse_1 = mne.minimum_norm.make_inverse_operator(
    info_1, fwd_1, noise_cov=noise_cov, loose="auto", depth=None, fixed=True
    )

stc_1 = mne.minimum_norm.apply_inverse_raw(raw, inverse_1, lambda2=1.0 / 9.0)

inverse_2 = mne.minimum_norm.make_inverse_operator(
    info_1, fwd_2, noise_cov=noise_cov, loose="auto", depth=None, fixed=True
    )

stc_2 = mne.minimum_norm.apply_inverse_raw(raw, inverse_2, lambda2=1.0 / 9.0)

correlations = []
for i in range(0, stc_1.data.shape[0]):
  c = np.corrcoef(stc_1.data[i], stc_2.data[i])
  correlations.append(c[0,1])

plt.figure()
sns.displot(correlations)
plt.title("Correlation between source time courses")
plt.xlabel("Correlation")
plt.ylabel("Count (sources)")

```

 ![image](https://global.discourse-cdn.com/free1/uploads/mne/original/2X/2/25deb629a90728567bf353602e080064d3bda581.png)

---

<div class="post-metadata">

**Author:** ![mscheltienne](https://yyz2.discourse-cdn.com/free1/user_avatar/mne.discourse.group/mscheltienne/32/827_2.png) [@mscheltienne](https://mne.discourse.group/u/mscheltienne)\
**Post date:** [April 17, 2025, 6:21pm UTC](https://mne.discourse.group/t/dev-head-t-and-forward-model-computation-s/11108/4 "2025-04-17T18:21:53Z")

</div>

@larsoner Maybe you want to chime in on this part?

> That said, I’ve been wondering how well the Maxwell filter handles projecting data to a different head position. I imagine the effectiveness depends on how far the original positions are from the target destination. In more extreme cases, this projection might not be sufficient — and using separate destinations (and thus separate forward solutions) per recording might yield better results. I’m not sure if there are any quantitative analyses on this topic — would be curious to hear if someone come across any.

---

<div class="post-metadata">

**Author:** ![larsoner](https://yyz2.discourse-cdn.com/free1/user_avatar/mne.discourse.group/larsoner/32/3_2.png) [@larsoner](https://mne.discourse.group/u/larsoner)\
**Post date:** [April 18, 2025, 5:07pm UTC](https://mne.discourse.group/t/dev-head-t-and-forward-model-computation-s/11108/5 "2025-04-18T17:07:21Z")

</div>

I haven’t read the whole thread but yes SNR changes when you move to a new virtual sensor / head position. When you move far the SNR generally suffers. Using different forward solutions could potentially help. I vaguely recall a poster from HBM or Biomag many years ago (5+?) where someone looked at computing a time-varying forward solution rather than using virtual sensors (kind of a more extreme / time-precise version of what you’re suggesting with the per-run forward) … but don’t remember how much it improved things 🤦 But I imagine it probably became a paper at some point…
