Source code for ebsdsim.normalize

"""Per-channel intensity scaling for Lambert display (not used in simulation output)."""

from __future__ import annotations

from typing import Literal

import numpy as np
from numpy.typing import NDArray

NormalizeMode = Literal["minmax", "robust"]


def _percentile(sorted_vals: NDArray[np.float32], p: float) -> float:
    idx = min(sorted_vals.size - 1, max(0, int(np.floor(p * (sorted_vals.size - 1)))))
    return float(sorted_vals[idx])


[docs] def scale_fs_channel( vals: NDArray[np.floating], normalize: NormalizeMode | None, *, robust_p_low: float = 0.01, robust_p_high: float = 0.99, ) -> NDArray[np.float32]: """Scale one fundamental-sector channel to ``[0, 1]`` for display. Parameters ---------- normalize: ``None`` — return raw values (no scaling). ``"minmax"`` — divide by per-channel min/max (lossless up to float precision). ``"robust"`` — IQR outlier gate, then percentile window (lossy by default uses ``robust_p_low`` / ``robust_p_high``). robust_p_low, robust_p_high: Percentiles in ``[0, 1]`` used only when ``normalize="robust"``. """ arr = np.asarray(vals, dtype=np.float32) if normalize is None: return arr.copy() if arr.dtype != np.float32 else arr.astype(np.float32, copy=False) if arr.size == 0: return arr.copy() if normalize == "minmax": lo = float(np.min(arr)) hi = float(np.max(arr)) elif normalize == "robust": if not (0.0 <= robust_p_low < robust_p_high <= 1.0): raise ValueError("robust_p_low and robust_p_high must satisfy 0 <= low < high <= 1") sorted_vals = np.sort(arr) q1 = _percentile(sorted_vals, 0.25) q3 = _percentile(sorted_vals, 0.75) iqr = q3 - q1 if iqr > 0: lo_bound = q1 - 3 * iqr hi_bound = q3 + 3 * iqr good = sorted_vals[(sorted_vals >= lo_bound) & (sorted_vals <= hi_bound)] if good.size > 0: lo = _percentile(good, robust_p_low) hi = _percentile(good, robust_p_high) else: lo = float(sorted_vals[0]) hi = float(sorted_vals[-1]) else: lo = float(sorted_vals[0]) hi = float(sorted_vals[-1]) else: raise ValueError(f"unknown normalize mode: {normalize!r}") span = hi - lo out = np.zeros_like(arr) if span <= 1e-15: return out out[:] = np.clip((arr - lo) / span, 0.0, 1.0) return out