"""照明均一性の空間分析モジュール(中心-周辺勾配)."""
import numpy
def calc_distance_map(height: int, width: int) -> numpy.ndarray:
"""各ピクセルの中心からの正規化楕円距離を算出する.
楕円距離を使用することで、縦横比が異なる画像でも中心を基準に
等方的なゾーン分類が行えるようにする.
Args:
height: 画像の高さ(ピクセル).
width: 画像の幅(ピクセル).
Returns:
正規化楕円距離の NumPy 配列(H x W, float64).値域は 0.0〜1.0.
"""
cy = height / 2
cx = width / 2
# mgrid で全ピクセルの y, x 座標を生成
y_coords, x_coords = numpy.mgrid[0:height, 0:width]
# 中心を原点とした楕円距離(縦横それぞれ中心距離で正規化)
dist = numpy.sqrt(((x_coords - cx) / cx) ** 2 + ((y_coords - cy) / cy) ** 2)
# 最大値で再正規化して 0.0〜1.0 に揃える
max_dist = dist.max()
if max_dist > 0:
dist = dist / max_dist
return dist
def classify_zones(
distance_map: numpy.ndarray,
center_threshold: float = 0.33,
periphery_threshold: float = 0.66,
) -> numpy.ndarray:
"""距離マップを 3 ゾーン(中心・中間・周辺)に分類する.
ゾーン境界を正規化距離の三等分付近に置くことで、
均一性評価の標準的な領域分割に対応する.
Args:
distance_map: 正規化楕円距離の NumPy 配列(H x W, float64).
center_threshold: 中心ゾーンの上限距離(デフォルト: 0.33).
periphery_threshold: 中間ゾーンの上限距離(デフォルト: 0.66).
Returns:
ゾーンマップの NumPy 配列(H x W, uint8).
0=center, 1=middle, 2=periphery.
"""
zone_map = numpy.zeros(distance_map.shape, dtype=numpy.uint8)
# 中間ゾーンと周辺ゾーンを順に上書きする
zone_map[distance_map >= center_threshold] = 1
zone_map[distance_map >= periphery_threshold] = 2
return zone_map
def calc_zone_stats(luminance: numpy.ndarray, zone_map: numpy.ndarray) -> dict:
"""ゾーン別の輝度統計と中心-周辺勾配指標を算出する.
center_periphery_ratio が 1.0 に近いほど均一、
gradient_magnitude が 0 に近いほど空間的ムラが小さい.
Args:
luminance: 輝度画像の NumPy 配列(H x W, float64).
zone_map: ゾーンマップの NumPy 配列(H x W, uint8).
0=center, 1=middle, 2=periphery.
Returns:
ゾーン統計を格納した辞書.キーは:
"center_mean", "center_std", "middle_mean", "middle_std",
"periphery_mean", "periphery_std",
"center_periphery_ratio", "gradient_magnitude".
"""
center_pixels = luminance[zone_map == 0]
middle_pixels = luminance[zone_map == 1]
periphery_pixels = luminance[zone_map == 2]
center_mean = float(numpy.mean(center_pixels))
center_std = float(numpy.std(center_pixels))
middle_mean = float(numpy.mean(middle_pixels))
middle_std = float(numpy.std(middle_pixels))
periphery_mean = float(numpy.mean(periphery_pixels))
periphery_std = float(numpy.std(periphery_pixels))
# 中心/周辺比: 照明が中心に集中しているかを示す
center_periphery_ratio = (
center_mean / periphery_mean if periphery_mean != 0.0 else float("inf")
)
# 勾配量(%): 中心と周辺の輝度差を中心輝度に対する比率で表す
gradient_magnitude = (
(center_mean - periphery_mean) / center_mean * 100
if center_mean != 0.0
else float("inf")
)
return {
"center_mean": center_mean,
"center_std": center_std,
"middle_mean": middle_mean,
"middle_std": middle_std,
"periphery_mean": periphery_mean,
"periphery_std": periphery_std,
"center_periphery_ratio": center_periphery_ratio,
"gradient_magnitude": gradient_magnitude,
}
def calc_radial_profile(
luminance: numpy.ndarray,
distance_map: numpy.ndarray,
num_bins: int = 20,
) -> numpy.ndarray:
"""放射状輝度プロファイルを算出する.
距離ビンごとに平均輝度を集計することで、
中心から周辺にかけての輝度変化の傾向を把握する.
Args:
luminance: 輝度画像の NumPy 配列(H x W, float64).
distance_map: 正規化楕円距離の NumPy 配列(H x W, float64).
num_bins: 距離方向の分割数(デフォルト: 20).
Returns:
放射状プロファイルの NumPy 配列(num_bins x 2, float64).
列 0 = ビン中心距離, 列 1 = 平均輝度.
"""
bin_edges = numpy.linspace(0.0, 1.0, num_bins + 1)
profile = numpy.zeros((num_bins, 2), dtype=numpy.float64)
for i in range(num_bins):
bin_center = (bin_edges[i] + bin_edges[i + 1]) / 2.0
mask = (distance_map >= bin_edges[i]) & (distance_map < bin_edges[i + 1])
# 最外ビンは上端を含める
if i == num_bins - 1:
mask = distance_map >= bin_edges[i]
pixels = luminance[mask]
mean_lum = float(numpy.mean(pixels)) if pixels.size > 0 else 0.0
profile[i, 0] = bin_center
profile[i, 1] = mean_lum
return profile
def calc_spatial_uniformity(luminance: numpy.ndarray) -> dict:
"""空間的均一性指標を一括算出するファサード関数.
中心-周辺勾配の定量化に必要な distance_map, zone_map,
zone_stats, radial_profile を一度の呼び出しで取得できる.
Args:
luminance: 輝度画像の NumPy 配列(H x W, float64).
Returns:
空間分析結果を格納した辞書.キーは:
"zone_stats" (dict), "radial_profile" (numpy.ndarray),
"zone_map" (numpy.ndarray).
"""
height, width = luminance.shape
distance_map = calc_distance_map(height, width)
zone_map = classify_zones(distance_map)
zone_stats = calc_zone_stats(luminance, zone_map)
radial_profile = calc_radial_profile(luminance, distance_map)
return {
"zone_stats": zone_stats,
"radial_profile": radial_profile,
"zone_map": zone_map,
}