Skip to content

Postprocessing

Smoothing and outlier removal are methods on ROI.

simnibs_reader.nifti.roi.ROI.postprocess(smooth_fwhm=2.0, outlier_method='iqr', portion=None)

Smooth and/or remove outliers.

Returns a new ROI — the original 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
Source code in simnibs_reader/nifti/roi.py
def postprocess(
    self,
    smooth_fwhm: float | None = 2.0,
    outlier_method: str = "iqr",
    portion: float | None = None,
) -> ROI:
    """Smooth and/or remove outliers.

    Returns a **new** ``ROI`` — the original 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.
    """
    efield_img = (
        self._smooth(smooth_fwhm)
        if smooth_fwhm and smooth_fwhm > 0
        else self.efield.img                              # .img, pas EField
    )
    values, masked_img    = self._extract(efield_img)
    filtered, cleaned_img = self._clean(values, outlier_method, portion)

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

simnibs_reader.nifti.roi.ROI.remove_outliers(values, method='iqr', z_thresh=3.5, iqr_factor=1.5, portion=None) staticmethod

Filter outliers on a 1-D vector.

Parameters:

Name Type Description Default
values ndarray

Input values.

required
method ('iqr', 'z')

"iqr" — keep values within [Q1 - factor·IQR, Q3 + factor·IQR]. "z" — keep values with robust |z-score| ≤ z_thresh.

"iqr"
z_thresh float

Threshold for the robust z-score method (default 3.5).

3.5
iqr_factor float

Multiplier for IQR fences (default 1.5).

1.5
portion float or None

If set, additionally trim to the central portion (e.g. 0.95 keeps the 2.5 %–97.5 % range).

None

Returns:

Name Type Description
filtered ndarray

Copy of values with outliers set to NaN.

keep ndarray

Boolean mask of retained values.

Source code in simnibs_reader/nifti/roi.py
@staticmethod
def remove_outliers(
    values: np.ndarray,
    method: str = "iqr",
    z_thresh: float = 3.5,
    iqr_factor: float = 1.5,
    portion: float | None = None,
) -> tuple[np.ndarray, np.ndarray]:
    """Filter outliers on a 1-D vector.

    Parameters
    ----------
    values : np.ndarray
        Input values.
    method : {"iqr", "z"}
        ``"iqr"`` — keep values within ``[Q1 - factor·IQR, Q3 + factor·IQR]``.
        ``"z"``   — keep values with robust |z-score| ≤ *z_thresh*.
    z_thresh : float
        Threshold for the robust z-score method (default 3.5).
    iqr_factor : float
        Multiplier for IQR fences (default 1.5).
    portion : float or None
        If set, additionally trim to the central *portion*
        (e.g. ``0.95`` keeps the 2.5 %–97.5 % range).

    Returns
    -------
    filtered : np.ndarray
        Copy of *values* with outliers set to ``NaN``.
    keep : np.ndarray
        Boolean mask of retained values.
    """
    vals = np.asarray(values, dtype=np.float64)
    keep = np.ones(vals.shape, dtype=bool)

    # Central portion trim
    if portion is not None:
        if not (0.0 < portion <= 1.0):
            raise ValueError("portion must be in (0, 1]")
        lo_q = (1.0 - portion) / 2.0
        hi_q = 1.0 - lo_q
        lo, hi = np.quantile(vals, [lo_q, hi_q])
        keep &= (vals >= lo) & (vals <= hi)

    # IQR or robust z-score
    if method.lower() == "iqr":
        q1, q3 = np.quantile(vals, [0.25, 0.75])
        iqr = q3 - q1
        keep &= (vals >= q1 - iqr_factor * iqr) & (vals <= q3 + iqr_factor * iqr)
    elif method.lower() == "z":
        med = np.median(vals)
        mad = np.median(np.abs(vals - med))
        if mad == 0:
            keep &= np.isfinite(vals)
        else:
            robust_z = 0.6745 * (vals - med) / mad
            keep &= np.abs(robust_z) <= z_thresh
    else:
        raise ValueError(f"method must be 'iqr' or 'z', got '{method}'")

    filtered = vals.copy()
    filtered[~keep] = np.nan
    return filtered, keep