添加信号格式修改

This commit is contained in:
xinzhu.yin
2026-05-22 11:31:36 +08:00
parent c42287b7d7
commit 9b2bc44e17
14 changed files with 434 additions and 356 deletions

View File

@@ -49,6 +49,7 @@ def plot_accuracy(self, accuracy_data, test_type):
fontsize=11, fontsize=11,
y=0.98, y=0.98,
fontweight="bold", fontweight="bold",
color="#111111",
) )
# ========== 29色6行5列布局 ========== # ========== 29色6行5列布局 ==========
@@ -205,6 +206,7 @@ def plot_accuracy(self, accuracy_data, test_type):
va="top", va="top",
fontsize=7.5, fontsize=7.5,
fontweight="bold", fontweight="bold",
color="#111111",
transform=self.accuracy_ax.transAxes, transform=self.accuracy_ax.transAxes,
) )
@@ -228,6 +230,7 @@ def plot_accuracy(self, accuracy_data, test_type):
va="center", va="center",
fontsize=7, fontsize=7,
fontweight="bold", fontweight="bold",
color="#111111",
transform=self.accuracy_ax.transAxes, transform=self.accuracy_ax.transAxes,
) )
@@ -279,11 +282,11 @@ def plot_accuracy(self, accuracy_data, test_type):
grade_color = "darkgreen" grade_color = "darkgreen"
elif avg_delta_e < 3: elif avg_delta_e < 3:
grade = "优秀" grade = "优秀"
grade_icon = "✓✓" grade_icon = "OK"
grade_color = "green" grade_color = "green"
elif avg_delta_e < 5: elif avg_delta_e < 5:
grade = "良好" grade = "良好"
grade_icon = "" grade_icon = "PASS"
grade_color = "orange" grade_color = "orange"
else: else:
grade = "需要校准" grade = "需要校准"
@@ -298,6 +301,7 @@ def plot_accuracy(self, accuracy_data, test_type):
va="bottom", va="bottom",
fontsize=7, fontsize=7,
fontweight="bold", fontweight="bold",
color="#111111",
transform=self.accuracy_ax.transAxes, transform=self.accuracy_ax.transAxes,
) )

View File

@@ -143,12 +143,10 @@ def run_custom_sdr_test(self, test_items):
self.log_gui.log("执行客户定制 SDR 测试...", level="info") self.log_gui.log("执行客户定制 SDR 测试...", level="info")
# 获取信号格式设置 # 获取信号格式设置
color_space = self.sdr_color_space_var.get() # BT.709/BT.601/BT.2020 color_space = self.sdr_color_space_var.get() # BT.709/BT.601/BT.2020
gamma_type = self.sdr_gamma_type_var.get() # 2.2/2.4/2.6
data_range = self.sdr_data_range_var.get() # Full/Limited data_range = self.sdr_data_range_var.get() # Full/Limited
bit_depth = self.sdr_bit_depth_var.get() # 8bit/10bit/12bit bit_depth = self.sdr_bit_depth_var.get() # 8bit/10bit/12bit
self.log_gui.log(f"信号格式: 色彩空间={color_space}, Gamma={gamma_type}", level="info") self.log_gui.log(f"信号格式: 色彩空间={color_space}, 数据范围={data_range}, 编码位深={bit_depth}", level="info")
self.log_gui.log(f" 数据范围={data_range}, 编码位深={bit_depth}", level="info")
self.log_gui.log("开始统一采集灰阶数据(用于 Gamma/CCT/对比度测试)", level="info") self.log_gui.log("开始统一采集灰阶数据(用于 Gamma/CCT/对比度测试)", level="info")
self.test_custom_sdr() self.test_custom_sdr()
@@ -168,12 +166,10 @@ def run_sdr_movie_test(self, test_items):
# 获取信号格式设置 # 获取信号格式设置
color_space = self.sdr_color_space_var.get() # BT.709/BT.601/BT.2020 color_space = self.sdr_color_space_var.get() # BT.709/BT.601/BT.2020
gamma_type = self.sdr_gamma_type_var.get() # 2.2/2.4/2.6
data_range = self.sdr_data_range_var.get() # Full/Limited data_range = self.sdr_data_range_var.get() # Full/Limited
bit_depth = self.sdr_bit_depth_var.get() # 8bit/10bit/12bit bit_depth = self.sdr_bit_depth_var.get() # 8bit/10bit/12bit
self.log_gui.log(f"信号格式: 色彩空间={color_space}, Gamma={gamma_type}", level="info") self.log_gui.log(f"信号格式: 色彩空间={color_space}, 数据范围={data_range}, 编码位深={bit_depth}", level="info")
self.log_gui.log(f" 数据范围={data_range}, 编码位深={bit_depth}", level="info")
# 判断是否需要灰阶数据 # 判断是否需要灰阶数据
needs_gray_data = any( needs_gray_data = any(
@@ -313,7 +309,20 @@ def send_fix_pattern(self, mode):
self.log_gui.log("=" * 50, level="separator") self.log_gui.log("=" * 50, level="separator")
# 4. 循环发送图案并采集数据 # 信号格式设置后等待电视重新锁定 HDMI 信号
# format_changed=True 表示本次 set_video_mode 的参数与上次不同TV 需要重新锁定
format_changed = getattr(getattr(self, "ucd", None), "format_changed", True)
if format_changed:
signal_settle = max(1.0, float(getattr(self, "signal_settle_time", 5.0)))
self.log_gui.log(
f"信号格式已变化,等待电视重新锁定: {signal_settle:.1f}s可通过 signal_settle_time 调整)",
level="info",
)
else:
signal_settle = 0.5
self.log_gui.log("信号格式未变化,短暂等待: 0.5s", level="info")
time.sleep(signal_settle)
total_patterns = session.total_patterns total_patterns = session.total_patterns
self.log_gui.log(f"开始采集数据,共 {total_patterns} 个图案", level="info") self.log_gui.log(f"开始采集数据,共 {total_patterns} 个图案", level="info")
settle_time = max(0.2, float(getattr(self, "pattern_settle_time", 1.0))) settle_time = max(0.2, float(getattr(self, "pattern_settle_time", 1.0)))
@@ -332,7 +341,8 @@ def send_fix_pattern(self, mode):
return results return results
should_log_detail = ( should_log_detail = (
i == 0 total_patterns <= progress_step
or i == 0
or (i + 1) == total_patterns or (i + 1) == total_patterns
or ((i + 1) % progress_step == 0) or ((i + 1) % progress_step == 0)
) )
@@ -477,12 +487,14 @@ def test_gamut(self, test_type):
# SDR 测试:使用色彩空间设置 # SDR 测试:使用色彩空间设置
color_space = self.sdr_color_space_var.get() color_space = self.sdr_color_space_var.get()
if color_space == "BT.709": if color_space in ("sRGB", "BT.709"):
reference_standard = "BT.709" reference_standard = "BT.709"
elif color_space == "BT.601": elif color_space == "BT.601":
reference_standard = "BT.601" reference_standard = "BT.601"
elif color_space == "BT.2020": elif color_space == "BT.2020":
reference_standard = "BT.2020" reference_standard = "BT.2020"
elif color_space == "DCI-P3":
reference_standard = "DCI-P3"
else: else:
reference_standard = "BT.709" reference_standard = "BT.709"
self.log_gui.log( self.log_gui.log(
@@ -665,7 +677,7 @@ def test_gamma(self, test_type, gray_data=None):
"gamma", {"gamma": results_with_gamma_list, "L_bar": L_bar} "gamma", {"gamma": results_with_gamma_list, "L_bar": L_bar}
) )
# 绘制Gamma曲线 # 绘制Gamma曲线SDR 使用用户选择的参考值)
if test_type == "sdr_movie": if test_type == "sdr_movie":
try: try:
target_gamma = float(self.sdr_gamma_type_var.get()) target_gamma = float(self.sdr_gamma_type_var.get())
@@ -673,7 +685,6 @@ def test_gamma(self, test_type, gray_data=None):
target_gamma = 2.2 target_gamma = 2.2
else: else:
target_gamma = 2.2 target_gamma = 2.2
self.plot_gamma(L_bar, results_with_gamma_list, target_gamma, test_type) self.plot_gamma(L_bar, results_with_gamma_list, target_gamma, test_type)
self._save_chart_snapshot(test_type, "gamma", (L_bar, results_with_gamma_list, target_gamma, test_type)) self._save_chart_snapshot(test_type, "gamma", (L_bar, results_with_gamma_list, target_gamma, test_type))
@@ -877,7 +888,7 @@ def test_contrast(self, test_type, gray_data=None):
def test_color_accuracy(self, test_type): def test_color_accuracy(self, test_type):
"""测试色准 - 使用手工实现的 ΔE 2000应用 Gamma""" """测试色准 - 使用手工实现的 ΔE 2000应用 Gamma"""
# ========== 读取用户选择的 Gamma ========== # ========== Gamma 参考值 ==========
if test_type == "sdr_movie": if test_type == "sdr_movie":
try: try:
target_gamma = float(self.sdr_gamma_type_var.get()) target_gamma = float(self.sdr_gamma_type_var.get())
@@ -886,7 +897,7 @@ def test_color_accuracy(self, test_type):
self.log_gui.log("=" * 50, level="separator") self.log_gui.log("=" * 50, level="separator")
self.log_gui.log(f"开始测试色准SDR Movie 标准 - 29色", level="info") self.log_gui.log(f"开始测试色准SDR Movie 标准 - 29色", level="info")
self.log_gui.log(f"使用 Gamma: {target_gamma}", level="success") # ← 新增 self.log_gui.log(f"使用 Gamma: {target_gamma}", level="success")
self.log_gui.log("=" * 50, level="separator") self.log_gui.log("=" * 50, level="separator")
elif test_type == "hdr_movie": elif test_type == "hdr_movie":
@@ -962,9 +973,9 @@ def test_color_accuracy(self, test_type):
color_patches.append(name) color_patches.append(name)
if delta_e < 3: if delta_e < 3:
grade, icon = "优秀", "" grade, icon = "优秀", "OK"
elif delta_e < 5: elif delta_e < 5:
grade, icon = "良好", "" grade, icon = "良好", "WARN"
else: else:
grade, icon = "偏差", "[Error]" grade, icon = "偏差", "[Error]"

View File

@@ -16,6 +16,7 @@ import json
import logging import logging
import mimetypes import mimetypes
import os import os
import sys
import shutil import shutil
import threading import threading
import time import time
@@ -253,8 +254,13 @@ def _call_pqtest_generate(user_message: str, session_id: str, timeout: float = A
def get_cache_dir(base_dir: Optional[str] = None) -> str: def get_cache_dir(base_dir: Optional[str] = None) -> str:
"""返回缓存目录,如不存在则创建。``base_dir`` 默认使用当前工作目录。""" """返回缓存目录,如不存在则创建。``base_dir`` 留空时使用应用根目录。"""
root = base_dir if base_dir else os.getcwd() if base_dir:
root = base_dir
elif getattr(sys, "frozen", False):
root = os.path.dirname(sys.executable)
else:
root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
path = os.path.join(root, _CACHE_DIRNAME) path = os.path.join(root, _CACHE_DIRNAME)
os.makedirs(path, exist_ok=True) os.makedirs(path, exist_ok=True)
return path return path

View File

@@ -40,56 +40,72 @@ class PatternService:
"info", "info",
) )
self.app.ucd.set_ucd_params(active_config) self.app.ucd.set_ucd_params(active_config)
elif test_type == "sdr_movie": elif test_type == "sdr_movie":
active_config = self._prepare_video_session( data_range = self.app.sdr_data_range_var.get()
mode=mode, if log_details:
test_type=test_type, self._log("=" * 50, "separator")
source_params=source_params, self._log("设置 SDR 信号格式:", "info")
data_range=self.app.sdr_data_range_var.get(), self._log("=" * 50, "separator")
log_title="设置 SDR 信号格式:", for label, value in [
setup_message=f"设置 SDR 测试图案({mode} 模式)..."
if mode != "accuracy"
else "设置 SDR 29色色准测试图案...",
setup_format=lambda: self.app.ucd.set_sdr_format(
color_space=self.app.sdr_color_space_var.get(),
gamma=self.app.sdr_gamma_type_var.get(),
data_range=self.app.sdr_data_range_var.get(),
bit_depth=self.app.sdr_bit_depth_var.get(),
),
log_items=[
("色彩空间", self.app.sdr_color_space_var.get()), ("色彩空间", self.app.sdr_color_space_var.get()),
("Gamma", self.app.sdr_gamma_type_var.get()), ("色彩格式", self.app.sdr_output_format_var.get()),
("数据范围", self.app.sdr_data_range_var.get()), ("Gamma", self.app.sdr_gamma_type_var.get()),
("数据范围", data_range),
("编码位深", self.app.sdr_bit_depth_var.get()), ("编码位深", self.app.sdr_bit_depth_var.get()),
], ]:
log_details=log_details, self._log(f" {label}: {value}", "info")
converted_params = convert_pattern_params(
source_params, data_range=data_range, verbose=False
) )
active_config = self.app.config.get_temp_config_with_converted_params(
mode=mode, converted_params=converted_params
)
self.app.ucd.set_ucd_params(active_config)
success = self.app.ucd.apply_signal_format(
color_space=self.app.sdr_color_space_var.get(),
data_range=data_range,
bit_depth=self.app.sdr_bit_depth_var.get(),
color_format=self.app.sdr_output_format_var.get(),
)
if log_details:
self._log(f"SDR 信号格式设置{'成功' if success else '失败'}", "success" if success else "error")
self._log(f"图案参数已设置,共 {len(converted_params)} 个图案", "success")
elif test_type == "hdr_movie": elif test_type == "hdr_movie":
active_config = self._prepare_video_session( data_range = self.app.hdr_data_range_var.get()
mode=mode, if log_details:
test_type=test_type, self._log("=" * 50, "separator")
source_params=source_params, self._log("设置 HDR 信号格式:", "info")
data_range=self.app.hdr_data_range_var.get(), self._log("=" * 50, "separator")
log_title="设置 HDR 信号格式:", for label, value in [
setup_message=f"设置 HDR 测试图案({mode} 模式)..."
if mode != "accuracy"
else "设置 HDR 29色色准测试图案...",
setup_format=lambda: self.app.ucd.set_hdr_format(
color_space=self.app.hdr_color_space_var.get(),
data_range=self.app.hdr_data_range_var.get(),
bit_depth=self.app.hdr_bit_depth_var.get(),
max_cll=self.app.hdr_maxcll_var.get(),
max_fall=self.app.hdr_maxfall_var.get(),
),
log_items=[
("色彩空间", self.app.hdr_color_space_var.get()), ("色彩空间", self.app.hdr_color_space_var.get()),
("数据范围", self.app.hdr_data_range_var.get()), ("色彩格式", self.app.hdr_output_format_var.get()),
("数据范围", data_range),
("编码位深", self.app.hdr_bit_depth_var.get()), ("编码位深", self.app.hdr_bit_depth_var.get()),
("MaxCLL", self.app.hdr_maxcll_var.get()), ("MaxCLL", self.app.hdr_maxcll_var.get()),
("MaxFALL", self.app.hdr_maxfall_var.get()), ("MaxFALL", self.app.hdr_maxfall_var.get()),
], ]:
log_details=log_details, self._log(f" {label}: {value}", "info")
converted_params = convert_pattern_params(
source_params, data_range=data_range, verbose=False
) )
active_config = self.app.config.get_temp_config_with_converted_params(
mode=mode, converted_params=converted_params
)
self.app.ucd.set_ucd_params(active_config)
success = self.app.ucd.apply_signal_format(
color_space=self.app.hdr_color_space_var.get(),
data_range=data_range,
bit_depth=self.app.hdr_bit_depth_var.get(),
color_format=self.app.hdr_output_format_var.get(),
max_cll=self.app.hdr_maxcll_var.get(),
max_fall=self.app.hdr_maxfall_var.get(),
)
if log_details:
self._log(f"HDR 信号格式设置{'成功' if success else '失败'}", "success" if success else "error")
self._log(f"图案参数已设置,共 {len(converted_params)} 个图案", "success")
else: else:
raise ValueError(f"不支持的测试类型: {test_type}") raise ValueError(f"不支持的测试类型: {test_type}")
@@ -122,50 +138,6 @@ class PatternService:
send_solid_rgb_pattern(self.app.ucd, converted_rgb, raise_on_error=True) send_solid_rgb_pattern(self.app.ucd, converted_rgb, raise_on_error=True)
return True return True
def _prepare_video_session(
self,
*,
mode,
test_type,
source_params,
data_range,
log_title,
setup_message,
setup_format,
log_items,
log_details,
):
if log_details:
self._log("=" * 50, "separator")
self._log(log_title, "info")
self._log("=" * 50, "separator")
for label, value in log_items:
self._log(f" {label}: {value}", "info")
success = setup_format()
if log_details:
self._log(
f"{test_type.split('_')[0].upper()} 信号格式设置{'成功' if success else '失败'}",
"success" if success else "error",
)
self._log(setup_message, "info")
converted_params = convert_pattern_params(
pattern_params=source_params,
data_range=data_range,
verbose=False,
)
active_config = self.app.config.get_temp_config_with_converted_params(
mode=mode,
converted_params=converted_params,
)
self.app.ucd.set_ucd_params(active_config)
if log_details:
self._log(f"图案参数已设置,共 {len(converted_params)} 个图案", "success")
return active_config
def _get_source_pattern_params(self, mode): def _get_source_pattern_params(self, mode):
return copy.deepcopy(get_pattern(mode)["pattern_params"]) return copy.deepcopy(get_pattern(mode)["pattern_params"])

View File

@@ -3,6 +3,7 @@
from __future__ import annotations from __future__ import annotations
import os import os
import sys
import threading import threading
import tkinter as tk import tkinter as tk
from tkinter import filedialog, messagebox, simpledialog from tkinter import filedialog, messagebox, simpledialog
@@ -193,6 +194,15 @@ def toggle_ai_image_panel(self):
self.show_panel("ai_image") self.show_panel("ai_image")
def _get_app_base_dir(self) -> str:
"""返回应用根目录settings 的上一级)。"""
if getattr(self, "config_file", None):
return os.path.dirname(os.path.dirname(self.config_file))
if getattr(sys, "frozen", False):
return os.path.dirname(sys.executable)
return os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
# ---------------- 列表 / 选中 ---------------- # ---------------- 列表 / 选中 ----------------
@@ -203,7 +213,7 @@ def reload_ai_image_list(self, auto_select_first=True):
其下列出该轮生成的所有图片。会话按"最近使用"倒序,组内按时间倒序。 其下列出该轮生成的所有图片。会话按"最近使用"倒序,组内按时间倒序。
auto_select_first: 是否自动选中第一张图片(默认 True auto_select_first: 是否自动选中第一张图片(默认 True
""" """
self.ai_image_records = _svc.list_records() self.ai_image_records = _svc.list_records(base_dir=_get_app_base_dir(self))
self.ai_image_listbox.delete(0, tk.END) self.ai_image_listbox.delete(0, tk.END)
# 维护行号 → 记录索引的映射;分隔头处为 None # 维护行号 → 记录索引的映射;分隔头处为 None
self._ai_image_row_map = [] self._ai_image_row_map = []
@@ -414,6 +424,7 @@ def _send_prompt(self):
prompt, prompt,
on_success=_success, on_success=_success,
on_error=_error, on_error=_error,
base_dir=_get_app_base_dir(self),
cancel_event=self._ai_image_cancel_event, cancel_event=self._ai_image_cancel_event,
) )
return return
@@ -422,6 +433,7 @@ def _send_prompt(self):
prompt, prompt,
on_success=_success, on_success=_success,
on_error=_error, on_error=_error,
base_dir=_get_app_base_dir(self),
cancel_event=self._ai_image_cancel_event, cancel_event=self._ai_image_cancel_event,
) )

View File

@@ -137,21 +137,21 @@ def create_signal_format_content(self):
sdr_color_space_combo = ttk.Combobox( sdr_color_space_combo = ttk.Combobox(
self.sdr_signal_frame, self.sdr_signal_frame,
textvariable=self.sdr_color_space_var, textvariable=self.sdr_color_space_var,
values=["BT.709", "BT.601", "BT.2020"], values=["sRGB", "BT.709", "BT.601", "BT.2020", "DCI-P3"],
width=10, width=10,
state="readonly", state="readonly",
) )
sdr_color_space_combo.grid(row=0, column=1, sticky=tk.W, padx=5, pady=2) sdr_color_space_combo.grid(row=0, column=1, sticky=tk.W, padx=5, pady=2)
# Gamma # Gamma测试参考值用于Gamma曲线绘制和色准计算
ttk.Label(self.sdr_signal_frame, text="Gamma:").grid( ttk.Label(self.sdr_signal_frame, text="Gamma:").grid(
row=1, column=0, sticky=tk.W, padx=5, pady=2 row=1, column=0, sticky=tk.W, padx=5, pady=2
) )
self.sdr_gamma_type_var = tk.StringVar(value="2.2") self.sdr_gamma_type_var = tk.StringVar(value=UCDEnum.SignalFormat.GammaType.GAMMA_22)
sdr_gamma_combo = ttk.Combobox( sdr_gamma_combo = ttk.Combobox(
self.sdr_signal_frame, self.sdr_signal_frame,
textvariable=self.sdr_gamma_type_var, textvariable=self.sdr_gamma_type_var,
values=["2.2", "2.4", "2.6"], values=UCDEnum.SignalFormat.GammaType.get_list(),
width=10, width=10,
state="readonly", state="readonly",
) )
@@ -161,11 +161,11 @@ def create_signal_format_content(self):
ttk.Label(self.sdr_signal_frame, text="数据范围:").grid( ttk.Label(self.sdr_signal_frame, text="数据范围:").grid(
row=2, column=0, sticky=tk.W, padx=5, pady=2 row=2, column=0, sticky=tk.W, padx=5, pady=2
) )
self.sdr_data_range_var = tk.StringVar(value="Full") self.sdr_data_range_var = tk.StringVar(value=UCDEnum.SignalFormat.DataRange.FULL)
sdr_range_combo = ttk.Combobox( sdr_range_combo = ttk.Combobox(
self.sdr_signal_frame, self.sdr_signal_frame,
textvariable=self.sdr_data_range_var, textvariable=self.sdr_data_range_var,
values=["Full", "Limited"], values=UCDEnum.SignalFormat.DataRange.get_list(),
width=10, width=10,
state="readonly", state="readonly",
) )
@@ -175,16 +175,50 @@ def create_signal_format_content(self):
ttk.Label(self.sdr_signal_frame, text="编码位深:").grid( ttk.Label(self.sdr_signal_frame, text="编码位深:").grid(
row=3, column=0, sticky=tk.W, padx=5, pady=2 row=3, column=0, sticky=tk.W, padx=5, pady=2
) )
self.sdr_bit_depth_var = tk.StringVar(value="8bit") self.sdr_bit_depth_var = tk.StringVar(value=UCDEnum.SignalFormat.BitDepth.BIT_8)
sdr_bit_depth_combo = ttk.Combobox( sdr_bit_depth_combo = ttk.Combobox(
self.sdr_signal_frame, self.sdr_signal_frame,
textvariable=self.sdr_bit_depth_var, textvariable=self.sdr_bit_depth_var,
values=["8bit", "10bit", "12bit"], values=UCDEnum.SignalFormat.BitDepth.get_list(),
width=10, width=10,
state="readonly", state="readonly",
) )
sdr_bit_depth_combo.grid(row=3, column=1, sticky=tk.W, padx=5, pady=2) sdr_bit_depth_combo.grid(row=3, column=1, sticky=tk.W, padx=5, pady=2)
# 分辨率
ttk.Label(self.sdr_signal_frame, text="分辨率:").grid(
row=4, column=0, sticky=tk.W, padx=5, pady=2
)
self.sdr_timing_var = tk.StringVar(
value=self.config.current_test_types.get("sdr_movie", {}).get(
"timing", "DMT 1920x1080@60Hz"
)
)
sdr_timing_combo = ttk.Combobox(
self.sdr_signal_frame,
textvariable=self.sdr_timing_var,
values=UCDEnum.TimingInfo.get_formatted_resolution_list(),
width=20,
state="readonly",
)
sdr_timing_combo.bind("<<ComboboxSelected>>", self.on_sdr_timing_changed)
sdr_timing_combo.grid(row=4, column=1, sticky=tk.W, padx=5, pady=2)
# 色彩格式
ttk.Label(self.sdr_signal_frame, text="色彩格式:").grid(
row=5, column=0, sticky=tk.W, padx=5, pady=2
)
self.sdr_output_format_var = tk.StringVar(value=UCDEnum.SignalFormat.OutputFormat.RGB)
sdr_output_format_combo = ttk.Combobox(
self.sdr_signal_frame,
textvariable=self.sdr_output_format_var,
values=UCDEnum.SignalFormat.OutputFormat.get_list(),
width=10,
state="readonly",
)
sdr_output_format_combo.bind("<<ComboboxSelected>>", self.on_sdr_output_format_changed)
sdr_output_format_combo.grid(row=5, column=1, sticky=tk.W, padx=5, pady=2)
# ==================== HDR信号格式设置 ==================== # ==================== HDR信号格式设置 ====================
self.hdr_signal_frame = ttk.Frame(self.signal_tabs) self.hdr_signal_frame = ttk.Frame(self.signal_tabs)
# 配置列权重 # 配置列权重
@@ -235,11 +269,11 @@ def create_signal_format_content(self):
ttk.Label(self.hdr_signal_frame, text="数据范围:").grid( ttk.Label(self.hdr_signal_frame, text="数据范围:").grid(
row=3, column=0, sticky=tk.W, padx=5, pady=2 row=3, column=0, sticky=tk.W, padx=5, pady=2
) )
self.hdr_data_range_var = tk.StringVar(value="Full") self.hdr_data_range_var = tk.StringVar(value=UCDEnum.SignalFormat.DataRange.FULL)
hdr_range_combo = ttk.Combobox( hdr_range_combo = ttk.Combobox(
self.hdr_signal_frame, self.hdr_signal_frame,
textvariable=self.hdr_data_range_var, textvariable=self.hdr_data_range_var,
values=["Full", "Limited"], values=UCDEnum.SignalFormat.DataRange.get_list(),
width=10, width=10,
state="readonly", state="readonly",
) )
@@ -249,16 +283,31 @@ def create_signal_format_content(self):
ttk.Label(self.hdr_signal_frame, text="编码位深:").grid( ttk.Label(self.hdr_signal_frame, text="编码位深:").grid(
row=4, column=0, sticky=tk.W, padx=5, pady=2 row=4, column=0, sticky=tk.W, padx=5, pady=2
) )
self.hdr_bit_depth_var = tk.StringVar(value="8bit") self.hdr_bit_depth_var = tk.StringVar(value=UCDEnum.SignalFormat.BitDepth.BIT_8)
hdr_bit_depth_combo = ttk.Combobox( hdr_bit_depth_combo = ttk.Combobox(
self.hdr_signal_frame, self.hdr_signal_frame,
textvariable=self.hdr_bit_depth_var, textvariable=self.hdr_bit_depth_var,
values=["8bit", "10bit", "12bit"], values=UCDEnum.SignalFormat.BitDepth.get_list(),
width=10, width=10,
state="readonly", state="readonly",
) )
hdr_bit_depth_combo.grid(row=4, column=1, sticky=tk.W, padx=5, pady=2) hdr_bit_depth_combo.grid(row=4, column=1, sticky=tk.W, padx=5, pady=2)
# 色彩格式
ttk.Label(self.hdr_signal_frame, text="色彩格式:").grid(
row=5, column=0, sticky=tk.W, padx=5, pady=2
)
self.hdr_output_format_var = tk.StringVar(value=UCDEnum.SignalFormat.OutputFormat.RGB)
hdr_output_format_combo = ttk.Combobox(
self.hdr_signal_frame,
textvariable=self.hdr_output_format_var,
values=UCDEnum.SignalFormat.OutputFormat.get_list(),
width=10,
state="readonly",
)
hdr_output_format_combo.bind("<<ComboboxSelected>>", self.on_hdr_output_format_changed)
hdr_output_format_combo.grid(row=5, column=1, sticky=tk.W, padx=5, pady=2)
# ==================== 初始化:默认只启用屏模组 Tab ==================== # ==================== 初始化:默认只启用屏模组 Tab ====================
self.signal_tabs.select(0) # 选中屏模组 self.signal_tabs.select(0) # 选中屏模组
self.signal_tabs.tab(1, state="disabled") # 禁用 SDR self.signal_tabs.tab(1, state="disabled") # 禁用 SDR
@@ -271,8 +320,9 @@ def create_connection_content(self):
com_frame = ttk.Frame(self.connection_frame) com_frame = ttk.Frame(self.connection_frame)
com_frame.pack(fill=tk.X, pady=5) com_frame.pack(fill=tk.X, pady=5)
# 获取可用的COM端口列表 # 获取可用的COM端口列表和UCD设备列表
available_ports = self.get_available_com_ports() available_ports = self.get_available_com_ports()
available_ucd_list = self.get_available_ucd_ports()
# 使用网格布局,更整齐 # 使用网格布局,更整齐
ttk.Label(com_frame, text="UCD列表:").grid( ttk.Label(com_frame, text="UCD列表:").grid(
@@ -282,7 +332,7 @@ def create_connection_content(self):
self.ucd_list_combo = ttk.Combobox( self.ucd_list_combo = ttk.Combobox(
com_frame, com_frame,
textvariable=self.ucd_list_var, textvariable=self.ucd_list_var,
values=available_ports, values=available_ucd_list,
width=10, width=10,
state="readonly", state="readonly",
) )
@@ -453,6 +503,19 @@ def create_test_type_frame(self):
) )
self.pantone_baseline_btn.pack(fill=tk.X, padx=0, pady=1) self.pantone_baseline_btn.pack(fill=tk.X, padx=0, pady=1)
# 测试版水印标签(版本 x.x.0.0 时显示)
from app_version import is_beta_version, APP_VERSION
if is_beta_version():
beta_lbl = tk.Label(
self.sidebar_frame,
text=f"[测试版] v{APP_VERSION}",
foreground="#ffffff",
background="#cc3300",
font=("微软雅黑", 8, "bold"),
anchor="center",
)
beta_lbl.pack(fill=tk.X, side=tk.BOTTOM, padx=4, pady=(6, 4))
# 注册面板按钮 # 注册面板按钮
if hasattr(self, "panels"): if hasattr(self, "panels"):
if "log" in self.panels: if "log" in self.panels:
@@ -569,6 +632,74 @@ def on_screen_module_timing_changed(self, event=None):
self.log_gui.log(f"屏模组信号格式更改失败: {str(e)}", level="error") self.log_gui.log(f"屏模组信号格式更改失败: {str(e)}", level="error")
def on_sdr_timing_changed(self, event=None):
"""SDR测试分辨率改变时的回调"""
try:
selected_timing = self.sdr_timing_var.get()
self.log_gui.log(f"SDR测试分辨率已更改为: {selected_timing}", level="info")
# 直接更新 sdr_movie 的 timing 配置
self.config.current_test_types["sdr_movie"]["timing"] = selected_timing
if self.testing:
self.log_gui.log("警告: 测试进行中,分辨率更改将在下次测试时生效", level="error")
self.save_pq_config()
except Exception as e:
self.log_gui.log(f"SDR测试分辨率更改失败: {str(e)}", level="error")
def on_sdr_output_format_changed(self, event=None):
"""SDR 色彩格式改变时的回调"""
try:
fmt = self.sdr_output_format_var.get()
self.log_gui.log(f"SDR色彩格式已更改为: {fmt}", level="info")
if self.testing:
self.log_gui.log("警告: 测试进行中,格式更改将在下次测试时生效", level="error")
return
if getattr(self.ucd, "status", False):
ok = self.ucd.apply_signal_format(
color_space=self.sdr_color_space_var.get(),
data_range=self.sdr_data_range_var.get(),
bit_depth=self.sdr_bit_depth_var.get(),
color_format=fmt,
)
if not ok:
self.log_gui.log("SDR色彩格式应用到UCD失败", level="error")
except Exception as e:
self.log_gui.log(f"SDR色彩格式更改失败: {str(e)}", level="error")
def on_hdr_output_format_changed(self, event=None):
"""HDR 色彩格式改变时的回调"""
try:
fmt = self.hdr_output_format_var.get()
self.log_gui.log(f"HDR色彩格式已更改为: {fmt}", level="info")
if self.testing:
self.log_gui.log("警告: 测试进行中,格式更改将在下次测试时生效", level="error")
return
if getattr(self.ucd, "status", False):
ok = self.ucd.apply_signal_format(
color_space=self.hdr_color_space_var.get(),
data_range=self.hdr_data_range_var.get(),
bit_depth=self.hdr_bit_depth_var.get(),
max_cll=self.hdr_maxcll_var.get(),
max_fall=self.hdr_maxfall_var.get(),
color_format=fmt,
)
if not ok:
self.log_gui.log("HDR色彩格式应用到UCD失败", level="error")
except Exception as e:
self.log_gui.log(f"HDR色彩格式更改失败: {str(e)}", level="error")
def update_test_items(self): def update_test_items(self):
"""根据当前测试类型更新测试项目复选框""" """根据当前测试类型更新测试项目复选框"""
# 先隐藏所有测试项目框架 # 先隐藏所有测试项目框架

View File

@@ -4,6 +4,7 @@ from __future__ import annotations
import datetime import datetime
import os import os
import sys
import threading import threading
import tkinter as tk import tkinter as tk
from tkinter import filedialog, messagebox from tkinter import filedialog, messagebox
@@ -11,7 +12,7 @@ from tkinter import filedialog, messagebox
import ttkbootstrap as ttk import ttkbootstrap as ttk
_TEMPLATE_FILE = "pantone\xa02670\xa0colors.xlsx" _TEMPLATE_FILE = "pantone_2670_colors.xlsx"
def create_pantone_baseline_panel(self): def create_pantone_baseline_panel(self):
@@ -153,8 +154,22 @@ def toggle_pantone_baseline_panel(self):
self.show_panel("pantone_baseline") self.show_panel("pantone_baseline")
def _get_settings_dir(self):
"""返回 settings 绝对目录,避免依赖当前工作目录。"""
if getattr(self, "config_file", None):
return os.path.dirname(self.config_file)
if getattr(sys, "frozen", False):
base_dir = os.path.dirname(sys.executable)
else:
base_dir = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
)
return os.path.join(base_dir, "settings")
def _load_patterns(self): def _load_patterns(self):
path = os.path.join("settings", _TEMPLATE_FILE) path = os.path.join(_get_settings_dir(self), _TEMPLATE_FILE)
if not os.path.isfile(path): if not os.path.isfile(path):
raise FileNotFoundError(f"未找到模板文件: {path}") raise FileNotFoundError(f"未找到模板文件: {path}")
@@ -513,7 +528,7 @@ def _auto_save_template(self):
def _write_template_xlsx(self, path): def _write_template_xlsx(self, path):
# 优先复制 settings 模板,再覆盖数据区;没有模板时自动创建同结构表。 # 优先复制 settings 模板,再覆盖数据区;没有模板时自动创建同结构表。
template_path = os.path.join("settings", _TEMPLATE_FILE) template_path = os.path.join(_get_settings_dir(self), _TEMPLATE_FILE)
from openpyxl import load_workbook, Workbook from openpyxl import load_workbook, Workbook
if os.path.isfile(template_path): if os.path.isfile(template_path):

View File

@@ -1,6 +1,15 @@
APP_NAME = "PQ 自动化测试工具" APP_NAME = "PQ 自动化测试工具"
APP_VERSION = "0.1.0" APP_VERSION = "106.26.0.0"
def is_beta_version(version: str = APP_VERSION) -> bool:
"""版本号第3、4段均为 '0' 时(格式 x.x.0.0)判定为测试版。"""
parts = version.split(".")
if len(parts) >= 4:
return parts[2] == "0" and parts[3] == "0"
return False
def get_app_title(): def get_app_title():
return f"{APP_NAME} v{APP_VERSION}" suffix = " [测试版]" if is_beta_version() else ""
return f"{APP_NAME} v{APP_VERSION}{suffix}"

View File

@@ -522,11 +522,7 @@ class UCDEnum:
@staticmethod @staticmethod
def get_list(): def get_list():
return [ return ["2.2", "2.4", "2.6"]
SignalFormat.GammaType.GAMMA_22,
SignalFormat.GammaType.GAMMA_24,
SignalFormat.GammaType.GAMMA_26,
]
@staticmethod @staticmethod
def get_gamma_value(gamma_str): def get_gamma_value(gamma_str):
@@ -542,12 +538,7 @@ class UCDEnum:
@staticmethod @staticmethod
def get_list(): def get_list():
return [SignalFormat.DataRange.FULL, SignalFormat.DataRange.LIMITED] return ["Full", "Limited"]
@staticmethod
def is_full_range(range_str):
"""判断是否为 Full Range"""
return range_str == SignalFormat.DataRange.FULL
class BitDepth: class BitDepth:
"""编码位深枚举""" """编码位深枚举"""
@@ -558,11 +549,7 @@ class UCDEnum:
@staticmethod @staticmethod
def get_list(): def get_list():
return [ return ["8bit", "10bit", "12bit"]
SignalFormat.BitDepth.BIT_8,
SignalFormat.BitDepth.BIT_10,
SignalFormat.BitDepth.BIT_12,
]
@staticmethod @staticmethod
def get_bit_value(bit_str): def get_bit_value(bit_str):
@@ -583,8 +570,44 @@ class UCDEnum:
@staticmethod @staticmethod
def get_maxcll_list(): def get_maxcll_list():
return SignalFormat.HDRMetadata.MAX_CLL_OPTIONS return [400, 600, 800, 1000, 1200, 1500, 2000, 4000]
@staticmethod @staticmethod
def get_maxfall_list(): def get_maxfall_list():
return SignalFormat.HDRMetadata.MAX_FALL_OPTIONS return [200, 300, 400, 500, 600, 800, 1000]
class OutputFormat:
"""输出色彩格式枚举(决定信号是 RGB 还是 YCbCr 格式)"""
RGB = "RGB"
YCBCR_422 = "YCbCr 4:2:2"
YCBCR_444 = "YCbCr 4:4:4"
YCBCR_420 = "YCbCr 4:2:0"
Y_ONLY = "Y Only"
IDO_DEFINED = "IDO Defined"
RAW = "RAW"
DSC = "DSC"
@staticmethod
def get_list():
return ["RGB", "YCbCr 4:4:4", "YCbCr 4:2:2", "YCbCr 4:2:0",
"Y Only", "IDO Defined", "RAW", "DSC"]
@staticmethod
def is_ycbcr(format_str):
return "YCbCr" in (format_str or "")
@staticmethod
def get_format_key(format_str):
"""将显示字符串转换为 UCDEnum.ColorInfo.get_color_format() 的 key"""
fmt_map = {
"RGB": "rgb",
"YCbCr 4:4:4": "ycbcr444",
"YCbCr 4:2:2": "ycbcr422",
"YCbCr 4:2:0": "ycbcr420",
"Y Only": "yonly",
"IDO Defined": "ido_defined",
"RAW": "raw",
"DSC": "dsc",
}
return fmt_map.get(format_str, "rgb")

View File

@@ -14,7 +14,7 @@ class UCDController:
self.role = None self.role = None
self.timing_manager = None self.timing_manager = None
self.config = None self.config = None
self.color_mode = None self.color_info = None
self.status = False self.status = False
self.current_interface = "HDMI" self.current_interface = "HDMI"
@@ -51,7 +51,7 @@ class UCDController:
pg, _ = self.get_tx_modules() pg, _ = self.get_tx_modules()
self.timing_manager = pg.timing_manager self.timing_manager = pg.timing_manager
self.color_mode = UniTAP.ColorInfo() self.color_info = UniTAP.ColorInfo()
self.status = True self.status = True
return True return True
@@ -60,23 +60,26 @@ class UCDController:
self._force_cleanup() self._force_cleanup()
return False return False
def _reset_state(self):
"""重置所有运行时状态(不关闭设备句柄)"""
self.dev = None
self.role = None
self.status = False
self.timing_manager = None
self.current_timing = None
self.current_pattern = None
self.current_pattern_param = None
self.current_pattern_params = None
self.current_pattern_index = 0
self.current_interface = "HDMI"
def close(self): def close(self):
"""关闭设备""" """关闭设备"""
try: try:
if self.dev: if self.dev:
self._close_device_object(self.dev) self._close_device_object(self.dev)
self.dev = None self._reset_state()
self.role = None
self.status = False
self.timing_manager = None
self.current_timing = None
self.current_pattern = None
self.current_pattern_param = None
self.current_pattern_params = None
self.current_pattern_index = 0
self.current_interface = "HDMI"
self.lUniTAP = None self.lUniTAP = None
for i in range(3): for i in range(3):
@@ -89,16 +92,7 @@ class UCDController:
return True return True
except Exception as e: except Exception as e:
self.dev = None self._reset_state()
self.role = None
self.status = False
self.timing_manager = None
self.current_timing = None
self.current_pattern = None
self.current_pattern_param = None
self.current_pattern_params = None
self.current_pattern_index = 0
self.current_interface = "HDMI"
try: try:
self.lUniTAP = None self.lUniTAP = None
@@ -135,16 +129,7 @@ class UCDController:
if self.dev: if self.dev:
self._close_device_object(self.dev) self._close_device_object(self.dev)
self.dev = None self._reset_state()
self.role = None
self.status = False
self.timing_manager = None
self.current_timing = None
self.current_pattern = None
self.current_pattern_param = None
self.current_pattern_params = None
self.current_pattern_index = 0
self.current_interface = "HDMI"
except Exception as e: except Exception as e:
pass pass
@@ -210,22 +195,12 @@ class UCDController:
pg, _ = self.get_tx_modules() pg, _ = self.get_tx_modules()
self.timing_manager = pg.timing_manager self.timing_manager = pg.timing_manager
if test_type == "hdr_movie": color_format = self.config.current_test_types[test_type]["color_format"]
color_format_str = self.config.current_test_types[test_type]["color_format"] bpc = self.config.current_test_types[test_type]["bpc"]
color_format = UCDEnum.ColorInfo.get_color_format(color_format_str) colorimetry = self.config.current_test_types[test_type]["colorimetry"]
if color_format: if not self.set_color_mode(color_format, bpc, colorimetry):
self.color_mode.color_format = color_format return False
else:
return False
else:
color_format = self.config.current_test_types[test_type]["color_format"]
bpc = self.config.current_test_types[test_type]["bpc"]
colorimetry = self.config.current_test_types[test_type]["colorimetry"]
if not self.set_color_mode(color_format, bpc, colorimetry):
return False
timing_str = self.config.current_test_types[test_type]["timing"] timing_str = self.config.current_test_types[test_type]["timing"]
self.set_timing_from_string(timing_str) self.set_timing_from_string(timing_str)
@@ -251,7 +226,7 @@ class UCDController:
return True return True
def send_image_pattern(self, image_path): def send_image_pattern(self, image_path):
"""发送图片 Pattern依赖当前 timing/color_mode 状态)。""" """发送图片 Pattern依赖当前 timing/color_info 状态)。"""
if not self.status or not self.role: if not self.status or not self.role:
return False return False
@@ -265,7 +240,7 @@ class UCDController:
return False return False
def send_solid_rgb_pattern(self, rgb): def send_solid_rgb_pattern(self, rgb):
"""发送纯色 RGB Pattern依赖当前 timing/color_mode 状态)。""" """发送纯色 RGB Pattern依赖当前 timing/color_info 状态)。"""
if not self.status or not self.role: if not self.status or not self.role:
return False return False
@@ -300,14 +275,7 @@ class UCDController:
def set_color_mode(self, cf, bpc, cm): def set_color_mode(self, cf, bpc, cm):
"""设置颜色模式""" """设置颜色模式"""
current_dynamic_range = None current_dynamic_range = self.color_info.dynamic_range
current_transfer_characteristic = None
if hasattr(self.color_mode, "dynamic_range"):
current_dynamic_range = self.color_mode.dynamic_range
if hasattr(self.color_mode, "transfer_characteristic"):
current_transfer_characteristic = self.color_mode.transfer_characteristic
color_format = UCDEnum.ColorInfo.get_color_format(cf) color_format = UCDEnum.ColorInfo.get_color_format(cf)
if color_format is None: if color_format is None:
@@ -320,20 +288,15 @@ class UCDController:
if colorimetry is None: if colorimetry is None:
return False return False
self.color_mode.color_format = color_format self.color_info.color_format = color_format
self.color_mode.bpc = bpc self.color_info.bpc = bpc
self.color_mode.colorimetry = colorimetry self.color_info.colorimetry = colorimetry
self.color_info.dynamic_range = current_dynamic_range
if current_dynamic_range is not None:
self.color_mode.dynamic_range = current_dynamic_range
if current_transfer_characteristic is not None:
self.color_mode.transfer_characteristic = current_transfer_characteristic
return True return True
def apply_video_mode(self): def apply_video_mode(self):
"""应用当前colormode和timing""" """应用当前 color_info 和 timing"""
if self.current_timing: if self.current_timing:
self.set_video_mode() self.set_video_mode()
return True return True
@@ -341,25 +304,34 @@ class UCDController:
def set_video_mode(self): def set_video_mode(self):
"""设置视频模式""" """设置视频模式"""
# 对比上次发出的配置,判断是否会触发电视重新锁定信号
current_config = (
self.current_timing,
self.color_info.color_format,
self.color_info.colorimetry,
self.color_info.dynamic_range,
self.color_info.bpc,
)
self.format_changed = (current_config != getattr(self, "_last_sent_config", None))
video_mode = UniTAP.VideoMode( video_mode = UniTAP.VideoMode(
timing=self.current_timing, color_info=self.color_mode timing=self.current_timing, color_info=self.color_info
) )
pg, _ = self.get_tx_modules() pg, _ = self.get_tx_modules()
pg.set_vm(vm=video_mode) pg.set_vm(vm=video_mode)
self._last_sent_config = current_config
return True return True
def set_pattern(self, pattern, pattern_params=None): def set_pattern(self, pattern, pattern_params=None):
"""设置pattern""" """设置pattern"""
if self.current_timing: if self.current_timing:
if ( needs_params = {
pattern == UCDEnum.VideoPatternInfo.VideoPatternParams.SolidColor UCDEnum.VideoPatternInfo.VideoPatternParams.SolidColor,
or pattern == UCDEnum.VideoPatternInfo.VideoPatternParams.WhiteVStrips UCDEnum.VideoPatternInfo.VideoPatternParams.WhiteVStrips,
or pattern UCDEnum.VideoPatternInfo.VideoPatternParams.GradientRGBStripes,
== UCDEnum.VideoPatternInfo.VideoPatternParams.GradientRGBStripes UCDEnum.VideoPatternInfo.VideoPatternParams.MotionPattern,
or pattern == UCDEnum.VideoPatternInfo.VideoPatternParams.MotionPattern UCDEnum.VideoPatternInfo.VideoPatternParams.SquareWindow,
or pattern == UCDEnum.VideoPatternInfo.VideoPatternParams.SquareWindow }
and pattern_params if pattern in needs_params and pattern_params:
):
self.set_pattern_params(pattern, pattern_params) self.set_pattern_params(pattern, pattern_params)
return True return True
return False return False
@@ -552,138 +524,62 @@ class UCDController:
else: else:
return False return False
def set_sdr_format( def apply_signal_format(
self, color_space=None, gamma=None, data_range=None, bit_depth=None self, color_space=None, data_range=None, bit_depth=None, color_format=None, **_
): ):
"""设置SDR信号格式""" """统一设置信号格式color_format / colorimetry / dynamic_range / bpc
Gamma/EOTF 传输特性在 ColorInfo API 中不存在;
def _get_colorimetry_from_color_space(color_space_name): max_cll / max_fall 暂无对应 SDK 接口,通过 **_ 接收后忽略。
"""将色彩空间名称转换为UniTAP colorimetry枚举值""" """
try:
colorimetry_map = {
"BT.709": UniTAP.ColorInfo.Colorimetry.CM_ITUR_BT709,
"BT.601": UniTAP.ColorInfo.Colorimetry.CM_ITUR_BT601,
"BT.2020": UniTAP.ColorInfo.Colorimetry.CM_ITUR_BT2020_RGB,
}
result = colorimetry_map.get(color_space_name)
return result if result else UniTAP.ColorInfo.Colorimetry.CM_ITUR_BT709
except Exception as e:
return UniTAP.ColorInfo.Colorimetry.CM_ITUR_BT709
def _set_gamma_transfer_characteristic(gamma_value_str):
"""设置Gamma传输特性"""
try:
gamma_value = float(gamma_value_str)
if abs(gamma_value - 2.2) < 0.1:
self.color_mode.transfer_characteristic = (
UniTAP.ColorInfo.TransferCharacteristic.TRANSFER_BT709
)
return True
elif abs(gamma_value - 2.4) < 0.1:
if hasattr(
UniTAP.ColorInfo.TransferCharacteristic, "TRANSFER_GAMMA24"
):
self.color_mode.transfer_characteristic = (
UniTAP.ColorInfo.TransferCharacteristic.TRANSFER_GAMMA24
)
return True
else:
self.color_mode.transfer_characteristic = (
UniTAP.ColorInfo.TransferCharacteristic.TRANSFER_BT709
)
return False
elif abs(gamma_value - 2.6) < 0.1:
if hasattr(
UniTAP.ColorInfo.TransferCharacteristic, "TRANSFER_GAMMA26"
):
self.color_mode.transfer_characteristic = (
UniTAP.ColorInfo.TransferCharacteristic.TRANSFER_GAMMA26
)
return True
else:
self.color_mode.transfer_characteristic = (
UniTAP.ColorInfo.TransferCharacteristic.TRANSFER_BT709
)
return False
else:
return False
except Exception as e:
return False
try: try:
if color_space: if color_format:
colorimetry = _get_colorimetry_from_color_space(color_space) fmt_key = UCDEnum.SignalFormat.OutputFormat.get_format_key(color_format)
if colorimetry: cf = UCDEnum.ColorInfo.get_color_format(fmt_key)
self.color_mode.colorimetry = colorimetry if cf is not None:
self.color_info.color_format = cf
if gamma: if color_space:
_set_gamma_transfer_characteristic(gamma) colorimetry = self._get_colorimetry_from_color_space(color_space, color_format)
if colorimetry:
self.color_info.colorimetry = colorimetry
if data_range: if data_range:
if data_range == "Full": if data_range == "Full":
self.color_mode.dynamic_range = ( self.color_info.dynamic_range = UniTAP.ColorInfo.DynamicRange.DR_VESA
UniTAP.ColorInfo.DynamicRange.DR_VESA
)
elif data_range == "Limited": elif data_range == "Limited":
self.color_mode.dynamic_range = UniTAP.ColorInfo.DynamicRange.DR_CTA self.color_info.dynamic_range = UniTAP.ColorInfo.DynamicRange.DR_CTA
if bit_depth: if bit_depth:
bpc = UCDEnum.SignalFormat.BitDepth.get_bit_value(bit_depth) bpc = UCDEnum.SignalFormat.BitDepth.get_bit_value(bit_depth)
self.color_mode.bpc = bpc self.color_info.bpc = bpc
if self.current_timing: if self.current_timing:
self.set_video_mode() self.set_video_mode()
return True return True
except Exception as e: except Exception:
return False return False
def set_hdr_format( # 向后兼容别名
self, set_sdr_format = apply_signal_format
color_space=None, set_hdr_format = apply_signal_format
data_range=None,
bit_depth=None,
max_cll=None,
max_fall=None,
):
"""设置HDR信号格式"""
try:
if color_space:
colorimetry = self._get_colorimetry_from_color_space(color_space)
if colorimetry:
self.color_mode.colorimetry = colorimetry
if data_range: def _get_colorimetry_from_color_space(self, color_space, color_format=None):
if data_range == "Full": """将色彩空间字符串转换为UniTAP.ColorInfo.Colorimetry。
self.color_mode.dynamic_range = ( BT.2020 在 YCbCr 输出时使用 CM_ITUR_BT2020_YCbCrRGB 输出时使用 CM_ITUR_BT2020_RGB。
UniTAP.ColorInfo.DynamicRange.DR_VESA """
) is_ycbcr = UCDEnum.SignalFormat.OutputFormat.is_ycbcr(color_format)
elif data_range == "Limited": bt2020_cm = (
self.color_mode.dynamic_range = UniTAP.ColorInfo.DynamicRange.DR_CTA UniTAP.ColorInfo.Colorimetry.CM_ITUR_BT2020_YCbCr
if is_ycbcr
if bit_depth: else UniTAP.ColorInfo.Colorimetry.CM_ITUR_BT2020_RGB
bpc = UCDEnum.SignalFormat.BitDepth.get_bit_value(bit_depth) )
self.color_mode.bpc = bpc
if self.current_timing:
self.set_video_mode()
return True
except Exception as e:
return False
def _get_colorimetry_from_color_space(self, color_space):
"""将色彩空间字符串转换为UniTAP.ColorInfo.Colorimetry"""
colorimetry_map = { colorimetry_map = {
"sRGB": UniTAP.ColorInfo.Colorimetry.CM_sRGB,
"BT.709": UniTAP.ColorInfo.Colorimetry.CM_ITUR_BT709, "BT.709": UniTAP.ColorInfo.Colorimetry.CM_ITUR_BT709,
"BT.601": UniTAP.ColorInfo.Colorimetry.CM_ITUR_BT601, "BT.601": UniTAP.ColorInfo.Colorimetry.CM_ITUR_BT601,
"BT.2020": UniTAP.ColorInfo.Colorimetry.CM_ITUR_BT2020_RGB, "BT.2020": bt2020_cm,
"DCI-P3": UniTAP.ColorInfo.Colorimetry.CM_DCI_P3, "DCI-P3": UniTAP.ColorInfo.Colorimetry.CM_DCI_P3,
} }
return colorimetry_map.get(color_space) return colorimetry_map.get(color_space)

View File

@@ -57,10 +57,14 @@ Section "Main Installation" SEC_MAIN
SetOutPath "$INSTDIR\internal" SetOutPath "$INSTDIR\internal"
File /r /x "*.pdb" /x "*.lib" /x "*.exp" /x "*.h" /x "__pycache__" /x "*.pyc" "${DIST_ROOT}\internal\*.*" File /r /x "*.pdb" /x "*.lib" /x "*.exp" /x "*.h" /x "__pycache__" /x "*.pyc" "${DIST_ROOT}\internal\*.*"
IfFileExists "$INSTDIR\settings\pq_config.json" +3 0 SetOutPath "$INSTDIR\settings"
SetOutPath "$INSTDIR\settings"
IfFileExists "$INSTDIR\settings\pq_config.json" +2 0
File /oname=pq_config.json "${PROJECT_ROOT}\settings\pq_config.json" File /oname=pq_config.json "${PROJECT_ROOT}\settings\pq_config.json"
IfFileExists "$INSTDIR\settings\pantone_2670_colors.xlsx" +2 0
File /oname=pantone_2670_colors.xlsx "${PROJECT_ROOT}\settings\pantone_2670_colors.xlsx"
WriteUninstaller "$INSTDIR\Uninstall.exe" WriteUninstaller "$INSTDIR\Uninstall.exe"
WriteRegStr HKCU "${APP_REG_KEY}" "InstallDir" "$INSTDIR" WriteRegStr HKCU "${APP_REG_KEY}" "InstallDir" "$INSTDIR"

View File

@@ -289,8 +289,11 @@ class PQAutomationApp:
create_test_type_frame = _main.create_test_type_frame create_test_type_frame = _main.create_test_type_frame
update_config_info_display = _main.update_config_info_display update_config_info_display = _main.update_config_info_display
on_screen_module_timing_changed = _main.on_screen_module_timing_changed on_screen_module_timing_changed = _main.on_screen_module_timing_changed
on_sdr_timing_changed = _main.on_sdr_timing_changed
update_test_items = _main.update_test_items update_test_items = _main.update_test_items
on_test_type_change = _main.on_test_type_change on_test_type_change = _main.on_test_type_change
on_sdr_output_format_changed = _main.on_sdr_output_format_changed
on_hdr_output_format_changed = _main.on_hdr_output_format_changed
create_cct_params_frame = _ccp.create_cct_params_frame create_cct_params_frame = _ccp.create_cct_params_frame
on_sdr_cct_param_focus_out = _ccp.on_sdr_cct_param_focus_out on_sdr_cct_param_focus_out = _ccp.on_sdr_cct_param_focus_out

View File

@@ -3,6 +3,12 @@
import os import os
import sys import sys
from PyInstaller.utils.hooks import collect_dynamic_libs, collect_data_files
numpy_binaries = collect_dynamic_libs('numpy')
matplotlib_datas = collect_data_files('matplotlib')
SPEC_DIR = ( SPEC_DIR = (
os.path.abspath(os.path.dirname(__file__)) os.path.abspath(os.path.dirname(__file__))
if "__file__" in globals() if "__file__" in globals()
@@ -22,20 +28,10 @@ from PyInstaller.utils.win32.versioninfo import (
VarStruct, VarStruct,
) )
def build_windows_version(version_text):
parts = [int(part) for part in version_text.split('.') if part.strip()]
parts = (parts + [0, 0, 0, 0])[:4]
return tuple(parts)
windows_version = build_windows_version(APP_VERSION)
windows_version_text = '.'.join(str(part) for part in windows_version)
version_info = VSVersionInfo( version_info = VSVersionInfo(
ffi=FixedFileInfo( ffi=FixedFileInfo(
filevers=windows_version, filevers=(5, 26, 1519, 2),
prodvers=windows_version, prodvers=(5, 0, 1, 0),
mask=0x3F, mask=0x3F,
flags=0x0, flags=0x0,
OS=0x40004, OS=0x40004,
@@ -51,11 +47,11 @@ version_info = VSVersionInfo(
[ [
StringStruct('CompanyName', 'Moka'), StringStruct('CompanyName', 'Moka'),
StringStruct('FileDescription', APP_NAME), StringStruct('FileDescription', APP_NAME),
StringStruct('FileVersion', windows_version_text), StringStruct('FileVersion', '5.26.1519.2'),
StringStruct('InternalName', 'pqAutomationApp'), StringStruct('InternalName', 'pqAutomationApp'),
StringStruct('OriginalFilename', 'pqAutomationApp.exe'), StringStruct('OriginalFilename', 'pqAutomationApp.exe'),
StringStruct('ProductName', APP_NAME), StringStruct('ProductName', APP_NAME),
StringStruct('ProductVersion', windows_version_text), StringStruct('ProductVersion', '5.0.1.0'),
], ],
) )
] ]
@@ -68,8 +64,8 @@ version_info = VSVersionInfo(
a = Analysis( a = Analysis(
['pqAutomationApp.py'], ['pqAutomationApp.py'],
pathex=[], pathex=[],
binaries=[], binaries=numpy_binaries,
datas=[('assets', 'assets'), ('UniTAP', 'UniTAP')], datas=[('assets', 'assets'), ('UniTAP', 'UniTAP')] + matplotlib_datas,
hiddenimports=[ hiddenimports=[
# app 包的子模块在主文件里是静态 import 的,但惰性调用 / 属性绑定 # app 包的子模块在主文件里是静态 import 的,但惰性调用 / 属性绑定
# 场景较多,显式列出可避免 PyInstaller 漏打包。 # 场景较多,显式列出可避免 PyInstaller 漏打包。

View File

@@ -9,7 +9,7 @@
"cct", "cct",
"contrast" "contrast"
], ],
"timing": "DMT 1920x 1080 @ 60Hz", "timing": "DMT 1600x 1200 @ 60Hz",
"color_format": "RGB", "color_format": "RGB",
"bpc": 8, "bpc": 8,
"colorimetry": "sRGB", "colorimetry": "sRGB",
@@ -24,11 +24,7 @@
"sdr_movie": { "sdr_movie": {
"name": "SDR Movie测试", "name": "SDR Movie测试",
"test_items": [ "test_items": [
"gamut", "gamut"
"gamma",
"cct",
"contrast",
"accuracy"
], ],
"timing": "DMT 1920x 1080 @ 60Hz", "timing": "DMT 1920x 1080 @ 60Hz",
"color_format": "RGB", "color_format": "RGB",
@@ -40,7 +36,7 @@
"y_ideal": 0.329, "y_ideal": 0.329,
"y_tolerance": 0.003 "y_tolerance": 0.003
}, },
"gamut_reference": "DCI-P3" "gamut_reference": "BT.2020"
}, },
"hdr_movie": { "hdr_movie": {
"name": "HDR Movie测试", "name": "HDR Movie测试",
@@ -64,7 +60,7 @@
} }
}, },
"device_config": { "device_config": {
"ca_com": "COM6", "ca_com": "COM3",
"ucd_list": "0: UCD-323 [2128C209]", "ucd_list": "0: UCD-323 [2128C209]",
"ca_channel": "0" "ca_channel": "0"
}, },