重构-抽纯函数

This commit is contained in:
xinzhu.yin
2026-04-20 09:41:24 +08:00
parent 90d7d8e3f0
commit 22c46632ac
8 changed files with 400 additions and 322 deletions

0
app/tests/__init__.py Normal file
View File

248
app/tests/color_accuracy.py Normal file
View File

@@ -0,0 +1,248 @@
"""色准ΔE2000 / 标准色)相关纯算法。
Step 1 重构:从 pqAutomationApp.PQAutomationApp 中原样搬迁以下方法,
去掉 self 参数,改为模块级纯函数:
- calculate_delta_e_2000
- calculate_color_accuracy
- get_accuracy_color_standards
"""
import math
import numpy as np
def calculate_delta_e_2000(
measured_x, measured_y, measured_lv, standard_x, standard_y
):
"""
计算 ΔE 2000 色差(修正版)
Args:
measured_x, measured_y: 测量的 xy 坐标
measured_lv: 测量的亮度cd/m²
standard_x, standard_y: 标准的 xy 坐标
Returns:
float: ΔE 2000 色差值
"""
# ========== 1. xy → XYZ使用实际亮度==========
def xy_to_XYZ(x, y, Y):
if y == 0:
return 0, 0, 0
X = x * Y / y
Z = (1 - x - y) * Y / y
return X, Y, Z
# 修复:使用实际测量的亮度
X1, Y1, Z1 = xy_to_XYZ(measured_x, measured_y, measured_lv)
# 修复:标准值使用相同的参考亮度(只比较色度差异)
X2, Y2, Z2 = xy_to_XYZ(standard_x, standard_y, measured_lv)
# ========== 2. XYZ → LabD65 白点)==========
def XYZ_to_Lab(X, Y, Z):
# D65 白点
Xn, Yn, Zn = 95.047, 100.000, 108.883
# 归一化
xr = X / Xn
yr = Y / Yn
zr = Z / Zn
# f(t) 函数
def f(t):
delta = 6.0 / 29.0
if t > delta ** 3:
return t ** (1.0 / 3.0)
else:
return t / (3 * delta ** 2) + 4.0 / 29.0
fx = f(xr)
fy = f(yr)
fz = f(zr)
L = 116 * fy - 16
a = 500 * (fx - fy)
b = 200 * (fy - fz)
return L, a, b
L1, a1, b1 = XYZ_to_Lab(X1, Y1, Z1)
L2, a2, b2 = XYZ_to_Lab(X2, Y2, Z2)
# ========== 3. ΔE 2000 公式 ==========
L_bar = (L1 + L2) / 2.0
C1 = math.sqrt(a1 ** 2 + b1 ** 2)
C2 = math.sqrt(a2 ** 2 + b2 ** 2)
C_bar = (C1 + C2) / 2.0
G = 0.5 * (1 - math.sqrt(C_bar ** 7 / (C_bar ** 7 + 25 ** 7)))
a1_prime = a1 * (1 + G)
a2_prime = a2 * (1 + G)
C1_prime = math.sqrt(a1_prime ** 2 + b1 ** 2)
C2_prime = math.sqrt(a2_prime ** 2 + b2 ** 2)
C_bar_prime = (C1_prime + C2_prime) / 2.0
def calc_hue(a_prime, b):
if a_prime == 0 and b == 0:
return 0
h = math.atan2(b, a_prime) * 180 / math.pi
if h < 0:
h += 360
return h
h1_prime = calc_hue(a1_prime, b1)
h2_prime = calc_hue(a2_prime, b2)
if C1_prime == 0 or C2_prime == 0:
delta_h_prime = 0
else:
delta_h = h2_prime - h1_prime
if abs(delta_h) <= 180:
delta_h_prime = delta_h
elif delta_h > 180:
delta_h_prime = delta_h - 360
else:
delta_h_prime = delta_h + 360
if C1_prime == 0 or C2_prime == 0:
H_bar_prime = h1_prime + h2_prime
else:
if abs(h1_prime - h2_prime) <= 180:
H_bar_prime = (h1_prime + h2_prime) / 2.0
elif h1_prime + h2_prime < 360:
H_bar_prime = (h1_prime + h2_prime + 360) / 2.0
else:
H_bar_prime = (h1_prime + h2_prime - 360) / 2.0
delta_L_prime = L2 - L1
delta_C_prime = C2_prime - C1_prime
delta_H_prime = (
2
* math.sqrt(C1_prime * C2_prime)
* math.sin(math.radians(delta_h_prime / 2.0))
)
S_L = 1 + (0.015 * (L_bar - 50) ** 2) / math.sqrt(20 + (L_bar - 50) ** 2)
S_C = 1 + 0.045 * C_bar_prime
T = (
1
- 0.17 * math.cos(math.radians(H_bar_prime - 30))
+ 0.24 * math.cos(math.radians(2 * H_bar_prime))
+ 0.32 * math.cos(math.radians(3 * H_bar_prime + 6))
- 0.20 * math.cos(math.radians(4 * H_bar_prime - 63))
)
S_H = 1 + 0.015 * C_bar_prime * T
delta_theta = 30 * math.exp(-(((H_bar_prime - 275) / 25) ** 2))
R_C = 2 * math.sqrt(C_bar_prime ** 7 / (C_bar_prime ** 7 + 25 ** 7))
R_T = -R_C * math.sin(math.radians(2 * delta_theta))
kL = 1.0
kC = 1.0
kH = 1.0
delta_E = math.sqrt(
(delta_L_prime / (kL * S_L)) ** 2
+ (delta_C_prime / (kC * S_C)) ** 2
+ (delta_H_prime / (kH * S_H)) ** 2
+ R_T * (delta_C_prime / (kC * S_C)) * (delta_H_prime / (kH * S_H))
)
return delta_E
def calculate_color_accuracy(measured, standard):
"""计算色差(简化版,欧氏距离 × 1000"""
delta_E = {}
for color in measured.keys():
dx = measured[color][0] - standard[color][0]
dy = measured[color][1] - standard[color][1]
delta_E[color] = np.sqrt(dx * dx + dy * dy) * 1000
return delta_E
# 29 色 SDR 标准色板(保持与原实现一致)
_SDR_COLOR_PATTERNS = [
("White", 255, 255, 255),
("Gray 80", 230, 230, 230),
("Gray 65", 209, 209, 209),
("Gray 50", 186, 186, 186),
("Gray 35", 158, 158, 158),
("Dark Skin", 115, 82, 66),
("Light Skin", 194, 150, 130),
("Blue Sky", 94, 122, 156),
("Foliage", 89, 107, 66),
("Blue Flower", 130, 128, 176),
("Bluish Green", 99, 189, 168),
("Orange", 217, 120, 41),
("Purplish Blue", 74, 92, 163),
("Moderate Red", 194, 84, 97),
("Purple", 92, 61, 107),
("Yellow Green", 158, 186, 64),
("Orange Yellow", 230, 161, 46),
("Blue (Legacy)", 51, 61, 150),
("Green (Legacy)", 71, 148, 71),
("Red (Legacy)", 176, 48, 59),
("Yellow (Legacy)", 237, 199, 33),
("Magenta (Legacy)", 186, 84, 145),
("Cyan (Legacy)", 0, 133, 163),
("100% Red", 255, 0, 0),
("100% Green", 0, 255, 0),
("100% Blue", 0, 0, 255),
("100% Cyan", 0, 255, 255),
("100% Magenta", 255, 0, 255),
("100% Yellow", 255, 255, 0),
]
def _rgb_to_xy_srgb(r, g, b):
"""sRGB (8bit) → CIE 1931 xy"""
r, g, b = r / 255.0, g / 255.0, b / 255.0
def gamma_decode(c):
if c <= 0.04045:
return c / 12.92
else:
return ((c + 0.055) / 1.055) ** 2.4
r_linear = gamma_decode(r)
g_linear = gamma_decode(g)
b_linear = gamma_decode(b)
# sRGB → XYZD65 白点IEC 61966-2-1
X = r_linear * 0.4124564 + g_linear * 0.3575761 + b_linear * 0.1804375
Y = r_linear * 0.2126729 + g_linear * 0.7151522 + b_linear * 0.0721750
Z = r_linear * 0.0193339 + g_linear * 0.1191920 + b_linear * 0.9503041
total = X + Y + Z
if total == 0:
return 0.3127, 0.3290 # D65 白点
return X / total, Y / total
def get_accuracy_color_standards(test_type):
"""
获取色准测试的标准 xy 色度坐标(动态计算)
Args:
test_type: 测试类型 ("sdr_movie""hdr_movie")
Returns:
dict: {color_name: (x, y), ...}
"""
# 注意:原实现对 sdr/hdr 使用同一张色板,这里保持原行为。
del test_type # 参数保留以兼容调用方签名
standards = {}
for name, r, g, b in _SDR_COLOR_PATTERNS:
standards[name] = _rgb_to_xy_srgb(r, g, b)
return standards

40
app/tests/eotf.py Normal file
View File

@@ -0,0 +1,40 @@
"""EOTFPQ / ST.2084)相关纯算法。"""
import numpy as np
def calculate_pq_curve(gray_levels):
"""计算 PQ (ST.2084) EOTF 理想曲线。
Args:
gray_levels: 灰阶百分比数组 (0-100)
Returns:
numpy.ndarray: 归一化亮度数组 (0-1)
"""
# PQ 曲线参数ITU-R BT.2100 标准)
m1 = 0.1593017578125 # = 2610 / 16384
m2 = 78.84375 # = 78.84375
c1 = 0.8359375 # = 3424 / 4096
c2 = 18.8515625 # = 2413 / 128
c3 = 18.6875 # = 2392 / 128
L_bar = []
for gray in gray_levels:
V = gray / 100.0
if V <= 0:
L_bar.append(0)
else:
V_pow = np.power(V, 1 / m2)
numerator = max(V_pow - c1, 0)
denominator = c2 - c3 * V_pow
if denominator > 0:
L = np.power(numerator / denominator, 1 / m1)
else:
L = 0
L_bar.append(L)
return np.array(L_bar)

19
app/tests/gamma.py Normal file
View File

@@ -0,0 +1,19 @@
"""Gamma 相关纯算法。"""
import algorithm.pq_algorithm as pq_algorithm
def calculate_gamma(results, max_index_fix, pattern_params=None):
"""计算 Gamma 值,返回 (results_with_gamma_list, L_bar)。
Args:
results: 测量结果列表
max_index_fix: 最大灰阶索引
pattern_params: 8bit pattern 参数,用于计算 input_level
(与 22293 Gamma 数据对齐)
"""
results_with_gamma_list = pq_algorithm.calculate_gamma(
results, max_index_fix, pattern_params
)
L_bar = pq_algorithm.calculate_L_bar(results)
return results_with_gamma_list, L_bar

9
app/tests/gamut.py Normal file
View File

@@ -0,0 +1,9 @@
"""色域Gamut相关纯算法。"""
import algorithm.pq_algorithm as pq_algorithm
def calculate_gamut_coverage(results):
"""计算色域覆盖率DCI-P3"""
area, coverage = pq_algorithm.calculate_gamut_coverage_DCIP3(results)
return area, coverage