Skip to content

ROIResult

simnibs_reader.efield.roi.ROIResult

E-field values extracted within a region of interest.

This is the main object returned by EFieldAccessor.get_roi(...). It holds the extracted values and provides methods for statistics, post-processing, and export.

Calling .postprocess() returns a new ROIResult with cleaned values (the original is never mutated).

Attributes:

Name Type Description
values ndarray

1-D array of e-field values inside the ROI (raw or cleaned).

mask_img Nifti1Image

Binary mask used for extraction.

masked_img Nifti1Image or None

Volumetric image before outlier removal (set by postprocess()).

cleaned_img Nifti1Image or None

Volumetric image after outlier removal (set by postprocess()).

efield EFieldAccessor

Back-reference to the source e-field.

is_cleaned bool

True if this result went through postprocess().

Source code in simnibs_reader/efield/roi.py
class ROIResult:
    """E-field values extracted within a region of interest.

    This is the main object returned by ``EFieldAccessor.get_roi(...)``.
    It holds the extracted values and provides methods for statistics,
    post-processing, and export.

    Calling ``.postprocess()`` returns a **new** ``ROIResult`` with
    cleaned values (the original is never mutated).

    Attributes
    ----------
    values : np.ndarray
        1-D array of e-field values inside the ROI (raw or cleaned).
    mask_img : nib.Nifti1Image
        Binary mask used for extraction.
    masked_img : nib.Nifti1Image or None
        Volumetric image before outlier removal (set by ``postprocess()``).
    cleaned_img : nib.Nifti1Image or None
        Volumetric image after outlier removal (set by ``postprocess()``).
    efield : EFieldAccessor
        Back-reference to the source e-field.
    is_cleaned : bool
        ``True`` if this result went through ``postprocess()``.
    """

    def __init__(
        self,
        values: np.ndarray,
        mask_img: nib.Nifti1Image,
        efield: "EFieldAccessor",
        *,
        masked_img: nib.Nifti1Image | None = None,
        cleaned_img: nib.Nifti1Image | None = None,
        is_cleaned: bool = False,
    ) -> None:
        self.values = values
        self.mask_img = mask_img
        self.efield = efield
        self.masked_img = masked_img
        self.cleaned_img = cleaned_img
        self.is_cleaned = is_cleaned

    # -- statistics -------------------------------------------------------

    def stats(self) -> dict[str, float | int]:
        """Descriptive statistics on the extracted values (NaN-aware)."""
        return compute_stats(self.values)

    # -- post-processing --------------------------------------------------

    def postprocess(
        self,
        smooth_fwhm: float | None = 2.0,
        outlier_method: str = "iqr",
        portion: float | None = None,
    ) -> "ROIResult":
        """Smooth and/or remove outliers.

        Returns a **new** ``ROIResult`` with cleaned values — the original
        instance is never mutated.

        Parameters
        ----------
        smooth_fwhm : float or None
            FWHM (mm) for Gaussian smoothing applied *before* masking.
            ``None`` or ``0`` to skip.
        outlier_method : {"iqr", "z"}
            Outlier removal strategy.
        portion : float or None
            Central portion to keep (e.g. ``0.95``). ``None`` to skip.

        Returns
        -------
        ROIResult
            New instance with ``.is_cleaned = True``, ``.masked_img``,
            and ``.cleaned_img`` populated.
        """
        from .postprocess import remove_outliers

        efield_img = self.efield.img
        mask_img = self.mask_img

        # Optional smoothing (applied to the full volume before masking)
        if smooth_fwhm and smooth_fwhm > 0:
            efield_img = image.smooth_img(efield_img, fwhm=smooth_fwhm)

        # Re-extract values after smoothing
        roi_values = masking.apply_mask(efield_img, mask_img).astype(np.float64)
        masked_img = masking.unmask(roi_values, mask_img)

        # Outlier removal (only on non-zero voxels to avoid background bias)
        filtered = roi_values.copy()
        nonzero = roi_values > 0
        if nonzero.any():
            cleaned_nonzero, _ = remove_outliers(
                roi_values[nonzero],
                method=outlier_method,
                portion=portion,
            )
            filtered[nonzero] = cleaned_nonzero

        cleaned_img = masking.unmask(filtered, mask_img)

        return ROIResult(
            values=filtered,
            mask_img=mask_img,
            efield=self.efield,
            masked_img=masked_img,
            cleaned_img=cleaned_img,
            is_cleaned=True,
        )

    # -- export -----------------------------------------------------------

    def save(
        self,
        path: str | Path,
        metrics: list[str] | None = None,
        format: str = "tsv",
    ) -> Path:
        """Export statistics to disk.

        Parameters
        ----------
        path : str or Path
            Output file path (extension is added automatically if missing).
        metrics : list of str, optional
            Subset of stat keys to export (default: all).
        format : {"tsv", "csv"}
            Delimiter format.
        """
        from ..io.export import save_results

        return save_results(self.stats(), path, metrics=metrics, format=format)

    def save_nifti(self, path: str | Path) -> Path:
        """Write the volumetric result to a NIfTI file.

        Saves ``cleaned_img`` if post-processed, otherwise ``masked_img``.

        Raises
        ------
        ValueError
            If neither image is available (raw extraction without
            ``postprocess()``).
        """
        from ..io.nifti import save_nifti

        img = self.cleaned_img or self.masked_img
        if img is None:
            raise ValueError(
                "No volumetric image to save. Call .postprocess() first, "
                "or use .save() to export stats only."
            )
        return save_nifti(img, path)

    # -- properties -------------------------------------------------------

    @property
    def n_voxels(self) -> int:
        return int(self.values.size)

    def __repr__(self) -> str:
        tag = "cleaned" if self.is_cleaned else "raw"
        return (
            f"ROIResult({tag}, n_voxels={self.n_voxels}, "
            f"mean={np.nanmean(self.values):.4f})"
        )

stats()

Descriptive statistics on the extracted values (NaN-aware).

Source code in simnibs_reader/efield/roi.py
def stats(self) -> dict[str, float | int]:
    """Descriptive statistics on the extracted values (NaN-aware)."""
    return compute_stats(self.values)

postprocess(smooth_fwhm=2.0, outlier_method='iqr', portion=None)

Smooth and/or remove outliers.

Returns a new ROIResult with cleaned values — the original instance is never mutated.

Parameters:

Name Type Description Default
smooth_fwhm float or None

FWHM (mm) for Gaussian smoothing applied before masking. None or 0 to skip.

2.0
outlier_method ('iqr', 'z')

Outlier removal strategy.

"iqr"
portion float or None

Central portion to keep (e.g. 0.95). None to skip.

None

Returns:

Type Description
ROIResult

New instance with .is_cleaned = True, .masked_img, and .cleaned_img populated.

Source code in simnibs_reader/efield/roi.py
def postprocess(
    self,
    smooth_fwhm: float | None = 2.0,
    outlier_method: str = "iqr",
    portion: float | None = None,
) -> "ROIResult":
    """Smooth and/or remove outliers.

    Returns a **new** ``ROIResult`` with cleaned values — the original
    instance is never mutated.

    Parameters
    ----------
    smooth_fwhm : float or None
        FWHM (mm) for Gaussian smoothing applied *before* masking.
        ``None`` or ``0`` to skip.
    outlier_method : {"iqr", "z"}
        Outlier removal strategy.
    portion : float or None
        Central portion to keep (e.g. ``0.95``). ``None`` to skip.

    Returns
    -------
    ROIResult
        New instance with ``.is_cleaned = True``, ``.masked_img``,
        and ``.cleaned_img`` populated.
    """
    from .postprocess import remove_outliers

    efield_img = self.efield.img
    mask_img = self.mask_img

    # Optional smoothing (applied to the full volume before masking)
    if smooth_fwhm and smooth_fwhm > 0:
        efield_img = image.smooth_img(efield_img, fwhm=smooth_fwhm)

    # Re-extract values after smoothing
    roi_values = masking.apply_mask(efield_img, mask_img).astype(np.float64)
    masked_img = masking.unmask(roi_values, mask_img)

    # Outlier removal (only on non-zero voxels to avoid background bias)
    filtered = roi_values.copy()
    nonzero = roi_values > 0
    if nonzero.any():
        cleaned_nonzero, _ = remove_outliers(
            roi_values[nonzero],
            method=outlier_method,
            portion=portion,
        )
        filtered[nonzero] = cleaned_nonzero

    cleaned_img = masking.unmask(filtered, mask_img)

    return ROIResult(
        values=filtered,
        mask_img=mask_img,
        efield=self.efield,
        masked_img=masked_img,
        cleaned_img=cleaned_img,
        is_cleaned=True,
    )

save(path, metrics=None, format='tsv')

Export statistics to disk.

Parameters:

Name Type Description Default
path str or Path

Output file path (extension is added automatically if missing).

required
metrics list of str

Subset of stat keys to export (default: all).

None
format ('tsv', 'csv')

Delimiter format.

"tsv"
Source code in simnibs_reader/efield/roi.py
def save(
    self,
    path: str | Path,
    metrics: list[str] | None = None,
    format: str = "tsv",
) -> Path:
    """Export statistics to disk.

    Parameters
    ----------
    path : str or Path
        Output file path (extension is added automatically if missing).
    metrics : list of str, optional
        Subset of stat keys to export (default: all).
    format : {"tsv", "csv"}
        Delimiter format.
    """
    from ..io.export import save_results

    return save_results(self.stats(), path, metrics=metrics, format=format)

save_nifti(path)

Write the volumetric result to a NIfTI file.

Saves cleaned_img if post-processed, otherwise masked_img.

Raises:

Type Description
ValueError

If neither image is available (raw extraction without postprocess()).

Source code in simnibs_reader/efield/roi.py
def save_nifti(self, path: str | Path) -> Path:
    """Write the volumetric result to a NIfTI file.

    Saves ``cleaned_img`` if post-processed, otherwise ``masked_img``.

    Raises
    ------
    ValueError
        If neither image is available (raw extraction without
        ``postprocess()``).
    """
    from ..io.nifti import save_nifti

    img = self.cleaned_img or self.masked_img
    if img is None:
        raise ValueError(
            "No volumetric image to save. Call .postprocess() first, "
            "or use .save() to export stats only."
        )
    return save_nifti(img, path)