initial commit
This commit is contained in:
503
mne/beamformer/_lcmv.py
Normal file
503
mne/beamformer/_lcmv.py
Normal file
@@ -0,0 +1,503 @@
|
||||
"""Compute Linearly constrained minimum variance (LCMV) beamformer."""
|
||||
|
||||
# Authors: The MNE-Python contributors.
|
||||
# License: BSD-3-Clause
|
||||
# Copyright the MNE-Python contributors.
|
||||
|
||||
import numpy as np
|
||||
|
||||
from .._fiff.meas_info import _simplify_info
|
||||
from .._fiff.pick import pick_channels_cov, pick_info
|
||||
from ..forward import _subject_from_forward
|
||||
from ..minimum_norm.inverse import _check_depth, _check_reference, combine_xyz
|
||||
from ..rank import compute_rank
|
||||
from ..source_estimate import _get_src_type, _make_stc
|
||||
from ..utils import (
|
||||
_check_channels_spatial_filter,
|
||||
_check_info_inv,
|
||||
_check_one_ch_type,
|
||||
logger,
|
||||
verbose,
|
||||
)
|
||||
from ._compute_beamformer import (
|
||||
Beamformer,
|
||||
_check_src_type,
|
||||
_compute_beamformer,
|
||||
_compute_power,
|
||||
_prepare_beamformer_input,
|
||||
_proj_whiten_data,
|
||||
)
|
||||
|
||||
|
||||
@verbose
|
||||
def make_lcmv(
|
||||
info,
|
||||
forward,
|
||||
data_cov,
|
||||
reg=0.05,
|
||||
noise_cov=None,
|
||||
label=None,
|
||||
pick_ori=None,
|
||||
rank="info",
|
||||
weight_norm="unit-noise-gain-invariant",
|
||||
reduce_rank=False,
|
||||
depth=None,
|
||||
inversion="matrix",
|
||||
verbose=None,
|
||||
):
|
||||
"""Compute LCMV spatial filter.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
%(info_not_none)s
|
||||
Specifies the channels to include. Bad channels (in ``info['bads']``)
|
||||
are not used.
|
||||
forward : instance of Forward
|
||||
Forward operator.
|
||||
data_cov : instance of Covariance
|
||||
The data covariance.
|
||||
reg : float
|
||||
The regularization for the whitened data covariance.
|
||||
noise_cov : instance of Covariance
|
||||
The noise covariance. If provided, whitening will be done. Providing a
|
||||
noise covariance is mandatory if you mix sensor types, e.g.
|
||||
gradiometers with magnetometers or EEG with MEG.
|
||||
|
||||
.. note::
|
||||
If ``noise_cov`` is ``None`` and ``weight_norm='unit-noise-gain'``,
|
||||
the unit noise is assumed to be 1 in SI units, e.g., 1 T for
|
||||
magnetometers, 1 V for EEG, so resulting amplitudes will be tiny.
|
||||
Consider using :func:`mne.make_ad_hoc_cov` to provide a
|
||||
``noise_cov`` to set noise values that are more reasonable for
|
||||
neural data or using ``weight_norm='nai'`` for weight-normalized
|
||||
beamformer output that is scaled by a noise estimate.
|
||||
label : instance of Label
|
||||
Restricts the LCMV solution to a given label.
|
||||
%(pick_ori_bf)s
|
||||
|
||||
- ``'vector'``
|
||||
Keeps the currents for each direction separate
|
||||
%(rank_info)s
|
||||
%(weight_norm)s
|
||||
|
||||
Defaults to ``'unit-noise-gain-invariant'``.
|
||||
%(reduce_rank)s
|
||||
%(depth)s
|
||||
|
||||
.. versionadded:: 0.18
|
||||
%(inversion_bf)s
|
||||
|
||||
.. versionadded:: 0.21
|
||||
%(verbose)s
|
||||
|
||||
Returns
|
||||
-------
|
||||
filters : instance of Beamformer
|
||||
Dictionary containing filter weights from LCMV beamformer.
|
||||
Contains the following keys:
|
||||
|
||||
'kind' : str
|
||||
The type of beamformer, in this case 'LCMV'.
|
||||
'weights' : array
|
||||
The filter weights of the beamformer.
|
||||
'data_cov' : instance of Covariance
|
||||
The data covariance matrix used to compute the beamformer.
|
||||
'noise_cov' : instance of Covariance | None
|
||||
The noise covariance matrix used to compute the beamformer.
|
||||
'whitener' : None | ndarray, shape (n_channels, n_channels)
|
||||
Whitening matrix, provided if whitening was applied to the
|
||||
covariance matrix and leadfield during computation of the
|
||||
beamformer weights.
|
||||
'weight_norm' : str | None
|
||||
Type of weight normalization used to compute the filter
|
||||
weights.
|
||||
'pick-ori' : None | 'max-power' | 'normal' | 'vector'
|
||||
The orientation in which the beamformer filters were computed.
|
||||
'ch_names' : list of str
|
||||
Channels used to compute the beamformer.
|
||||
'proj' : array
|
||||
Projections used to compute the beamformer.
|
||||
'is_ssp' : bool
|
||||
If True, projections were applied prior to filter computation.
|
||||
'vertices' : list
|
||||
Vertices for which the filter weights were computed.
|
||||
'is_free_ori' : bool
|
||||
If True, the filter was computed with free source orientation.
|
||||
'n_sources' : int
|
||||
Number of source location for which the filter weight were
|
||||
computed.
|
||||
'src_type' : str
|
||||
Type of source space.
|
||||
'source_nn' : ndarray, shape (n_sources, 3)
|
||||
For each source location, the surface normal.
|
||||
'proj' : ndarray, shape (n_channels, n_channels)
|
||||
Projections used to compute the beamformer.
|
||||
'subject' : str
|
||||
The subject ID.
|
||||
'rank' : int
|
||||
The rank of the data covariance matrix used to compute the
|
||||
beamformer weights.
|
||||
'max-power-ori' : ndarray, shape (n_sources, 3) | None
|
||||
When pick_ori='max-power', this fields contains the estimated
|
||||
direction of maximum power at each source location.
|
||||
'inversion' : 'single' | 'matrix'
|
||||
Whether the spatial filters were computed for each dipole
|
||||
separately or jointly for all dipoles at each vertex using a
|
||||
matrix inversion.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The original reference is :footcite:`VanVeenEtAl1997`.
|
||||
|
||||
To obtain the Sekihara unit-noise-gain vector beamformer, you should use
|
||||
``weight_norm='unit-noise-gain', pick_ori='vector'`` followed by
|
||||
:meth:`vec_stc.project('pca', src) <mne.VectorSourceEstimate.project>`.
|
||||
|
||||
.. versionchanged:: 0.21
|
||||
The computations were extensively reworked, and the default for
|
||||
``weight_norm`` was set to ``'unit-noise-gain-invariant'``.
|
||||
|
||||
References
|
||||
----------
|
||||
.. footbibliography::
|
||||
"""
|
||||
# check number of sensor types present in the data and ensure a noise cov
|
||||
info = _simplify_info(info, keep=("proc_history",))
|
||||
noise_cov, _, allow_mismatch = _check_one_ch_type(
|
||||
"lcmv", info, forward, data_cov, noise_cov
|
||||
)
|
||||
# XXX we need this extra picking step (can't just rely on minimum norm's
|
||||
# because there can be a mismatch. Should probably add an extra arg to
|
||||
# _prepare_beamformer_input at some point (later)
|
||||
picks = _check_info_inv(info, forward, data_cov, noise_cov)
|
||||
info = pick_info(info, picks)
|
||||
data_rank = compute_rank(data_cov, rank=rank, info=info)
|
||||
noise_rank = compute_rank(noise_cov, rank=rank, info=info)
|
||||
for key in data_rank:
|
||||
if (
|
||||
key not in noise_rank or data_rank[key] != noise_rank[key]
|
||||
) and not allow_mismatch:
|
||||
raise ValueError(
|
||||
f"{key} data rank ({data_rank[key]}) did not match the noise rank ("
|
||||
f"{noise_rank.get(key, None)})"
|
||||
)
|
||||
del noise_rank
|
||||
rank = data_rank
|
||||
logger.info(f"Making LCMV beamformer with rank {rank}")
|
||||
del data_rank
|
||||
depth = _check_depth(depth, "depth_sparse")
|
||||
if inversion == "single":
|
||||
depth["combine_xyz"] = False
|
||||
|
||||
(
|
||||
is_free_ori,
|
||||
info,
|
||||
proj,
|
||||
vertno,
|
||||
G,
|
||||
whitener,
|
||||
nn,
|
||||
orient_std,
|
||||
) = _prepare_beamformer_input(
|
||||
info,
|
||||
forward,
|
||||
label,
|
||||
pick_ori,
|
||||
noise_cov=noise_cov,
|
||||
rank=rank,
|
||||
pca=False,
|
||||
**depth,
|
||||
)
|
||||
ch_names = list(info["ch_names"])
|
||||
|
||||
data_cov = pick_channels_cov(data_cov, include=ch_names)
|
||||
Cm = data_cov._get_square()
|
||||
if "estimator" in data_cov:
|
||||
del data_cov["estimator"]
|
||||
rank_int = sum(rank.values())
|
||||
del rank
|
||||
|
||||
# compute spatial filter
|
||||
n_orient = 3 if is_free_ori else 1
|
||||
W, max_power_ori = _compute_beamformer(
|
||||
G,
|
||||
Cm,
|
||||
reg,
|
||||
n_orient,
|
||||
weight_norm,
|
||||
pick_ori,
|
||||
reduce_rank,
|
||||
rank_int,
|
||||
inversion=inversion,
|
||||
nn=nn,
|
||||
orient_std=orient_std,
|
||||
whitener=whitener,
|
||||
)
|
||||
|
||||
# get src type to store with filters for _make_stc
|
||||
src_type = _get_src_type(forward["src"], vertno)
|
||||
|
||||
# get subject to store with filters
|
||||
subject_from = _subject_from_forward(forward)
|
||||
|
||||
# Is the computed beamformer a scalar or vector beamformer?
|
||||
is_free_ori = is_free_ori if pick_ori in [None, "vector"] else False
|
||||
is_ssp = bool(info["projs"])
|
||||
|
||||
filters = Beamformer(
|
||||
kind="LCMV",
|
||||
weights=W,
|
||||
data_cov=data_cov,
|
||||
noise_cov=noise_cov,
|
||||
whitener=whitener,
|
||||
weight_norm=weight_norm,
|
||||
pick_ori=pick_ori,
|
||||
ch_names=ch_names,
|
||||
proj=proj,
|
||||
is_ssp=is_ssp,
|
||||
vertices=vertno,
|
||||
is_free_ori=is_free_ori,
|
||||
n_sources=forward["nsource"],
|
||||
src_type=src_type,
|
||||
source_nn=forward["source_nn"].copy(),
|
||||
subject=subject_from,
|
||||
rank=rank_int,
|
||||
max_power_ori=max_power_ori,
|
||||
inversion=inversion,
|
||||
)
|
||||
|
||||
return filters
|
||||
|
||||
|
||||
def _apply_lcmv(data, filters, info, tmin):
|
||||
"""Apply LCMV spatial filter to data for source reconstruction."""
|
||||
if isinstance(data, np.ndarray) and data.ndim == 2:
|
||||
data = [data]
|
||||
return_single = True
|
||||
else:
|
||||
return_single = False
|
||||
|
||||
W = filters["weights"]
|
||||
|
||||
for i, M in enumerate(data):
|
||||
if len(M) != len(filters["ch_names"]):
|
||||
raise ValueError("data and picks must have the same length")
|
||||
|
||||
if not return_single:
|
||||
logger.info(f"Processing epoch : {i + 1}")
|
||||
|
||||
M = _proj_whiten_data(M, info["projs"], filters)
|
||||
|
||||
# project to source space using beamformer weights
|
||||
vector = False
|
||||
if filters["is_free_ori"]:
|
||||
sol = np.dot(W, M)
|
||||
if filters["pick_ori"] == "vector":
|
||||
vector = True
|
||||
else:
|
||||
logger.info("combining the current components...")
|
||||
sol = combine_xyz(sol)
|
||||
else:
|
||||
# Linear inverse: do computation here or delayed
|
||||
if M.shape[0] < W.shape[0] and filters["pick_ori"] != "max-power":
|
||||
sol = (W, M)
|
||||
else:
|
||||
sol = np.dot(W, M)
|
||||
|
||||
tstep = 1.0 / info["sfreq"]
|
||||
|
||||
# compatibility with 0.16, add src_type as None if not present:
|
||||
filters, warn_text = _check_src_type(filters)
|
||||
|
||||
yield _make_stc(
|
||||
sol,
|
||||
vertices=filters["vertices"],
|
||||
tmin=tmin,
|
||||
tstep=tstep,
|
||||
subject=filters["subject"],
|
||||
vector=vector,
|
||||
source_nn=filters["source_nn"],
|
||||
src_type=filters["src_type"],
|
||||
warn_text=warn_text,
|
||||
)
|
||||
|
||||
logger.info("[done]")
|
||||
|
||||
|
||||
@verbose
|
||||
def apply_lcmv(evoked, filters, *, verbose=None):
|
||||
"""Apply Linearly Constrained Minimum Variance (LCMV) beamformer weights.
|
||||
|
||||
Apply Linearly Constrained Minimum Variance (LCMV) beamformer weights
|
||||
on evoked data.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
evoked : Evoked
|
||||
Evoked data to invert.
|
||||
filters : instance of Beamformer
|
||||
LCMV spatial filter (beamformer weights).
|
||||
Filter weights returned from :func:`make_lcmv`.
|
||||
%(verbose)s
|
||||
|
||||
Returns
|
||||
-------
|
||||
stc : SourceEstimate | VolSourceEstimate | VectorSourceEstimate
|
||||
Source time courses.
|
||||
|
||||
See Also
|
||||
--------
|
||||
make_lcmv, apply_lcmv_raw, apply_lcmv_epochs, apply_lcmv_cov
|
||||
|
||||
Notes
|
||||
-----
|
||||
.. versionadded:: 0.18
|
||||
"""
|
||||
_check_reference(evoked)
|
||||
|
||||
info = evoked.info
|
||||
data = evoked.data
|
||||
tmin = evoked.times[0]
|
||||
|
||||
sel = _check_channels_spatial_filter(evoked.ch_names, filters)
|
||||
data = data[sel]
|
||||
|
||||
stc = _apply_lcmv(data=data, filters=filters, info=info, tmin=tmin)
|
||||
|
||||
return next(stc)
|
||||
|
||||
|
||||
@verbose
|
||||
def apply_lcmv_epochs(epochs, filters, *, return_generator=False, verbose=None):
|
||||
"""Apply Linearly Constrained Minimum Variance (LCMV) beamformer weights.
|
||||
|
||||
Apply Linearly Constrained Minimum Variance (LCMV) beamformer weights
|
||||
on single trial data.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
epochs : Epochs
|
||||
Single trial epochs.
|
||||
filters : instance of Beamformer
|
||||
LCMV spatial filter (beamformer weights)
|
||||
Filter weights returned from :func:`make_lcmv`.
|
||||
return_generator : bool
|
||||
Return a generator object instead of a list. This allows iterating
|
||||
over the stcs without having to keep them all in memory.
|
||||
%(verbose)s
|
||||
|
||||
Returns
|
||||
-------
|
||||
stc: list | generator of (SourceEstimate | VolSourceEstimate)
|
||||
The source estimates for all epochs.
|
||||
|
||||
See Also
|
||||
--------
|
||||
make_lcmv, apply_lcmv_raw, apply_lcmv, apply_lcmv_cov
|
||||
"""
|
||||
_check_reference(epochs)
|
||||
|
||||
info = epochs.info
|
||||
tmin = epochs.times[0]
|
||||
|
||||
sel = _check_channels_spatial_filter(epochs.ch_names, filters)
|
||||
data = epochs.get_data(sel)
|
||||
stcs = _apply_lcmv(data=data, filters=filters, info=info, tmin=tmin)
|
||||
|
||||
if not return_generator:
|
||||
stcs = [s for s in stcs]
|
||||
|
||||
return stcs
|
||||
|
||||
|
||||
@verbose
|
||||
def apply_lcmv_raw(raw, filters, start=None, stop=None, *, verbose=None):
|
||||
"""Apply Linearly Constrained Minimum Variance (LCMV) beamformer weights.
|
||||
|
||||
Apply Linearly Constrained Minimum Variance (LCMV) beamformer weights
|
||||
on raw data.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
raw : mne.io.Raw
|
||||
Raw data to invert.
|
||||
filters : instance of Beamformer
|
||||
LCMV spatial filter (beamformer weights).
|
||||
Filter weights returned from :func:`make_lcmv`.
|
||||
start : int
|
||||
Index of first time sample (index not time is seconds).
|
||||
stop : int
|
||||
Index of first time sample not to include (index not time is seconds).
|
||||
%(verbose)s
|
||||
|
||||
Returns
|
||||
-------
|
||||
stc : SourceEstimate | VolSourceEstimate
|
||||
Source time courses.
|
||||
|
||||
See Also
|
||||
--------
|
||||
make_lcmv, apply_lcmv_epochs, apply_lcmv, apply_lcmv_cov
|
||||
"""
|
||||
_check_reference(raw)
|
||||
|
||||
info = raw.info
|
||||
|
||||
sel = _check_channels_spatial_filter(raw.ch_names, filters)
|
||||
data, times = raw[sel, start:stop]
|
||||
tmin = times[0]
|
||||
|
||||
stc = _apply_lcmv(data=data, filters=filters, info=info, tmin=tmin)
|
||||
|
||||
return next(stc)
|
||||
|
||||
|
||||
@verbose
|
||||
def apply_lcmv_cov(data_cov, filters, verbose=None):
|
||||
"""Apply Linearly Constrained Minimum Variance (LCMV) beamformer weights.
|
||||
|
||||
Apply Linearly Constrained Minimum Variance (LCMV) beamformer weights
|
||||
to a data covariance matrix to estimate source power.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data_cov : instance of Covariance
|
||||
Data covariance matrix.
|
||||
filters : instance of Beamformer
|
||||
LCMV spatial filter (beamformer weights).
|
||||
Filter weights returned from :func:`make_lcmv`.
|
||||
%(verbose)s
|
||||
|
||||
Returns
|
||||
-------
|
||||
stc : SourceEstimate | VolSourceEstimate
|
||||
Source power.
|
||||
|
||||
See Also
|
||||
--------
|
||||
make_lcmv, apply_lcmv, apply_lcmv_epochs, apply_lcmv_raw
|
||||
"""
|
||||
sel = _check_channels_spatial_filter(data_cov.ch_names, filters)
|
||||
sel_names = [data_cov.ch_names[ii] for ii in sel]
|
||||
data_cov = pick_channels_cov(data_cov, sel_names)
|
||||
|
||||
n_orient = filters["weights"].shape[0] // filters["n_sources"]
|
||||
# Need to project and whiten along both dimensions
|
||||
data = _proj_whiten_data(data_cov["data"].T, data_cov["projs"], filters)
|
||||
data = _proj_whiten_data(data.T, data_cov["projs"], filters)
|
||||
del data_cov
|
||||
source_power = _compute_power(data, filters["weights"], n_orient)
|
||||
|
||||
# compatibility with 0.16, add src_type as None if not present:
|
||||
filters, warn_text = _check_src_type(filters)
|
||||
|
||||
return _make_stc(
|
||||
source_power,
|
||||
vertices=filters["vertices"],
|
||||
src_type=filters["src_type"],
|
||||
tmin=0.0,
|
||||
tstep=1.0,
|
||||
subject=filters["subject"],
|
||||
source_nn=filters["source_nn"],
|
||||
warn_text=warn_text,
|
||||
)
|
||||
Reference in New Issue
Block a user