Skip to content

Commit f4d879d

Browse files
committed
work on dipolefitui
1 parent a92aead commit f4d879d

4 files changed

Lines changed: 136 additions & 107 deletions

File tree

examples/inverse/multi_dipole_model.py

Lines changed: 37 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,38 @@
11
"""
22
.. _ex-multi-dipole:
33
4-
=================================================================
5-
Computing source timecourses with an XFit-like multi-dipole model
6-
=================================================================
7-
8-
MEGIN's XFit program offers a "guided ECD modeling" interface, where multiple
9-
dipoles can be fitted interactively. By manually selecting subsets of sensors
10-
and time ranges, dipoles can be fitted to specific signal components. Then,
11-
source timecourses can be computed using a multi-dipole model. The advantage of
12-
using a multi-dipole model over fitting each dipole in isolation, is that when
13-
multiple dipoles contribute to the same signal component, the model can make
14-
sure that activity assigned to one dipole is not also assigned to another. This
15-
example shows how to build a multi-dipole model for estimating source
16-
timecourses for evokeds or single epochs.
17-
18-
The XFit program is the recommended approach for guided ECD modeling, because
19-
it offers a convenient graphical user interface for it. These dipoles can then
20-
be imported into MNE-Python by using the :func:`mne.read_dipole` function for
21-
building and applying the multi-dipole model. In addition, this example will
22-
also demonstrate how to perform guided ECD modeling using only MNE-Python
23-
functionality, which is less convenient than using XFit, but has the benefit of
24-
being reproducible.
4+
======================
5+
Guided dipole modeling
6+
======================
7+
8+
Inspired by MEGIN's XFit program, MNE-Python offers a guided dipole modeling interface.
9+
Through this interface, you can fit dipoles at specific timings using selected subsets
10+
of sensors to gradually build up a multi dipole source estimate. This is the manual
11+
alternative to using automated algorithms such as TRAP-MUSIC and MxNE.
2512
"""
2613
# Author: Marijn van Vliet <w.m.vanvliet@gmail.com>
2714
#
2815
# License: BSD-3-Clause
2916
# Copyright the MNE-Python contributors.
3017

31-
###############################################################################
32-
# Importing everything and setting up the data paths for the MNE-Sample
33-
# dataset.
18+
########################################################################################
19+
# Read the MEG data from the audvis experiment. Make epochs for the left auditory
20+
# condition.
3421
import matplotlib.pyplot as plt
3522
import numpy as np
3623

3724
import mne
38-
from mne.channels import read_vectorview_selection
3925
from mne.datasets import sample
40-
from mne.minimum_norm import apply_inverse, apply_inverse_epochs, make_inverse_operator
4126

4227
data_path = sample.data_path()
4328
meg_path = data_path / "MEG" / "sample"
4429
raw_fname = meg_path / "sample_audvis_raw.fif"
45-
cov_fname = meg_path / "sample_audvis-shrunk-cov.fif"
46-
bem_dir = data_path / "subjects" / "sample" / "bem"
47-
bem_fname = bem_dir / "sample-5120-5120-5120-bem-sol.fif"
48-
49-
###############################################################################
50-
# Read the MEG data from the audvis experiment. Make epochs and evokeds for the
51-
# left and right auditory conditions.
5230
raw = mne.io.read_raw_fif(raw_fname)
53-
raw = raw.pick(picks=["meg", "eog", "stim"])
5431
info = raw.info
5532

5633
# Create epochs for auditory events
5734
events = mne.find_events(raw)
58-
event_id = dict(right=1, left=2)
35+
event_id = dict(left=2)
5936
epochs = mne.Epochs(
6037
raw,
6138
events,
@@ -67,18 +44,36 @@
6744
)
6845

6946
# Create evokeds for left and right auditory stimulation
70-
evoked_left = epochs["left"].average()
71-
evoked_right = epochs["right"].average()
47+
evoked = epochs["left"].average()
48+
49+
########################################################################################
50+
# Guided dipole modeling, meaning fitting dipoles to a manually selected subset of
51+
# sensors as a manually chosen time, can now be performed using the
52+
# :class:`mne.gui.DipoleFitting` GUI. By specifying only ``evokeds``, a default noise
53+
# covariance matrix and 1-layer spherical head model will be used.
54+
mne.gui.dipolefit(evoked)
7255

7356
###############################################################################
74-
# Guided dipole modeling, meaning fitting dipoles to a manually selected subset
75-
# of sensors as a manually chosen time, can now be performed in MEGINs XFit on
76-
# the evokeds we computed above. However, it is possible to do it completely
77-
# in MNE-Python.
57+
# The source modeling can be made more precise by using a noise covariance matrix and
58+
# MRI-based 3-layer BEM head model:
59+
cov_fname = meg_path / "sample_audvis-shrunk-cov.fif"
60+
subjects_dir = data_path / "subjects"
61+
bem_dir = subjects_dir / "sample" / "bem"
62+
bem_fname = bem_dir / "sample-5120-5120-5120-bem-sol.fif"
63+
trans_fname = meg_path / "sample_audvis_raw-trans.fif"
7864

79-
# Setup conductor model
8065
cov = mne.read_cov(cov_fname) # bad channels were already excluded here
8166
bem = mne.read_bem_solution(bem_fname)
67+
trans = mne.read_trans(trans_fname)
68+
69+
mne.gui.dipolefit(
70+
evoked,
71+
cov=cov.as_diag(),
72+
bem=bem,
73+
trans=trans,
74+
subject="sample",
75+
subjects_dir=subjects_dir,
76+
)
8277

8378
# Fit two dipoles at t=80ms. The first dipole is fitted using only the sensors
8479
# on the left side of the helmet. The second dipole is fitted using only the

mne/gui/__init__.pyi

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
1-
__all__ = ["_GUIScraper", "coregistration", "DipoleFitUI"]
2-
from ._gui import _GUIScraper, coregistration
3-
from ._xfit import DipoleFitUI
1+
__all__ = ["_GUIScraper", "coregistration", "dipolefit"]
2+
from ._gui import _GUIScraper, coregistration, dipolefit

mne/gui/_gui.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,91 @@ def coregistration(
165165
)
166166

167167

168+
@verbose
169+
def dipolefit(
170+
evoked,
171+
cov=None,
172+
bem=None,
173+
initial_time=None,
174+
trans=None,
175+
stc=None,
176+
subject=None,
177+
subjects_dir=None,
178+
rank="info",
179+
show_density=True,
180+
ch_type=None,
181+
n_jobs=None,
182+
show=True,
183+
block=False,
184+
verbose=None,
185+
):
186+
"""GUI for interactive dipole fitting, inspired by MEGIN's XFit program.
187+
188+
Parameters
189+
----------
190+
evoked : instance of Evoked
191+
Evoked data to show fieldmap of and fit dipoles to.
192+
cov : instance of Covariance | "baseline" | None
193+
Noise covariance matrix. If ``None``, an ad-hoc covariance matrix is used with
194+
default values for the diagonal elements (see Notes). If ``"baseline"``, the
195+
diagonal elements is estimated from the baseline period of the evoked data.
196+
bem : instance of ConductorModel | None
197+
Boundary element model to use in forward calculations. If ``None``, a spherical
198+
model is used.
199+
initial_time : float | None
200+
Initial time point to show. If ``None``, the time point of the maximum field
201+
strength is used.
202+
trans : instance of Transform | None
203+
The transformation from head coordinates to MRI coordinates. If ``None``,
204+
the identity matrix is used and everything will be done in head coordinates.
205+
stc : instance of SourceEstimate | None
206+
An optional distributed source estimate to show alongside the fieldmap. The time
207+
samples need to match those of the evoked data.
208+
subject : str | None
209+
The subject name. If ``None``, no MRI data is shown.
210+
%(subjects_dir)s
211+
%(rank)s
212+
show_density : bool
213+
Whether to show the density of the fieldmap.
214+
ch_type : "meg" | "eeg" | None
215+
Type of channels to use for the dipole fitting. By default (``None``) both MEG
216+
and EEG channels will be used.
217+
%(n_jobs)s
218+
show : bool
219+
Show the GUI if True.
220+
block : bool
221+
Whether to halt program execution until the figure is closed.
222+
%(verbose)s
223+
224+
Notes
225+
-----
226+
When using ``cov=None`` the default noise values are 5 fT/cm, 20 fT, and 0.2 µV for
227+
gradiometers, magnetometers, and EEG channels respectively.
228+
"""
229+
from ..viz.backends.renderer import MNE_3D_BACKEND_TESTING
230+
from ._xfit import DipoleFitUI
231+
232+
if MNE_3D_BACKEND_TESTING:
233+
show = block = False
234+
return DipoleFitUI(
235+
evoked=evoked,
236+
cov=cov,
237+
bem=bem,
238+
initial_time=initial_time,
239+
trans=trans,
240+
stc=stc,
241+
subject=subject,
242+
subjects_dir=subjects_dir,
243+
rank=rank,
244+
show_density=show_density,
245+
ch_type=ch_type,
246+
n_jobs=n_jobs,
247+
show=show,
248+
block=block,
249+
verbose=verbose,
250+
)
251+
252+
168253
class _GUIScraper:
169254
"""Scrape GUI outputs."""
170255

mne/gui/_xfit.py

Lines changed: 12 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -25,69 +25,11 @@
2525
from ..utils import _check_option, fill_doc, logger, verbose
2626
from ..viz import EvokedField, create_3d_figure
2727
from ..viz._3d import _plot_head_surface, _plot_sensors_3d
28+
from ..viz.backends._utils import _qt_app_exec
2829
from ..viz.ui_events import link, subscribe
2930
from ..viz.utils import _get_color_list
3031

3132

32-
@fill_doc
33-
@verbose
34-
def dipolefit(
35-
evoked,
36-
cov=None,
37-
bem=None,
38-
initial_time=None,
39-
trans=None,
40-
rank=None,
41-
show_density=True,
42-
subject=None,
43-
subjects_dir=None,
44-
n_jobs=None,
45-
verbose=None,
46-
):
47-
"""GUI for interactive dipole fitting, inspired by MEGIN's XFit program.
48-
49-
Parameters
50-
----------
51-
evoked : instance of Evoked
52-
Evoked data to show fieldmap of and fit dipoles to.
53-
cov : instance of Covariance | None
54-
Noise covariance matrix. If ``None``, an ad-hoc covariance matrix is used.
55-
bem : instance of ConductorModel | None
56-
Boundary element model to use in forward calculations. If ``None``, a spherical
57-
model is used.
58-
initial_time : float | None
59-
Initial time point to show. If ``None``, the time point of the maximum field
60-
strength is used.
61-
trans : instance of Transform | None
62-
The transformation from head coordinates to MRI coordinates. If ``None``, the
63-
identity matrix is used.
64-
stc : instance of SourceEstimate | None
65-
An optional distributed source estimate to show alongside the fieldmap.
66-
%(rank)s
67-
show_density : bool
68-
Whether to show the density of the fieldmap.
69-
subject : str | None
70-
The subject name. If ``None``, no MRI data is shown.
71-
%(subjects_dir)s
72-
%(n_jobs)s
73-
%(verbose)s
74-
"""
75-
return DipoleFitUI(
76-
evoked=evoked,
77-
cov=cov,
78-
bem=bem,
79-
initial_time=initial_time,
80-
trans=trans,
81-
stc=None,
82-
rank=rank,
83-
show_density=show_density,
84-
subject=subject,
85-
subjects_dir=subjects_dir,
86-
n_jobs=n_jobs,
87-
verbose=verbose,
88-
)
89-
90-
9133
@fill_doc
9234
class DipoleFitUI:
9335
"""GUI for interactive dipole fitting, inspired by MEGIN's XFit program.
@@ -144,6 +86,8 @@ def __init__(
14486
show_density=True,
14587
ch_type=None,
14688
n_jobs=None,
89+
show=True,
90+
block=False,
14791
verbose=None,
14892
):
14993
if cov is None:
@@ -213,17 +157,23 @@ def __init__(
213157
self._verbose = verbose
214158

215159
# Configure the GUI.
216-
self._renderer = self._configure_main_display()
160+
self._renderer = self._configure_main_display(show)
217161
self._configure_dock()
218162

163+
# must be done last
164+
if show:
165+
self._renderer.show()
166+
if block and self._renderer._kind != "notebook":
167+
_qt_app_exec(self._renderer.figure.store["app"])
168+
219169
@property
220170
def dipoles(self):
221171
"""A list of all the fitted dipoles that are enabled in the GUI."""
222172
return [d["dip"] for d in self._dipoles.values() if d["active"]]
223173

224-
def _configure_main_display(self):
174+
def _configure_main_display(self, show=True):
225175
"""Configure main 3D display of the GUI."""
226-
fig = create_3d_figure((1500, 1020), bgcolor="white", show=True)
176+
fig = create_3d_figure((1500, 1020), bgcolor="white", show=show)
227177

228178
self._fig_stc = None
229179
if self._stc is not None:

0 commit comments

Comments
 (0)