Skip to content

Deblurred Flexible-Extent ESMV

Solver ID: DEBLUR_FLEX_ESMV

Usage

from invert import Solver

# fwd = ...    (mne.Forward object)
# evoked = ... (mne.Evoked object)

solver = Solver("DEBLUR_FLEX_ESMV")
solver.make_inverse_operator(fwd)
stc = solver.apply_inverse_operator(evoked)
stc.plot()

Overview

Flex-extent ESMV variant with a graph-based unsharp-mask deblurring postprocess to reduce spatial dispersion/leakage.

References

  1. Lukas Hecker (2025). Unpublished.
  2. Jonmohamadi, Y., Poudel, G., Innes, C., Weiss, D., Krueger, R., & Jones, R. (2014). Comparison of beamformers for EEG source signal reconstruction. Biomedical Signal Processing and Control, 14, 175-188.

API Reference

Bases: BaseSolver

FlexESMV3: FlexESMV2 + graph unsharp-mask deblurring.

FlexESMV2 is very strong on MLE/EMD/AP but tends to over-blur in the FWHM-based spatial dispersion metric. This variant applies a light, graph-based unsharp mask to reduce leakage/blur while keeping the time courses from FlexESMV2.

Source code in invert/solvers/beamformers/deblur_flex_esmv.py
class SolverDeblurFlexESMV(BaseSolver):
    """FlexESMV3: FlexESMV2 + graph unsharp-mask deblurring.

    FlexESMV2 is very strong on MLE/EMD/AP but tends to over-blur in the
    FWHM-based spatial dispersion metric. This variant applies a light,
    graph-based unsharp mask to reduce leakage/blur while keeping the time
    courses from FlexESMV2.
    """

    meta = SolverMeta(
        slug="deblur_flex_esmv",
        full_name="Deblurred Flexible-Extent ESMV",
        category="Beamformers",
        description=(
            "Flex-extent ESMV variant with a graph-based unsharp-mask deblurring "
            "postprocess to reduce spatial dispersion/leakage."
        ),
        references=[
            "Lukas Hecker (2025). Unpublished.",
            "Jonmohamadi, Y., Poudel, G., Innes, C., Weiss, D., Krueger, R., & Jones, R. "
            "(2014). Comparison of beamformers for EEG source signal reconstruction. "
            "Biomedical Signal Processing and Control, 14, 175-188.",
        ],
    )

    def __init__(
        self,
        name: str = "DeblurFlexESMV (FlexESMV2+GraphSharp) Beamformer",
        params: _FlexSharpParams | None = None,
        reduce_rank: bool = True,
        rank: str | int = "auto",
        **kwargs,
    ):
        if params is None:
            params = _FlexSharpParams()
        self.name = name
        self._fs = params
        self._inner_kwargs = dict(kwargs)
        self._inner_kwargs.pop("verbose", None)
        return super().__init__(reduce_rank=reduce_rank, rank=rank, **kwargs)

    def make_inverse_operator(self, forward, mne_obj, *args, alpha="auto", **kwargs):
        super().make_inverse_operator(forward, *args, alpha=alpha, **kwargs)
        _ = self.unpack_data_obj(mne_obj)
        adjacency = mne.spatial_src_adjacency(self.forward["src"], verbose=0)
        self._src_laplacian = sparse_laplacian(adjacency, normed=False).tocsc()
        self._src_n_dipoles = int(self._src_laplacian.shape[0])
        self.inverse_operators = []
        return self

    def apply_inverse_operator(self, mne_obj):  # type: ignore[override]
        # Base solution
        base = SolverFlexESMV2(
            reduce_rank=self.reduce_rank,
            rank=self.rank,
            verbose=self.verbose,
            **self._inner_kwargs,
        )
        base.make_inverse_operator(self.forward, mne_obj, alpha="auto")
        stc = base.apply_inverse_operator(mne_obj)

        y = stc.data
        if y.ndim != 2 or y.size == 0:
            return stc

        fs = self._fs
        p = np.mean(np.abs(y), axis=1)
        pmax = float(np.max(p)) + fs.eps
        patchiness = float(np.mean((p / pmax) > fs.patchiness_threshold))
        tau = fs.tau_max * float(np.exp(-patchiness / fs.tau_fscale))

        n_dipoles = y.shape[0]
        if (
            hasattr(self, "_src_laplacian")
            and getattr(self, "_src_n_dipoles", n_dipoles) == n_dipoles
        ):
            A = (
                sparse_identity(n_dipoles, format="csc")
                + fs.smooth_alpha * self._src_laplacian
            )
            try:
                lu = splu(A)
                p_smooth = lu.solve(p)
                p_sharp = (1.0 + tau) * p - tau * p_smooth
                p_sharp = np.clip(p_sharp, 0.0, None)
                scale = np.clip(p_sharp / (p + fs.eps), 0.0, fs.max_scale)
                stc.data[:] = y * scale[:, None]
            except Exception as e:
                logger.debug("DeblurFlexESMV graph-sharpen failed, skipping: %s", e)

        return stc

__init__

__init__(
    name: str = "DeblurFlexESMV (FlexESMV2+GraphSharp) Beamformer",
    params: _FlexSharpParams | None = None,
    reduce_rank: bool = True,
    rank: str | int = "auto",
    **kwargs,
)
Source code in invert/solvers/beamformers/deblur_flex_esmv.py
def __init__(
    self,
    name: str = "DeblurFlexESMV (FlexESMV2+GraphSharp) Beamformer",
    params: _FlexSharpParams | None = None,
    reduce_rank: bool = True,
    rank: str | int = "auto",
    **kwargs,
):
    if params is None:
        params = _FlexSharpParams()
    self.name = name
    self._fs = params
    self._inner_kwargs = dict(kwargs)
    self._inner_kwargs.pop("verbose", None)
    return super().__init__(reduce_rank=reduce_rank, rank=rank, **kwargs)

make_inverse_operator

make_inverse_operator(
    forward, mne_obj, *args, alpha="auto", **kwargs
)
Source code in invert/solvers/beamformers/deblur_flex_esmv.py
def make_inverse_operator(self, forward, mne_obj, *args, alpha="auto", **kwargs):
    super().make_inverse_operator(forward, *args, alpha=alpha, **kwargs)
    _ = self.unpack_data_obj(mne_obj)
    adjacency = mne.spatial_src_adjacency(self.forward["src"], verbose=0)
    self._src_laplacian = sparse_laplacian(adjacency, normed=False).tocsc()
    self._src_n_dipoles = int(self._src_laplacian.shape[0])
    self.inverse_operators = []
    return self

apply_inverse_operator

apply_inverse_operator(mne_obj)
Source code in invert/solvers/beamformers/deblur_flex_esmv.py
def apply_inverse_operator(self, mne_obj):  # type: ignore[override]
    # Base solution
    base = SolverFlexESMV2(
        reduce_rank=self.reduce_rank,
        rank=self.rank,
        verbose=self.verbose,
        **self._inner_kwargs,
    )
    base.make_inverse_operator(self.forward, mne_obj, alpha="auto")
    stc = base.apply_inverse_operator(mne_obj)

    y = stc.data
    if y.ndim != 2 or y.size == 0:
        return stc

    fs = self._fs
    p = np.mean(np.abs(y), axis=1)
    pmax = float(np.max(p)) + fs.eps
    patchiness = float(np.mean((p / pmax) > fs.patchiness_threshold))
    tau = fs.tau_max * float(np.exp(-patchiness / fs.tau_fscale))

    n_dipoles = y.shape[0]
    if (
        hasattr(self, "_src_laplacian")
        and getattr(self, "_src_n_dipoles", n_dipoles) == n_dipoles
    ):
        A = (
            sparse_identity(n_dipoles, format="csc")
            + fs.smooth_alpha * self._src_laplacian
        )
        try:
            lu = splu(A)
            p_smooth = lu.solve(p)
            p_sharp = (1.0 + tau) * p - tau * p_smooth
            p_sharp = np.clip(p_sharp, 0.0, None)
            scale = np.clip(p_sharp / (p + fs.eps), 0.0, fs.max_scale)
            stc.data[:] = y * scale[:, None]
        except Exception as e:
            logger.debug("DeblurFlexESMV graph-sharpen failed, skipping: %s", e)

    return stc