"""uniformity.py の仕様ベーステスト.
仕様出典: SmTIAS-Evaluation/SPEC_02_照明均一性評価アルゴリズム.md
"""
import numpy
import pytest
from sm_tias_lightsim.uniformity import (
REC709_COEFF_B,
REC709_COEFF_G,
REC709_COEFF_R,
calc_uniformity,
to_grayscale,
)
# ---------------------------------------------------------------------------
# REC709 定数のテスト
# ---------------------------------------------------------------------------
def test_rec709_coefficients_sum_to_one():
"""REC709 3 係数の和が 1.0 であること.
仕様: Y = 0.2126 R + 0.7152 G + 0.0722 B (SPEC_02 輝度変換節)
"""
total = REC709_COEFF_R + REC709_COEFF_G + REC709_COEFF_B
assert abs(total - 1.0) < 1e-10
def test_rec709_coefficient_r_value():
"""REC709 R 係数が 0.2126 であること."""
assert REC709_COEFF_R == pytest.approx(0.2126)
def test_rec709_coefficient_g_value():
"""REC709 G 係数が 0.7152 であること."""
assert REC709_COEFF_G == pytest.approx(0.7152)
def test_rec709_coefficient_b_value():
"""REC709 B 係数が 0.0722 であること."""
assert REC709_COEFF_B == pytest.approx(0.0722)
# ---------------------------------------------------------------------------
# to_grayscale のテスト
# ---------------------------------------------------------------------------
def test_grayscale_uses_rec709_coefficients_red(red_pixel):
"""純赤入力で期待輝度が REC709_COEFF_R * 255 になること.
仕様: Y = 0.2126 R + 0.7152 G + 0.0722 B (SPEC_02)
"""
result = to_grayscale(red_pixel)
expected = REC709_COEFF_R * 255.0
assert result[0, 0] == pytest.approx(expected)
def test_grayscale_uses_rec709_coefficients_green(green_pixel):
"""純緑入力で期待輝度が REC709_COEFF_G * 255 になること."""
result = to_grayscale(green_pixel)
expected = REC709_COEFF_G * 255.0
assert result[0, 0] == pytest.approx(expected)
def test_grayscale_uses_rec709_coefficients_blue(blue_pixel):
"""純青入力で期待輝度が REC709_COEFF_B * 255 になること."""
result = to_grayscale(blue_pixel)
expected = REC709_COEFF_B * 255.0
assert result[0, 0] == pytest.approx(expected)
def test_grayscale_returns_float64(red_pixel):
"""to_grayscale の出力 dtype が numpy.float64 であること.
仕様: 出力値域 0.0〜255.0, float64 (SPEC_02 輝度変換節)
"""
result = to_grayscale(red_pixel)
assert result.dtype == numpy.float64
def test_grayscale_preserves_shape_hxw():
"""入力 (H, W, 3) に対して出力が (H, W) になること.
仕様: 出力は H x W の輝度画像 (SPEC_02)
"""
image = numpy.zeros((7, 13, 3), dtype=numpy.uint8)
result = to_grayscale(image)
assert result.shape == (7, 13)
def test_grayscale_white_pixel_equals_255():
"""白 (255, 255, 255) の輝度が 255 になること.
Rec.709 係数の和が 1.0 なので白は 255 のまま.
"""
white = numpy.array([[[255, 255, 255]]], dtype=numpy.uint8)
result = to_grayscale(white)
assert result[0, 0] == pytest.approx(255.0)
def test_grayscale_black_pixel_equals_zero():
"""黒 (0, 0, 0) の輝度が 0 になること."""
black = numpy.array([[[0, 0, 0]]], dtype=numpy.uint8)
result = to_grayscale(black)
assert result[0, 0] == pytest.approx(0.0)
# ---------------------------------------------------------------------------
# calc_uniformity のテスト
# ---------------------------------------------------------------------------
def test_calc_uniformity_uniform_image(uniform_luminance_100x100):
"""全画素が同値の輝度マップでは cov==0, std==0, max_min_ratio==1.0 になること.
仕様:
- CoV = σ / μ (SPEC_02 均一性指標節)
- Max/Min Ratio = max / min; 理想的な均一照明では 1.0 (SPEC_02)
"""
result = calc_uniformity(uniform_luminance_100x100)
assert result["cov"] == pytest.approx(0.0)
assert result["std"] == pytest.approx(0.0)
assert result["max_min_ratio"] == pytest.approx(1.0)
assert result["mean"] == pytest.approx(100.0)
assert result["max"] == pytest.approx(100.0)
assert result["min"] == pytest.approx(100.0)
def test_calc_uniformity_known_values():
"""既知の配列 [50, 100, 150] で mean/std/cov が正しい値になること.
手計算:
- mean = 100.0
- std = sqrt(((50-100)^2 + (100-100)^2 + (150-100)^2) / 3)
= sqrt((2500 + 0 + 2500) / 3) = sqrt(5000/3) ≈ 40.8248...
- cov = std / mean ≈ 0.40825...
- max_min_ratio = 150 / 50 = 3.0
"""
luminance = numpy.array([[50.0, 100.0, 150.0]], dtype=numpy.float64)
result = calc_uniformity(luminance)
expected_mean = 100.0
expected_std = numpy.std(numpy.array([50.0, 100.0, 150.0]))
expected_cov = expected_std / expected_mean
expected_ratio = 150.0 / 50.0
assert result["mean"] == pytest.approx(expected_mean)
assert result["std"] == pytest.approx(float(expected_std))
assert result["cov"] == pytest.approx(expected_cov)
assert result["max_min_ratio"] == pytest.approx(expected_ratio)
assert result["max"] == pytest.approx(150.0)
assert result["min"] == pytest.approx(50.0)
def test_calc_uniformity_result_contains_required_keys():
"""戻り値 dict が必須キーを全て含むこと."""
luminance = numpy.array([[10.0, 20.0]], dtype=numpy.float64)
result = calc_uniformity(luminance)
assert set(result.keys()) == {"cov", "std", "max_min_ratio", "mean", "max", "min"}
def test_calc_uniformity_raises_on_zero_min():
"""最小値が 0 の輝度マップで ValueError が発生すること.
仕様: Y_min = 0 の場合は計算不能 (SPEC_02 均一性指標節)
"""
luminance = numpy.array([[0.0, 100.0, 200.0]], dtype=numpy.float64)
with pytest.raises(ValueError):
calc_uniformity(luminance)
def test_calc_uniformity_cov_formula():
"""CoV が σ / μ の公式に従うこと (SPEC_02)."""
luminance = numpy.array([[80.0, 100.0, 120.0]], dtype=numpy.float64)
result = calc_uniformity(luminance)
expected_cov = result["std"] / result["mean"]
assert result["cov"] == pytest.approx(expected_cov)