"""evaluation/radial.py の仕様ベーステスト.
仕様出典: SmTIAS-Evaluation/SPEC_02_照明均一性評価アルゴリズム.md「放射状輝度プロファイル」節
"""
import numpy
import pytest
from sm_tias_lightsim.evaluation.radial import calc_radial_profile
# ---------------------------------------------------------------------------
# 出力の形状・型テスト
# ---------------------------------------------------------------------------
def test_radial_profile_shape_and_dtype(uniform_luminance_100x100):
"""デフォルト n_bins=20 で出力 shape が (20, 2), dtype が float64 になること.
仕様: 出力 20×2 の配列 (列 0=ビン中心距離, 列 1=平均輝度), dtype=numpy.float64
(SPEC_02 放射状輝度プロファイル節)
"""
profile = calc_radial_profile(uniform_luminance_100x100)
assert profile.shape == (20, 2)
assert profile.dtype == numpy.float64
def test_radial_profile_default_bins(uniform_luminance_100x100):
"""デフォルト引数で 20 ビンのプロファイルが返ること."""
profile = calc_radial_profile(uniform_luminance_100x100)
assert profile.shape[0] == 20
def test_radial_profile_custom_bins(uniform_luminance_100x100):
"""n_bins=10 で shape が (10, 2) になること.
仕様: n_bins でビン数を指定可能 (radial.py docstring)
"""
profile = calc_radial_profile(uniform_luminance_100x100, n_bins=10)
assert profile.shape == (10, 2)
# ---------------------------------------------------------------------------
# ビン中心距離のテスト
# ---------------------------------------------------------------------------
def test_radial_profile_distances_increasing(uniform_luminance_100x100):
"""列 0 (ビン中心距離) が単調増加であること.
仕様: 中心から周辺にかけての輝度変化を連続的に把握 (SPEC_02)
ビン中心は linspace(0, 1, n_bins+1) の隣接平均なので常に単調増加する.
"""
profile = calc_radial_profile(uniform_luminance_100x100)
distances = profile[:, 0]
diffs = numpy.diff(distances)
assert numpy.all(diffs > 0)
def test_radial_profile_distances_in_unit_range(uniform_luminance_100x100):
"""列 0 の値が [0, 1] 範囲内であること.
仕様: 正規化楕円距離 d ∈ [0, 1] を n_bins 等分 (SPEC_02)
"""
profile = calc_radial_profile(uniform_luminance_100x100)
distances = profile[:, 0]
assert float(numpy.min(distances)) >= 0.0
assert float(numpy.max(distances)) <= 1.0
def test_radial_profile_bin_centers_correct():
"""ビン中心距離が linspace(0, 1, n_bins+1) の隣接平均と一致すること."""
n_bins = 5
luminance = numpy.full((100, 100), 50.0, dtype=numpy.float64)
profile = calc_radial_profile(luminance, n_bins=n_bins)
bin_edges = numpy.linspace(0.0, 1.0, n_bins + 1)
expected_centers = (bin_edges[:-1] + bin_edges[1:]) / 2.0
assert numpy.allclose(profile[:, 0], expected_centers)
# ---------------------------------------------------------------------------
# 輝度値のテスト
# ---------------------------------------------------------------------------
def test_radial_profile_uniform_luminance(uniform_luminance_100x100):
"""一様輝度では全ビンの平均輝度が同じ値 (NaN ビンを除く) になること.
仕様: 一様照明では放射プロファイルが平坦 (SPEC_02)
"""
profile = calc_radial_profile(uniform_luminance_100x100)
mean_values = profile[:, 1]
valid_mask = ~numpy.isnan(mean_values)
assert numpy.any(valid_mask), "有効なビンが 1 つ以上存在すること"
valid_values = mean_values[valid_mask]
assert numpy.allclose(valid_values, valid_values[0])
def test_radial_profile_includes_d_one():
"""d == 1.0 の画素が最後のビンに含まれること(最外ビンが NaN にならない).
仕様: 最外ビンは上端を含める (d=1.0 の画素を欠損させない) (SPEC_02)
100×100 の画像では隅のピクセルが d=1.0 付近に存在する.
"""
luminance = numpy.full((100, 100), 128.0, dtype=numpy.float64)
profile = calc_radial_profile(luminance, n_bins=20)
last_bin_value = profile[-1, 1]
assert not numpy.isnan(last_bin_value), "最外ビンが NaN であってはならない"
def test_radial_profile_radial_gradient(radial_gradient_luminance_100x100):
"""中心が明るいグラジェント輝度では, 先頭ビンの輝度が末尾ビンより大きいこと.
仕様: 中心-周辺方向の輝度変化を連続的に把握 (SPEC_02)
合成輝度: 200 - 100 * d なので中心 (d≈0) が周辺 (d≈1) より明るい.
"""
profile = calc_radial_profile(radial_gradient_luminance_100x100)
mean_values = profile[:, 1]
valid_mask = ~numpy.isnan(mean_values)
valid_values = mean_values[valid_mask]
assert valid_values[0] > valid_values[-1]
def test_radial_profile_empty_bins_are_nan():
"""画素が存在しない空ビンは numpy.nan になること.
仕様: 空ビンは numpy.nan (radial.py docstring)
非常に小さい画像 (3×3) で n_bins=20 のとき,距離が到達しないビンが発生しうる.
"""
luminance = numpy.full((3, 3), 50.0, dtype=numpy.float64)
profile = calc_radial_profile(luminance, n_bins=20)
# 3×3 画像で 20 ビンのうち少なくとも 1 ビンが空である可能性を確認
# (実際にどのビンが空になるかは距離マップに依存するが,nan があることを検証)
# ここでは nan が存在する可能性を確認するだけとし,存在する場合は値が nan であること
mean_values = profile[:, 1]
for v in mean_values:
assert v == pytest.approx(float(v)) or numpy.isnan(v)