Source code for opticks.contrast_model.mtf_utils

# opticks Models and analysis tools for optical system engineering
#
# Copyright (C) Egemen Imre
#
# Licensed under GNU GPL v3.0. See LICENSE.md for more info.
"""
Shared sanity-check helpers for the MTF model code.

Two patterns are consolidated here:

- ``reject_unused_params`` — guards against caller-supplied keyword
  parameters that the chosen model / kernel / preset does not actually
  use. Used by ``validate_*_params`` functions and preset overrides.
- ``check_mtf_below_unity`` / ``check_mtf_nonneg`` — atomic output
  sanity checks composed at call sites. Passive (detector / optics)
  MTF stages call both; processing-stage MTF (resampling kernels with
  edge-boost behaviour like Bicubic / Lanczos) calls only the
  non-negativity primitive.

Each primitive treats non-finite values (NaN, ±Inf) as failures, so
silent NaN propagation is not possible.
"""

from collections.abc import Iterable, Mapping

import numpy as np
from numpy.typing import NDArray


[docs] def reject_unused_params( supplied: Mapping[str, object], required: Iterable[str], *, model_name: str, ) -> None: """Raise ``ValueError`` if any supplied parameter is not in ``required``. Parameters supplied as ``None`` are ignored (treated as "not supplied"). Parameters ---------- supplied : Mapping[str, object] Mapping from parameter name to caller-supplied value. required : Iterable[str] Names of parameters the chosen model / kernel / preset accepts. model_name : str Human-readable identifier of the model / kernel / preset, used in the error message (e.g. ``f"model {model}"``). """ required_set = required if isinstance(required, (set, frozenset)) else set(required) for name, value in supplied.items(): if value is not None and name not in required_set: raise ValueError( f"Parameter '{name}' is not used by {model_name}. " f"Remove it or choose a different model." )
[docs] def check_mtf_below_unity( values: float | NDArray[np.float64], model_name: str, ) -> None: """Raise ``ValueError`` if any MTF value exceeds 1 (or is non-finite). Use for passive MTF stages where ``MTF > 1`` is unphysical (detector, optics). For processing-stage MTF that legitimately exceeds 1 (e.g. Bicubic / Lanczos edge-boost), call only :func:`check_mtf_nonneg`. Parameters ---------- values : float | NDArray[np.float64] MTF value(s) to check (scalar or array). model_name : str Identifier of the MTF model, used in the error message. """ arr = np.asarray(values, dtype=float) if not np.all(np.isfinite(arr)): raise ValueError(f"MTF contains non-finite values for '{model_name}'.") if np.any(arr > 1.0): raise ValueError( f"MTF exceeds 1 for '{model_name}': max={float(np.max(arr)):.6g}. " f"Check physical parameters." )
[docs] def check_mtf_nonneg( values: float | NDArray[np.float64], model_name: str, ) -> None: """Raise ``ValueError`` if any MTF value is negative (or is non-finite). Parameters ---------- values : float | NDArray[np.float64] MTF value(s) to check (scalar or array). model_name : str Identifier of the MTF model, used in the error message. """ arr = np.asarray(values, dtype=float) if not np.all(np.isfinite(arr)): raise ValueError(f"MTF contains non-finite values for '{model_name}'.") if np.any(arr < 0.0): raise ValueError( f"MTF has negative values for '{model_name}': " f"min={float(np.min(arr)):.6g}. Check physical parameters." )