diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/resources.py b/app/resources.py new file mode 100644 index 0000000..e58cfea --- /dev/null +++ b/app/resources.py @@ -0,0 +1,55 @@ +"""资源与全局样式工具。 + +Step 0 重构:将原先散落在 pqAutomationApp.py 顶部的 +get_resource_path / load_icon / backgroud_style_set 三个辅助函数 +迁移到独立模块,保持行为完全一致。 +""" + +import os +import sys + +import ttkbootstrap as ttk +from PIL import Image, ImageTk + + +# 项目根目录(app/ 的上一级)。开发环境下用它作为资源查找基准, +# 从而兼容从项目根目录启动的 pqAutomationApp.py。 +_PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +def get_resource_path(relative_path): + """ + 获取资源文件的绝对路径(兼容开发环境和打包后) + + Args: + relative_path: 相对路径,如 "assets/cie.png" + + Returns: + str: 资源文件的绝对路径 + """ + try: + # PyInstaller 打包后的临时文件夹路径 + base_path = sys._MEIPASS + except AttributeError: + # 开发环境:使用项目根目录 + base_path = _PROJECT_ROOT + + return os.path.join(base_path, relative_path) + + +def load_icon(png_path): + """加载并调整图标大小为 24x24(原注释写 64x64,实际 resize 为 24x24,保持原行为)""" + img = Image.open(get_resource_path(png_path)) + img = img.resize((24, 24), Image.LANCZOS) + return ImageTk.PhotoImage(img) + + +def backgroud_style_set(): + style = ttk.Style() + # 移除背景色设置,使用默认背景色 + style.configure( + "SidebarSelected.TButton", + # anchor="w", + padding=10, + background="#005470", + ) diff --git a/app/tests/__init__.py b/app/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/tests/color_accuracy.py b/app/tests/color_accuracy.py new file mode 100644 index 0000000..76646b8 --- /dev/null +++ b/app/tests/color_accuracy.py @@ -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 → Lab(D65 白点)========== + 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 → XYZ(D65 白点,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 diff --git a/app/tests/eotf.py b/app/tests/eotf.py new file mode 100644 index 0000000..c7bd63e --- /dev/null +++ b/app/tests/eotf.py @@ -0,0 +1,40 @@ +"""EOTF(PQ / 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) diff --git a/app/tests/gamma.py b/app/tests/gamma.py new file mode 100644 index 0000000..2c750d6 --- /dev/null +++ b/app/tests/gamma.py @@ -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 diff --git a/app/tests/gamut.py b/app/tests/gamut.py new file mode 100644 index 0000000..2c30d6f --- /dev/null +++ b/app/tests/gamut.py @@ -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 diff --git a/pqAutomationApp.py b/pqAutomationApp.py index 2135651..a3e0d60 100644 --- a/pqAutomationApp.py +++ b/pqAutomationApp.py @@ -32,48 +32,26 @@ from colormath.color_conversions import convert_color from colormath.color_diff import delta_e_cie2000 from views.pq_debug_panel import PQDebugPanel +# Step 0/1 重构:资源工具和纯算法已迁移到 app/ 包,这里重新导入以保持 +# 对原函数名/方法名的向后兼容(老代码内部仍用 self.calculate_* 调用)。 +from app.resources import ( + backgroud_style_set, + get_resource_path, + load_icon, +) +from app.tests.color_accuracy import ( + calculate_color_accuracy as _calc_color_accuracy, + calculate_delta_e_2000 as _calc_delta_e_2000, + get_accuracy_color_standards as _get_accuracy_color_standards, +) +from app.tests.eotf import calculate_pq_curve as _calc_pq_curve +from app.tests.gamma import calculate_gamma as _calc_gamma +from app.tests.gamut import calculate_gamut_coverage as _calc_gamut_coverage + plt.rcParams["font.family"] = ["sans-serif"] plt.rcParams["font.sans-serif"] = ["Microsoft YaHei"] -def get_resource_path(relative_path): - """ - 获取资源文件的绝对路径(兼容开发环境和打包后) - - Args: - relative_path: 相对路径,如 "assets/cie.png" - - Returns: - str: 资源文件的绝对路径 - """ - try: - # PyInstaller 打包后的临时文件夹路径 - base_path = sys._MEIPASS - except AttributeError: - # 开发环境:使用脚本所在目录 - base_path = os.path.dirname(os.path.abspath(__file__)) - - return os.path.join(base_path, relative_path) - - -def load_icon(png_path): - """加载并调整图标大小为64x64""" - img = Image.open(get_resource_path(png_path)) - img = img.resize((24, 24), Image.LANCZOS) - return ImageTk.PhotoImage(img) - - -def backgroud_style_set(): - style = ttk.Style() - # 移除背景色设置,使用默认背景色 - style.configure( - "SidebarSelected.TButton", - # anchor="w", - padding=10, - background="#005470", - ) - - class PQAutomationApp: def __init__(self, root): self.root = root @@ -6727,149 +6705,11 @@ class PQAutomationApp: def calculate_delta_e_2000( self, 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 色差值 - """ - import math - - # ========== 1. xy → XYZ(使用实际亮度)========== - def xy_to_XYZ(x, y, 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 → Lab(D65 白点)========== - 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)) + """转发到 app.tests.color_accuracy.calculate_delta_e_2000(Step 1 重构)""" + return _calc_delta_e_2000( + measured_x, measured_y, measured_lv, standard_x, standard_y ) - 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 test_color_accuracy(self, test_type): """测试色准 - 使用手工实现的 ΔE 2000(应用 Gamma)""" @@ -7029,119 +6869,20 @@ class PQAutomationApp: self.log_gui.log("色准测试完成") def get_accuracy_color_standards(self, test_type): - """ - 获取色准测试的标准 xy 色度坐标(动态计算) - - Args: - test_type: 测试类型 ("sdr_movie" 或 "hdr_movie") - - Returns: - dict: {color_name: (x, y), ...} - """ - - # ========== RGB → xy 转换函数 ========== - def rgb_to_xy_srgb(r, g, b): - """sRGB (8bit) → CIE 1931 xy""" - # 1. 归一化到 0-1 - r, g, b = r / 255.0, g / 255.0, b / 255.0 - - # 2. sRGB Gamma 解码 - 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) - - # 3. sRGB → XYZ(D65 白点,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 - - # 4. XYZ → xy - total = X + Y + Z - if total == 0: - return 0.3127, 0.3290 # D65 白点 - - x = X / total - y = Y / total - return x, y - - # ========== 你的 RGB 定义(29色)========== - 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), - ] - - # ========== 动态计算 xy 坐标 ========== - standards = {} - for name, r, g, b in SDR_COLOR_PATTERNS: - x, y = rgb_to_xy_srgb(r, g, b) - standards[name] = (x, y) - - return standards + """转发到 app.tests.color_accuracy.get_accuracy_color_standards(Step 1 重构)""" + return _get_accuracy_color_standards(test_type) def calculate_gamut_coverage(self, results): - """计算色域覆盖率""" - area, coverage = pq_algorithm.calculate_gamut_coverage_DCIP3(results) - return area, coverage + """转发到 app.tests.gamut.calculate_gamut_coverage(Step 1 重构)""" + return _calc_gamut_coverage(results) def calculate_gamma(self, results, max_index_fix, pattern_params=None): - """计算Gamma值,返回results + gamma - - 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 + """转发到 app.tests.gamma.calculate_gamma(Step 1 重构)""" + return _calc_gamma(results, max_index_fix, pattern_params) def calculate_color_accuracy(self, measured, standard): - """计算色差""" - # 使用简化的色差计算方法 - 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 # 放大1000倍便于显示 - - return delta_E + """转发到 app.tests.color_accuracy.calculate_color_accuracy(Step 1 重构)""" + return _calc_color_accuracy(measured, standard) def plot_gamut(self, results, coverage, test_type): """绘制色域图 - 根据用户选择的参考标准动态计算覆盖率""" @@ -7946,42 +7687,8 @@ class PQAutomationApp: self.log_gui.log("EOTF 曲线 + 数据表格绘制完成") def calculate_pq_curve(self, gray_levels): - """计算 PQ (ST.2084) EOTF 理想曲线 - - Args: - gray_levels: 灰阶百分比数组 (0-100) - - Returns: - L_bar: 归一化亮度数组 (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: - # 归一化灰阶(0-1) - V = gray / 100.0 - - if V <= 0: - L_bar.append(0) - else: - # PQ 反向 EOTF 计算 - 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) + """转发到 app.tests.eotf.calculate_pq_curve(Step 1 重构)""" + return _calc_pq_curve(gray_levels) def plot_cct(self, test_type): """绘制 x 和 y 坐标分离图 - 每个点标注纵坐标值"""