2026-04-16 16:51:05 +08:00
|
|
|
|
import ttkbootstrap as ttk
|
|
|
|
|
|
import tkinter as tk
|
|
|
|
|
|
from tkinter import messagebox, filedialog
|
|
|
|
|
|
import sys
|
|
|
|
|
|
import threading
|
|
|
|
|
|
import time
|
|
|
|
|
|
import os
|
|
|
|
|
|
import datetime
|
2026-04-17 11:17:29 +08:00
|
|
|
|
import colour
|
2026-04-16 16:51:05 +08:00
|
|
|
|
import json
|
|
|
|
|
|
import traceback
|
|
|
|
|
|
import numpy as np
|
2026-04-17 11:17:29 +08:00
|
|
|
|
import matplotlib.pyplot as plt
|
|
|
|
|
|
import matplotlib.image as mpimg
|
2026-04-16 16:51:05 +08:00
|
|
|
|
import algorithm.pq_algorithm as pq_algorithm
|
2026-04-17 11:17:29 +08:00
|
|
|
|
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
2026-04-16 16:51:05 +08:00
|
|
|
|
from app_version import APP_NAME, APP_VERSION, get_app_title
|
2026-04-20 11:48:38 +08:00
|
|
|
|
from drivers.caSerail import CASerail
|
|
|
|
|
|
from drivers.tvSerail import tvSerial
|
|
|
|
|
|
from drivers.UCD323_Function import UCDController
|
|
|
|
|
|
from drivers.UCD323_Enum import UCDEnum
|
|
|
|
|
|
from app.pq.pq_config import PQConfig
|
|
|
|
|
|
from app.pq.pq_result import PQResult
|
|
|
|
|
|
from app.data_range_converter import convert_pattern_params
|
2026-04-16 16:51:05 +08:00
|
|
|
|
from PIL import Image, ImageTk
|
2026-04-20 11:48:38 +08:00
|
|
|
|
from app.views.collapsing_frame import CollapsingFrame
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
# from app.views.pq_history_gui import PQHistoryGUI
|
|
|
|
|
|
from app.views.pq_log_gui import PQLogGUI
|
2026-04-17 11:17:29 +08:00
|
|
|
|
from colormath.color_objects import xyYColor, LabColor
|
|
|
|
|
|
from colormath.color_conversions import convert_color
|
|
|
|
|
|
from colormath.color_diff import delta_e_cie2000
|
2026-04-20 11:48:38 +08:00
|
|
|
|
from app.views.pq_debug_panel import PQDebugPanel
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 09:41:24 +08:00
|
|
|
|
# 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
|
2026-04-20 10:00:44 +08:00
|
|
|
|
from app.plots.plot_accuracy import plot_accuracy as _plot_accuracy
|
|
|
|
|
|
from app.plots.plot_cct import plot_cct as _plot_cct
|
|
|
|
|
|
from app.plots.plot_contrast import plot_contrast as _plot_contrast
|
|
|
|
|
|
from app.plots.plot_eotf import plot_eotf as _plot_eotf
|
|
|
|
|
|
from app.plots.plot_gamma import plot_gamma as _plot_gamma
|
|
|
|
|
|
from app.plots.plot_gamut import plot_gamut as _plot_gamut
|
2026-04-20 10:16:31 +08:00
|
|
|
|
from app.views.chart_frame import (
|
|
|
|
|
|
clear_chart as _cf_clear_chart,
|
|
|
|
|
|
create_result_chart_frame as _cf_create_result_chart_frame,
|
|
|
|
|
|
init_accuracy_chart as _cf_init_accuracy_chart,
|
|
|
|
|
|
init_cct_chart as _cf_init_cct_chart,
|
|
|
|
|
|
init_contrast_chart as _cf_init_contrast_chart,
|
|
|
|
|
|
init_eotf_chart as _cf_init_eotf_chart,
|
|
|
|
|
|
init_gamma_chart as _cf_init_gamma_chart,
|
|
|
|
|
|
init_gamut_chart as _cf_init_gamut_chart,
|
|
|
|
|
|
on_chart_tab_changed as _cf_on_chart_tab_changed,
|
|
|
|
|
|
update_chart_tabs_state as _cf_update_chart_tabs_state,
|
|
|
|
|
|
)
|
2026-04-20 10:54:47 +08:00
|
|
|
|
from app.config_io import (
|
|
|
|
|
|
clear_config_file as _cfg_clear_config_file,
|
|
|
|
|
|
get_config_path as _cfg_get_config_path,
|
|
|
|
|
|
load_pq_config as _cfg_load_pq_config,
|
|
|
|
|
|
save_pq_config as _cfg_save_pq_config,
|
|
|
|
|
|
)
|
|
|
|
|
|
from app.tests.local_dimming import (
|
|
|
|
|
|
clear_ld_records as _ld_clear_ld_records,
|
|
|
|
|
|
measure_ld_luminance as _ld_measure_ld_luminance,
|
|
|
|
|
|
save_local_dimming_results as _ld_save_local_dimming_results,
|
|
|
|
|
|
send_ld_window as _ld_send_ld_window,
|
|
|
|
|
|
start_local_dimming_test as _ld_start_local_dimming_test,
|
|
|
|
|
|
stop_local_dimming_test as _ld_stop_local_dimming_test,
|
|
|
|
|
|
update_ld_results as _ld_update_ld_results,
|
|
|
|
|
|
)
|
|
|
|
|
|
from app.device.connection import (
|
|
|
|
|
|
check_com_connections as _dev_check_com_connections,
|
|
|
|
|
|
check_port_connection as _dev_check_port_connection,
|
|
|
|
|
|
disconnect_com_connections as _dev_disconnect_com_connections,
|
|
|
|
|
|
enable_com_widgets as _dev_enable_com_widgets,
|
|
|
|
|
|
get_available_com_ports as _dev_get_available_com_ports,
|
|
|
|
|
|
get_available_ucd_ports as _dev_get_available_ucd_ports,
|
|
|
|
|
|
refresh_com_ports as _dev_refresh_com_ports,
|
|
|
|
|
|
update_connection_indicator as _dev_update_connection_indicator,
|
|
|
|
|
|
)
|
2026-04-20 11:13:57 +08:00
|
|
|
|
from app.runner.test_runner import (
|
|
|
|
|
|
get_current_test_result as _run_get_current_test_result,
|
|
|
|
|
|
new_pq_results as _run_new_pq_results,
|
|
|
|
|
|
on_custom_template_test_completed as _run_on_custom_template_test_completed,
|
|
|
|
|
|
on_test_completed as _run_on_test_completed,
|
|
|
|
|
|
on_test_error as _run_on_test_error,
|
|
|
|
|
|
run_custom_sdr_test as _run_run_custom_sdr_test,
|
|
|
|
|
|
run_hdr_movie_test as _run_run_hdr_movie_test,
|
|
|
|
|
|
run_screen_module_test as _run_run_screen_module_test,
|
|
|
|
|
|
run_sdr_movie_test as _run_run_sdr_movie_test,
|
|
|
|
|
|
run_test as _run_run_test,
|
|
|
|
|
|
send_fix_pattern as _run_send_fix_pattern,
|
|
|
|
|
|
test_cct as _run_test_cct,
|
|
|
|
|
|
test_color_accuracy as _run_test_color_accuracy,
|
|
|
|
|
|
test_contrast as _run_test_contrast,
|
|
|
|
|
|
test_custom_sdr as _run_test_custom_sdr,
|
|
|
|
|
|
test_eotf as _run_test_eotf,
|
|
|
|
|
|
test_gamma as _run_test_gamma,
|
|
|
|
|
|
test_gamut as _run_test_gamut,
|
|
|
|
|
|
)
|
2026-04-20 11:48:38 +08:00
|
|
|
|
from app.views.panel_manager import (
|
|
|
|
|
|
hide_all_panels as _pm_hide_all_panels,
|
|
|
|
|
|
register_panel as _pm_register_panel,
|
|
|
|
|
|
show_panel as _pm_show_panel,
|
|
|
|
|
|
)
|
|
|
|
|
|
from app.views.panels.side_panels import (
|
|
|
|
|
|
create_local_dimming_panel as _side_create_local_dimming_panel,
|
|
|
|
|
|
create_log_panel as _side_create_log_panel,
|
|
|
|
|
|
toggle_hdr_debug_panel as _side_toggle_hdr_debug_panel,
|
|
|
|
|
|
toggle_local_dimming_panel as _side_toggle_local_dimming_panel,
|
|
|
|
|
|
toggle_log_panel as _side_toggle_log_panel,
|
|
|
|
|
|
toggle_screen_debug_panel as _side_toggle_screen_debug_panel,
|
|
|
|
|
|
toggle_sdr_debug_panel as _side_toggle_sdr_debug_panel,
|
|
|
|
|
|
)
|
|
|
|
|
|
from app.views.panels.custom_template_panel import (
|
|
|
|
|
|
_clear_custom_result_row as _ctpl__clear_custom_result_row,
|
|
|
|
|
|
_run_custom_row_single_step as _ctpl__run_custom_row_single_step,
|
|
|
|
|
|
_update_custom_result_row as _ctpl__update_custom_result_row,
|
|
|
|
|
|
append_custom_template_result as _ctpl_append_custom_template_result,
|
|
|
|
|
|
auto_expand_custom_result_view as _ctpl_auto_expand_custom_result_view,
|
|
|
|
|
|
clear_custom_template_results as _ctpl_clear_custom_template_results,
|
|
|
|
|
|
copy_custom_result_table as _ctpl_copy_custom_result_table,
|
|
|
|
|
|
create_custom_template_result_panel as _ctpl_create_custom_template_result_panel,
|
|
|
|
|
|
fill_custom_result_test_data as _ctpl_fill_custom_result_test_data,
|
|
|
|
|
|
set_custom_result_table_locked as _ctpl_set_custom_result_table_locked,
|
|
|
|
|
|
show_custom_result_context_menu as _ctpl_show_custom_result_context_menu,
|
|
|
|
|
|
start_custom_row_single_step as _ctpl_start_custom_row_single_step,
|
|
|
|
|
|
start_custom_template_test as _ctpl_start_custom_template_test,
|
|
|
|
|
|
)
|
|
|
|
|
|
from app.views.panels.cct_panel import (
|
|
|
|
|
|
create_cct_params_frame as _cct_create_cct_params_frame,
|
|
|
|
|
|
on_cct_param_change as _cct_on_cct_param_change,
|
|
|
|
|
|
on_cct_param_focus_out as _cct_on_cct_param_focus_out,
|
|
|
|
|
|
on_hdr_cct_param_focus_out as _cct_on_hdr_cct_param_focus_out,
|
|
|
|
|
|
on_sdr_cct_param_focus_out as _cct_on_sdr_cct_param_focus_out,
|
|
|
|
|
|
recalculate_cct as _cct_recalculate_cct,
|
|
|
|
|
|
recalculate_gamut as _cct_recalculate_gamut,
|
|
|
|
|
|
reload_cct_params as _cct_reload_cct_params,
|
|
|
|
|
|
save_cct_params as _cct_save_cct_params,
|
|
|
|
|
|
save_hdr_cct_params as _cct_save_hdr_cct_params,
|
|
|
|
|
|
save_sdr_cct_params as _cct_save_sdr_cct_params,
|
|
|
|
|
|
toggle_cct_params_frame as _cct_toggle_cct_params_frame,
|
|
|
|
|
|
)
|
|
|
|
|
|
from app.views.panels.main_layout import (
|
|
|
|
|
|
create_connection_content as _layout_create_connection_content,
|
|
|
|
|
|
create_floating_config_panel as _layout_create_floating_config_panel,
|
|
|
|
|
|
create_operation_frame as _layout_create_operation_frame,
|
|
|
|
|
|
create_signal_format_content as _layout_create_signal_format_content,
|
|
|
|
|
|
create_test_items_content as _layout_create_test_items_content,
|
|
|
|
|
|
create_test_type_frame as _layout_create_test_type_frame,
|
|
|
|
|
|
update_config_info_display as _layout_update_config_info_display,
|
|
|
|
|
|
)
|
2026-04-20 09:41:24 +08:00
|
|
|
|
|
2026-04-17 11:17:29 +08:00
|
|
|
|
plt.rcParams["font.family"] = ["sans-serif"]
|
|
|
|
|
|
plt.rcParams["font.sans-serif"] = ["Microsoft YaHei"]
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PQAutomationApp:
|
|
|
|
|
|
def __init__(self, root):
|
|
|
|
|
|
self.root = root
|
|
|
|
|
|
self.root.title(get_app_title())
|
|
|
|
|
|
self.root.geometry("900x650")
|
|
|
|
|
|
self.root.minsize(900, 650)
|
|
|
|
|
|
self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
|
|
|
|
|
|
self.app_name = APP_NAME
|
|
|
|
|
|
self.app_version = APP_VERSION
|
|
|
|
|
|
|
|
|
|
|
|
self.config_cleared = False
|
|
|
|
|
|
|
|
|
|
|
|
# 初始化设备连接状态
|
|
|
|
|
|
self.ca = None # CA410色度计
|
|
|
|
|
|
self.ucd = UCDController() # 信号发生器
|
|
|
|
|
|
|
|
|
|
|
|
# 初始化测试状态
|
|
|
|
|
|
self.testing = False
|
|
|
|
|
|
self.test_thread = None
|
|
|
|
|
|
# 采集节奏参数:默认在稳定性与速度之间取平衡,可按现场情况再微调。
|
|
|
|
|
|
self.pattern_settle_time = 0.4
|
|
|
|
|
|
self.pattern_progress_log_step = 5
|
|
|
|
|
|
|
|
|
|
|
|
# 创建主框架
|
|
|
|
|
|
self.main_frame = ttk.Frame(root)
|
|
|
|
|
|
|
|
|
|
|
|
self.main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
|
|
|
|
|
|
|
|
|
|
|
backgroud_style_set()
|
|
|
|
|
|
|
|
|
|
|
|
# 创建配置对象
|
|
|
|
|
|
self.config = PQConfig()
|
|
|
|
|
|
self.results = None
|
|
|
|
|
|
|
|
|
|
|
|
# 加载上次保存的设置
|
|
|
|
|
|
self.config_file = self.get_config_path()
|
|
|
|
|
|
self.load_pq_config()
|
|
|
|
|
|
|
|
|
|
|
|
# 如果加载的配置不是屏模组,强制切换为屏模组
|
|
|
|
|
|
if self.config.current_test_type != "screen_module":
|
|
|
|
|
|
self.config.set_current_test_type("screen_module")
|
|
|
|
|
|
|
|
|
|
|
|
# 初始化侧边栏功能显示状态 - 使用统一的页面管理
|
|
|
|
|
|
self.current_panel = None # 当前显示的面板名称
|
|
|
|
|
|
self.panels = {} # 存储所有面板的信息
|
|
|
|
|
|
self.log_visible = False
|
|
|
|
|
|
|
|
|
|
|
|
# 创建左侧面板
|
|
|
|
|
|
self.left_frame = ttk.Frame(self.main_frame, width=180)
|
|
|
|
|
|
self.left_frame.pack(side=tk.LEFT, fill=tk.Y, padx=0, pady=5)
|
|
|
|
|
|
self.left_frame.pack_propagate(False)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建左侧导航栏
|
|
|
|
|
|
self.sidebar_frame = ttk.Frame(self.left_frame, bootstyle="primary")
|
|
|
|
|
|
self.sidebar_frame.pack(fill=tk.BOTH, expand=True, padx=0, pady=5)
|
|
|
|
|
|
# self.sidebar_frame.pack_propagate(False)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建右侧内容区域
|
|
|
|
|
|
self.content_frame = ttk.Frame(self.main_frame)
|
|
|
|
|
|
self.content_frame.pack(
|
|
|
|
|
|
side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5, pady=5
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建右侧内容区域的上中下三个分区
|
|
|
|
|
|
self.control_frame_top = ttk.Frame(self.content_frame)
|
|
|
|
|
|
self.control_frame_top.pack(
|
|
|
|
|
|
side=tk.TOP, fill=tk.BOTH, expand=True, padx=0, pady=5
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
self.control_frame_middle = ttk.Frame(self.content_frame)
|
|
|
|
|
|
self.control_frame_middle.pack(
|
|
|
|
|
|
side=tk.TOP, fill=tk.BOTH, expand=True, padx=0, pady=5
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
self.control_frame_bottom = ttk.Frame(self.content_frame)
|
|
|
|
|
|
self.control_frame_bottom.pack(
|
|
|
|
|
|
side=tk.TOP, fill=tk.BOTH, expand=True, padx=0, pady=5
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建右上角悬浮配置框
|
|
|
|
|
|
self.create_floating_config_panel()
|
|
|
|
|
|
|
|
|
|
|
|
# 创建右侧结果显示区域
|
|
|
|
|
|
self.result_frame = ttk.LabelFrame(self.control_frame_middle, text="测试结果")
|
|
|
|
|
|
self.result_frame.pack(
|
2026-04-20 11:48:38 +08:00
|
|
|
|
side=tk.BOTTOM, fill=tk.BOTH, expand=True, padx=0, pady=5
|
2026-04-16 16:51:05 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
# 创建日志显示区域
|
|
|
|
|
|
self.create_log_panel()
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
# 创建 Local Dimming 面板
|
|
|
|
|
|
self.create_local_dimming_panel()
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
# 创建测试类型选择区域
|
|
|
|
|
|
self.create_test_type_frame()
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
# 创建操作按钮区域
|
|
|
|
|
|
self.create_operation_frame()
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
# 创建结果图表区域
|
|
|
|
|
|
self.create_result_chart_frame()
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
# 创建客户模板结果显示区域(黑底表格)
|
|
|
|
|
|
self.create_custom_template_result_panel()
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
# 在所有控件创建完成后,统一初始化测试类型
|
|
|
|
|
|
self.root.after(100, self.initialize_default_test_type)
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
# 状态栏
|
|
|
|
|
|
self.status_var = tk.StringVar(value="就绪")
|
|
|
|
|
|
self.status_bar = ttk.Label(
|
|
|
|
|
|
root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W
|
2026-04-16 16:51:05 +08:00
|
|
|
|
)
|
2026-04-20 11:48:38 +08:00
|
|
|
|
self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 15:34:45 +08:00
|
|
|
|
def _dispatch_ui(self, fn, *args, **kwargs):
|
|
|
|
|
|
"""把 fn(*args, **kwargs) 调度到 Tk 主线程执行。
|
|
|
|
|
|
|
|
|
|
|
|
统一替代散落各处的 self.root.after(0, lambda: ...) 写法:
|
|
|
|
|
|
- 自动捕获异常并记录日志,避免工作线程静默丢失 UI 更新失败;
|
|
|
|
|
|
- 参数用位置/关键字传入,绕开 lambda 闭包捕获变量的常见坑;
|
|
|
|
|
|
- 允许在 UI 销毁(如关闭窗口)后安全失败。
|
|
|
|
|
|
"""
|
|
|
|
|
|
def _runner():
|
|
|
|
|
|
try:
|
|
|
|
|
|
fn(*args, **kwargs)
|
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
|
log = getattr(self, "log_gui", None)
|
|
|
|
|
|
if log is not None:
|
|
|
|
|
|
try:
|
|
|
|
|
|
log.log(f"UI 调度异常: {exc}")
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.root.after(0, _runner)
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
get_config_path = _cfg_get_config_path
|
|
|
|
|
|
def initialize_default_test_type(self):
|
|
|
|
|
|
"""初始化默认测试类型(在所有控件创建完成后调用)"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 强制切换到屏模组
|
|
|
|
|
|
self.change_test_type("screen_module")
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
if hasattr(self, "log_gui"):
|
|
|
|
|
|
self.log_gui.log("✓ 默认测试类型已设置为屏模组")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
if hasattr(self, "log_gui"):
|
|
|
|
|
|
self.log_gui.log(f"初始化默认测试类型失败: {str(e)}")
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
init_gamut_chart = _cf_init_gamut_chart
|
|
|
|
|
|
init_gamma_chart = _cf_init_gamma_chart
|
|
|
|
|
|
init_eotf_chart = _cf_init_eotf_chart
|
|
|
|
|
|
init_cct_chart = _cf_init_cct_chart
|
|
|
|
|
|
init_contrast_chart = _cf_init_contrast_chart
|
|
|
|
|
|
init_accuracy_chart = _cf_init_accuracy_chart
|
|
|
|
|
|
clear_chart = _cf_clear_chart
|
|
|
|
|
|
create_floating_config_panel = _layout_create_floating_config_panel
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
create_test_items_content = _layout_create_test_items_content
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
create_cct_params_frame = _cct_create_cct_params_frame
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
on_sdr_cct_param_focus_out = _cct_on_sdr_cct_param_focus_out
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
save_sdr_cct_params = _cct_save_sdr_cct_params
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
on_hdr_cct_param_focus_out = _cct_on_hdr_cct_param_focus_out
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
save_hdr_cct_params = _cct_save_hdr_cct_params
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
recalculate_cct = _cct_recalculate_cct
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
recalculate_gamut = _cct_recalculate_gamut
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
on_cct_param_change = _cct_on_cct_param_change
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
on_cct_param_focus_out = _cct_on_cct_param_focus_out
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
save_cct_params = _cct_save_cct_params
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
reload_cct_params = _cct_reload_cct_params
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
def update_test_items(self):
|
|
|
|
|
|
"""根据当前测试类型更新测试项目复选框"""
|
|
|
|
|
|
# 先隐藏所有测试项目框架
|
|
|
|
|
|
for config in self.test_items.values():
|
|
|
|
|
|
config["frame"].pack_forget()
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
current_test_type = self.config.current_test_type
|
|
|
|
|
|
self.test_vars = {}
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
if current_test_type in self.test_items:
|
|
|
|
|
|
config = self.test_items[current_test_type]
|
|
|
|
|
|
frame = config["frame"]
|
|
|
|
|
|
frame.pack(fill=tk.X, padx=5, pady=5)
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
# 添加测试类型标签
|
|
|
|
|
|
type_label = ttk.Label(
|
|
|
|
|
|
frame,
|
|
|
|
|
|
text=self.get_test_type_display_name(current_test_type),
|
|
|
|
|
|
style="primary.TLabel",
|
|
|
|
|
|
)
|
|
|
|
|
|
type_label.grid(row=0, column=0, columnspan=2, sticky=tk.W, padx=5, pady=3)
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
# 从配置中读取保存的选择状态
|
|
|
|
|
|
saved_test_items = self.config.current_test_types[current_test_type].get(
|
|
|
|
|
|
"test_items", []
|
|
|
|
|
|
)
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
# 添加复选框
|
|
|
|
|
|
for i, (text, var_name) in enumerate(config["items"]):
|
|
|
|
|
|
# 修改:根据配置决定是否勾选
|
|
|
|
|
|
# 如果配置中有该测试项,则勾选;否则不勾选
|
|
|
|
|
|
is_checked = var_name in saved_test_items
|
|
|
|
|
|
var = tk.BooleanVar(value=is_checked)
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
self.test_vars[f"{current_test_type}_{var_name}"] = var
|
|
|
|
|
|
ttk.Checkbutton(
|
|
|
|
|
|
frame,
|
|
|
|
|
|
text=text,
|
|
|
|
|
|
variable=var,
|
|
|
|
|
|
bootstyle="round-toggle",
|
|
|
|
|
|
command=self.update_config_and_tabs,
|
|
|
|
|
|
).grid(row=i // 2 + 1, column=i % 2, sticky=tk.W, padx=10, pady=5)
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
# 只有在 chart_notebook 已创建后才更新状态
|
|
|
|
|
|
if hasattr(self, "chart_notebook"):
|
|
|
|
|
|
self.update_chart_tabs_state()
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
# 更新色度参数框的显示状态
|
|
|
|
|
|
if hasattr(self, "cct_params_frame"):
|
|
|
|
|
|
self.toggle_cct_params_frame()
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
# ========== 新增方法: 更新配置并同步Tab状态 ==========
|
|
|
|
|
|
def update_config_and_tabs(self):
|
|
|
|
|
|
"""更新配置并同步图表Tab状态"""
|
|
|
|
|
|
self.update_config()
|
|
|
|
|
|
self.update_chart_tabs_state()
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
update_chart_tabs_state = _cf_update_chart_tabs_state
|
|
|
|
|
|
def get_test_type_display_name(self, test_type):
|
|
|
|
|
|
"""获取测试类型的显示名称"""
|
|
|
|
|
|
display_names = {
|
|
|
|
|
|
"screen_module": "屏模组性能测试",
|
|
|
|
|
|
"sdr_movie": "SDR Movie测试",
|
|
|
|
|
|
"hdr_movie": "HDR Movie测试",
|
2026-04-16 16:51:05 +08:00
|
|
|
|
}
|
2026-04-20 11:48:38 +08:00
|
|
|
|
return display_names.get(test_type, test_type)
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
create_signal_format_content = _layout_create_signal_format_content
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
create_connection_content = _layout_create_connection_content
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
get_available_ucd_ports = _dev_get_available_ucd_ports
|
|
|
|
|
|
get_available_com_ports = _dev_get_available_com_ports
|
|
|
|
|
|
refresh_com_ports = _dev_refresh_com_ports
|
|
|
|
|
|
check_com_connections = _dev_check_com_connections
|
|
|
|
|
|
update_connection_indicator = _dev_update_connection_indicator
|
|
|
|
|
|
check_port_connection = _dev_check_port_connection
|
|
|
|
|
|
enable_com_widgets = _dev_enable_com_widgets
|
|
|
|
|
|
disconnect_com_connections = _dev_disconnect_com_connections
|
|
|
|
|
|
create_test_type_frame = _layout_create_test_type_frame
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
update_config_info_display = _layout_update_config_info_display
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
create_operation_frame = _layout_create_operation_frame
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
create_custom_template_result_panel = _ctpl_create_custom_template_result_panel
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
show_custom_result_context_menu = _ctpl_show_custom_result_context_menu
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
set_custom_result_table_locked = _ctpl_set_custom_result_table_locked
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
start_custom_row_single_step = _ctpl_start_custom_row_single_step
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
_clear_custom_result_row = _ctpl__clear_custom_result_row
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
_run_custom_row_single_step = _ctpl__run_custom_row_single_step
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
_update_custom_result_row = _ctpl__update_custom_result_row
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
copy_custom_result_table = _ctpl_copy_custom_result_table
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
fill_custom_result_test_data = _ctpl_fill_custom_result_test_data
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
clear_custom_template_results = _ctpl_clear_custom_template_results
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
auto_expand_custom_result_view = _ctpl_auto_expand_custom_result_view
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
append_custom_template_result = _ctpl_append_custom_template_result
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
start_custom_template_test = _ctpl_start_custom_template_test
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
register_panel = _pm_register_panel
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
show_panel = _pm_show_panel
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
hide_all_panels = _pm_hide_all_panels
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
create_log_panel = _side_create_log_panel
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
create_local_dimming_panel = _side_create_local_dimming_panel
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
toggle_local_dimming_panel = _side_toggle_local_dimming_panel
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
toggle_log_panel = _side_toggle_log_panel
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:13:57 +08:00
|
|
|
|
create_result_chart_frame = _cf_create_result_chart_frame
|
|
|
|
|
|
on_chart_tab_changed = _cf_on_chart_tab_changed
|
2026-04-16 16:51:05 +08:00
|
|
|
|
def change_test_type(self, test_type):
|
|
|
|
|
|
"""切换测试类型"""
|
|
|
|
|
|
# 切换测试类型时,自动隐藏日志面板和 Local Dimming 面板
|
|
|
|
|
|
if self.current_panel in ("log", "local_dimming"):
|
|
|
|
|
|
self.hide_all_panels()
|
|
|
|
|
|
# 先保存当前测试类型的色度参数
|
|
|
|
|
|
if hasattr(self, "cct_x_ideal_var"):
|
|
|
|
|
|
try:
|
|
|
|
|
|
current_type = self.config.current_test_type
|
|
|
|
|
|
if current_type == "screen_module":
|
|
|
|
|
|
self.save_cct_params()
|
|
|
|
|
|
elif current_type == "sdr_movie":
|
|
|
|
|
|
self.save_sdr_cct_params()
|
|
|
|
|
|
elif current_type == "hdr_movie":
|
|
|
|
|
|
if hasattr(self, "save_hdr_cct_params"):
|
|
|
|
|
|
self.save_hdr_cct_params()
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
if hasattr(self, "log_gui"):
|
|
|
|
|
|
self.log_gui.log(f"保存参数失败: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
# 更新测试类型
|
|
|
|
|
|
self.test_type_var.set(test_type)
|
|
|
|
|
|
if hasattr(self, "config") and hasattr(self.config, "set_current_test_type"):
|
|
|
|
|
|
success = self.config.set_current_test_type(test_type)
|
|
|
|
|
|
if not success and hasattr(self, "log_gui"):
|
|
|
|
|
|
self.log_gui.log(f"切换测试类型失败: {test_type}")
|
|
|
|
|
|
|
|
|
|
|
|
# 更新测试项目和侧边栏
|
|
|
|
|
|
self.update_test_items()
|
|
|
|
|
|
self.update_sidebar_selection()
|
|
|
|
|
|
self.on_test_type_change()
|
|
|
|
|
|
|
|
|
|
|
|
# ========== ✅ 1. 切换信号格式 Tab ==========
|
|
|
|
|
|
if hasattr(self, "signal_tabs"):
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 定义测试类型与信号格式 Tab 的映射
|
|
|
|
|
|
tab_mapping = {
|
|
|
|
|
|
"screen_module": 0, # 屏模组测试
|
|
|
|
|
|
"sdr_movie": 1, # SDR测试
|
|
|
|
|
|
"hdr_movie": 2, # HDR
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
target_tab = tab_mapping.get(test_type, 0)
|
|
|
|
|
|
|
|
|
|
|
|
# 先启用所有 Tab
|
|
|
|
|
|
for i in range(3):
|
|
|
|
|
|
self.signal_tabs.tab(i, state="normal")
|
|
|
|
|
|
|
|
|
|
|
|
# 切换到目标 Tab
|
|
|
|
|
|
self.signal_tabs.select(target_tab)
|
|
|
|
|
|
|
|
|
|
|
|
# 强制刷新显示
|
|
|
|
|
|
self.signal_tabs.update()
|
|
|
|
|
|
self.root.update_idletasks()
|
|
|
|
|
|
|
|
|
|
|
|
# 强制显示对应的 Frame
|
|
|
|
|
|
if target_tab == 0:
|
|
|
|
|
|
self.screen_module_signal_frame.tkraise()
|
|
|
|
|
|
elif target_tab == 1:
|
|
|
|
|
|
self.sdr_signal_frame.tkraise()
|
|
|
|
|
|
elif target_tab == 2:
|
|
|
|
|
|
self.hdr_signal_frame.tkraise()
|
|
|
|
|
|
|
|
|
|
|
|
# 禁用其他 Tab
|
|
|
|
|
|
for i in range(3):
|
|
|
|
|
|
if i != target_tab:
|
|
|
|
|
|
self.signal_tabs.tab(i, state="disabled")
|
|
|
|
|
|
|
|
|
|
|
|
# 日志记录
|
|
|
|
|
|
if hasattr(self, "log_gui"):
|
|
|
|
|
|
tab_names = ["屏模组测试", "SDR测试", "HDR"]
|
|
|
|
|
|
self.log_gui.log(f"✓ 已切换到 {tab_names[target_tab]} 信号格式")
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
if hasattr(self, "log_gui"):
|
|
|
|
|
|
self.log_gui.log(f"切换信号格式失败: {str(e)}")
|
|
|
|
|
|
else:
|
|
|
|
|
|
if hasattr(self, "log_gui"):
|
|
|
|
|
|
self.log_gui.log("⚠️ signal_tabs 尚未创建")
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 2. 动态切换 Gamma/EOTF Tab ==========
|
|
|
|
|
|
if hasattr(self, "chart_notebook"):
|
|
|
|
|
|
try:
|
|
|
|
|
|
current_tabs = list(self.chart_notebook.tabs())
|
|
|
|
|
|
|
|
|
|
|
|
# 获取当前 Tab 的索引
|
|
|
|
|
|
gamma_tab_id = str(self.gamma_chart_frame)
|
|
|
|
|
|
eotf_tab_id = str(self.eotf_chart_frame)
|
|
|
|
|
|
|
|
|
|
|
|
if test_type == "hdr_movie":
|
|
|
|
|
|
# ========== HDR 测试:移除 Gamma,添加 EOTF ==========
|
|
|
|
|
|
|
|
|
|
|
|
# 1. 如果 Gamma Tab 存在,移除它
|
|
|
|
|
|
if gamma_tab_id in current_tabs:
|
|
|
|
|
|
gamma_index = current_tabs.index(gamma_tab_id)
|
|
|
|
|
|
self.chart_notebook.forget(gamma_index)
|
|
|
|
|
|
if hasattr(self, "log_gui"):
|
|
|
|
|
|
self.log_gui.log("✓ 已隐藏 Gamma 曲线 Tab")
|
|
|
|
|
|
|
|
|
|
|
|
# 2. 如果 EOTF Tab 不存在,添加它(在色域图之后)
|
|
|
|
|
|
if eotf_tab_id not in current_tabs:
|
|
|
|
|
|
self.chart_notebook.insert(
|
|
|
|
|
|
1, self.eotf_chart_frame, text="EOTF 曲线"
|
|
|
|
|
|
)
|
|
|
|
|
|
if hasattr(self, "log_gui"):
|
|
|
|
|
|
self.log_gui.log("✓ 已显示 EOTF 曲线 Tab")
|
|
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
# ========== SDR/屏模组测试:移除 EOTF,添加 Gamma ==========
|
|
|
|
|
|
|
|
|
|
|
|
# 1. 如果 EOTF Tab 存在,移除它
|
|
|
|
|
|
if eotf_tab_id in current_tabs:
|
|
|
|
|
|
eotf_index = current_tabs.index(eotf_tab_id)
|
|
|
|
|
|
self.chart_notebook.forget(eotf_index)
|
|
|
|
|
|
if hasattr(self, "log_gui"):
|
|
|
|
|
|
self.log_gui.log("✓ 已隐藏 EOTF 曲线 Tab")
|
|
|
|
|
|
|
|
|
|
|
|
# 2. 如果 Gamma Tab 不存在,添加它(在色域图之后)
|
|
|
|
|
|
if gamma_tab_id not in current_tabs:
|
|
|
|
|
|
self.chart_notebook.insert(
|
|
|
|
|
|
1, self.gamma_chart_frame, text="Gamma 曲线"
|
|
|
|
|
|
)
|
|
|
|
|
|
if hasattr(self, "log_gui"):
|
|
|
|
|
|
self.log_gui.log("✓ 已显示 Gamma 曲线 Tab")
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 3. 仅在 SDR 测试显示客户模板结果 Tab ==========
|
|
|
|
|
|
custom_tab_id = str(self.custom_template_tab_frame)
|
|
|
|
|
|
current_tabs = list(self.chart_notebook.tabs())
|
|
|
|
|
|
|
|
|
|
|
|
if test_type == "sdr_movie":
|
|
|
|
|
|
if custom_tab_id not in current_tabs:
|
|
|
|
|
|
self.chart_notebook.add(
|
|
|
|
|
|
self.custom_template_tab_frame,
|
|
|
|
|
|
text="客户模板结果显示",
|
|
|
|
|
|
)
|
|
|
|
|
|
if hasattr(self, "log_gui"):
|
|
|
|
|
|
self.log_gui.log("✓ 已显示客户模板结果 Tab")
|
|
|
|
|
|
else:
|
|
|
|
|
|
if custom_tab_id in current_tabs:
|
|
|
|
|
|
self.chart_notebook.forget(self.custom_template_tab_frame)
|
|
|
|
|
|
if hasattr(self, "log_gui"):
|
|
|
|
|
|
self.log_gui.log("✓ 已隐藏客户模板结果 Tab")
|
|
|
|
|
|
|
|
|
|
|
|
# 刷新显示
|
|
|
|
|
|
self.chart_notebook.update_idletasks()
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
if hasattr(self, "log_gui"):
|
|
|
|
|
|
self.log_gui.log(f"切换 Gamma/EOTF Tab 失败: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
def update_sidebar_selection(self):
|
|
|
|
|
|
"""更新侧边栏按钮的选中状态"""
|
|
|
|
|
|
# 重置所有按钮样式为默认
|
|
|
|
|
|
self.screen_module_btn.configure(style="Sidebar.TButton")
|
|
|
|
|
|
self.sdr_movie_btn.configure(style="Sidebar.TButton")
|
|
|
|
|
|
self.hdr_movie_btn.configure(style="Sidebar.TButton")
|
|
|
|
|
|
|
|
|
|
|
|
# 设置当前选中按钮的样式
|
|
|
|
|
|
current_type = self.test_type_var.get()
|
|
|
|
|
|
if current_type == "screen_module":
|
|
|
|
|
|
self.screen_module_btn.configure(style="SidebarSelected.TButton")
|
|
|
|
|
|
elif current_type == "sdr_movie":
|
|
|
|
|
|
self.sdr_movie_btn.configure(style="SidebarSelected.TButton")
|
|
|
|
|
|
elif current_type == "hdr_movie":
|
|
|
|
|
|
self.hdr_movie_btn.configure(style="SidebarSelected.TButton")
|
|
|
|
|
|
|
|
|
|
|
|
def on_test_type_change(self):
|
|
|
|
|
|
"""根据测试类型更新内容区域"""
|
|
|
|
|
|
test_type = self.test_type_var.get()
|
|
|
|
|
|
|
|
|
|
|
|
# 获取当前测试类型的配置
|
|
|
|
|
|
if hasattr(self, "config") and hasattr(self.config, "get_current_config"):
|
|
|
|
|
|
current_config = self.config.get_current_config()
|
|
|
|
|
|
|
|
|
|
|
|
# 更新配置信息显示
|
|
|
|
|
|
self.update_config_info_display()
|
|
|
|
|
|
|
|
|
|
|
|
# SDR 选中时显示客户模版按钮
|
|
|
|
|
|
self.update_custom_button_visibility()
|
|
|
|
|
|
|
|
|
|
|
|
def update_custom_button_visibility(self):
|
|
|
|
|
|
"""只在 SDR 测试时显示客户模版按钮"""
|
|
|
|
|
|
if not hasattr(self, "custom_btn") or not hasattr(self, "test_type_var"):
|
|
|
|
|
|
return
|
|
|
|
|
|
if self.test_type_var.get() == "sdr_movie":
|
|
|
|
|
|
if not self.custom_btn.winfo_manager():
|
|
|
|
|
|
self.custom_btn.pack(side=tk.LEFT, padx=5)
|
|
|
|
|
|
else:
|
|
|
|
|
|
if self.custom_btn.winfo_manager():
|
|
|
|
|
|
self.custom_btn.pack_forget()
|
|
|
|
|
|
|
|
|
|
|
|
def start_test(self):
|
|
|
|
|
|
"""开始测试"""
|
|
|
|
|
|
# 检查设备连接状态
|
|
|
|
|
|
if self.ca is None or self.ucd is None:
|
|
|
|
|
|
messagebox.showerror("错误", "请先连接CA410和信号发生器")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 检查是否已经在测试中
|
|
|
|
|
|
if self.testing:
|
|
|
|
|
|
messagebox.showinfo("提示", "测试已在进行中")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# ✅ 禁用并隐藏单步调试
|
|
|
|
|
|
if hasattr(self, "debug_panel"):
|
|
|
|
|
|
self.debug_panel.disable_all_debug()
|
|
|
|
|
|
self.log_gui.log("✓ 单步调试已禁用")
|
|
|
|
|
|
|
|
|
|
|
|
if hasattr(self, "debug_container"):
|
|
|
|
|
|
self.debug_container.pack_forget()
|
|
|
|
|
|
self.log_gui.log("✓ 单步调试面板已隐藏")
|
|
|
|
|
|
|
|
|
|
|
|
# 获取测试类型和测试项目
|
|
|
|
|
|
test_type = self.test_type_var.get()
|
|
|
|
|
|
test_items = self.get_selected_test_items()
|
|
|
|
|
|
|
|
|
|
|
|
if not test_items:
|
|
|
|
|
|
messagebox.showinfo("提示", "请至少选择一个测试项目")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 自动收起配置项
|
|
|
|
|
|
if hasattr(self, "config_panel_frame"):
|
|
|
|
|
|
try:
|
|
|
|
|
|
if self.config_panel_frame.winfo_viewable():
|
|
|
|
|
|
self.config_panel_frame.btn.invoke()
|
|
|
|
|
|
self.root.update_idletasks()
|
|
|
|
|
|
time.sleep(0.2)
|
|
|
|
|
|
except:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
# 禁用配置项按钮
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.config_panel_frame.btn.configure(state="disabled")
|
|
|
|
|
|
except:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
# ✅ 新增:禁用色域参考标准下拉框
|
|
|
|
|
|
try:
|
|
|
|
|
|
if hasattr(self, "screen_gamut_combo"):
|
|
|
|
|
|
self.screen_gamut_combo.configure(state="disabled")
|
|
|
|
|
|
if hasattr(self, "sdr_gamut_combo"):
|
|
|
|
|
|
self.sdr_gamut_combo.configure(state="disabled")
|
|
|
|
|
|
if hasattr(self, "hdr_gamut_combo"):
|
|
|
|
|
|
self.hdr_gamut_combo.configure(state="disabled")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
self.log_gui.log(f"禁用色域参考标准失败: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
# 隐藏所有重新计算按钮
|
|
|
|
|
|
if hasattr(self, "recalc_cct_btn"):
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.recalc_cct_btn.grid_remove()
|
|
|
|
|
|
except:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
if hasattr(self, "sdr_recalc_cct_btn"):
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.sdr_recalc_cct_btn.grid_remove()
|
|
|
|
|
|
except:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
if hasattr(self, "hdr_recalc_cct_btn"):
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.hdr_recalc_cct_btn.grid_remove()
|
|
|
|
|
|
except:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
# 更新UI状态
|
|
|
|
|
|
self.testing = True
|
|
|
|
|
|
self.start_btn.config(state=tk.DISABLED)
|
|
|
|
|
|
self.stop_btn.config(state=tk.NORMAL)
|
|
|
|
|
|
self.save_btn.config(state=tk.DISABLED)
|
|
|
|
|
|
self.clear_config_btn.config(state=tk.DISABLED)
|
|
|
|
|
|
self.status_var.set("测试进行中...")
|
|
|
|
|
|
|
|
|
|
|
|
# 清空日志和图表
|
|
|
|
|
|
self.log_gui.clear_log()
|
|
|
|
|
|
self.clear_chart()
|
|
|
|
|
|
|
|
|
|
|
|
# 根据测试类型显示不同提示
|
|
|
|
|
|
if test_type == "screen_module":
|
|
|
|
|
|
# 屏模组测试:提示 byPass All PQ
|
|
|
|
|
|
message = f"开始屏模组性能测试,请 byPass All PQ"
|
|
|
|
|
|
|
|
|
|
|
|
elif test_type == "sdr_movie":
|
|
|
|
|
|
# SDR测试:提示设置正确图像模式
|
|
|
|
|
|
message = f"开始 SDR Movie 测试,请设置正确的图像模式"
|
|
|
|
|
|
|
|
|
|
|
|
elif test_type == "hdr_movie":
|
|
|
|
|
|
# HDR测试:提示设置正确图像模式
|
|
|
|
|
|
message = f"开始 HDR Movie 测试,请设置正确的图像模式"
|
|
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
message = f"开始{self.get_test_type_name(test_type)}测试"
|
|
|
|
|
|
|
|
|
|
|
|
confirm = messagebox.askyesno("确认测试", message)
|
|
|
|
|
|
|
|
|
|
|
|
if not confirm:
|
|
|
|
|
|
self.testing = False
|
|
|
|
|
|
self.start_btn.config(state=tk.NORMAL)
|
|
|
|
|
|
self.stop_btn.config(state=tk.DISABLED)
|
|
|
|
|
|
self.clear_config_btn.config(state=tk.NORMAL)
|
|
|
|
|
|
self.status_var.set("测试已取消")
|
|
|
|
|
|
|
|
|
|
|
|
# 恢复配置项按钮
|
|
|
|
|
|
if hasattr(self, "config_panel_frame"):
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.config_panel_frame.btn.configure(state="normal")
|
|
|
|
|
|
except:
|
|
|
|
|
|
pass
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 在新线程中执行测试
|
|
|
|
|
|
self.test_thread = threading.Thread(
|
|
|
|
|
|
target=self.run_test, args=(test_type, test_items)
|
|
|
|
|
|
)
|
|
|
|
|
|
self.test_thread.daemon = True
|
|
|
|
|
|
self.test_thread.start()
|
|
|
|
|
|
|
|
|
|
|
|
def stop_test(self):
|
|
|
|
|
|
"""停止测试 - 放弃本次所有数据(完全集成版)"""
|
|
|
|
|
|
if not self.testing:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 1. 添加确认对话框 ==========
|
|
|
|
|
|
confirm = messagebox.askyesno(
|
|
|
|
|
|
"确认停止测试",
|
|
|
|
|
|
"测试正在进行中,确定要停止吗?\n\n⚠️ 停止后将放弃本次测试的所有数据,无法保存。",
|
|
|
|
|
|
icon="warning",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if not confirm:
|
|
|
|
|
|
self.log_gui.log("用户取消停止操作")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 2. 立即设置停止标志 ==========
|
|
|
|
|
|
self.testing = False # ← 关键:先设置标志,让测试线程停止
|
|
|
|
|
|
|
|
|
|
|
|
self.log_gui.log("=" * 50)
|
|
|
|
|
|
self.log_gui.log("⚠️ 正在停止测试...")
|
|
|
|
|
|
self.log_gui.log("=" * 50)
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 3. 立即更新UI状态(让用户感知到停止)==========
|
|
|
|
|
|
self.stop_btn.config(state=tk.DISABLED)
|
|
|
|
|
|
self.status_var.set("正在停止测试,请稍候...")
|
|
|
|
|
|
self.root.update() # 立即刷新界面
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 4. 等待测试线程结束 ==========
|
|
|
|
|
|
if self.test_thread and self.test_thread.is_alive():
|
|
|
|
|
|
self.log_gui.log("等待测试线程结束...")
|
|
|
|
|
|
|
|
|
|
|
|
# 等待最多5秒
|
|
|
|
|
|
for i in range(50): # 50 * 0.1秒 = 5秒
|
|
|
|
|
|
if not self.test_thread.is_alive():
|
|
|
|
|
|
break
|
|
|
|
|
|
time.sleep(0.1)
|
|
|
|
|
|
self.root.update() # 保持界面响应
|
|
|
|
|
|
|
|
|
|
|
|
if self.test_thread.is_alive():
|
|
|
|
|
|
self.log_gui.log("⚠️ 测试线程未能正常结束,将在后台继续等待")
|
|
|
|
|
|
else:
|
|
|
|
|
|
self.log_gui.log("✓ 测试线程已结束")
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 5. 延迟1秒后执行清理(使用内部函数)==========
|
|
|
|
|
|
def cleanup_and_finish():
|
|
|
|
|
|
"""清理数据并完成停止操作"""
|
|
|
|
|
|
# ========== 5.1 清理测试数据 ==========
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.log_gui.log("清理测试数据...")
|
|
|
|
|
|
|
|
|
|
|
|
# 清空测试结果对象
|
|
|
|
|
|
if hasattr(self, "results"):
|
|
|
|
|
|
self.results = None
|
|
|
|
|
|
self.log_gui.log(" ✓ 测试结果对象已清空")
|
|
|
|
|
|
|
|
|
|
|
|
# 清空中间数据缓存
|
|
|
|
|
|
for attr in [
|
|
|
|
|
|
"gamut_results",
|
|
|
|
|
|
"gamma_results",
|
|
|
|
|
|
"cct_results",
|
|
|
|
|
|
"contrast_results",
|
|
|
|
|
|
"accuracy_results",
|
|
|
|
|
|
]:
|
|
|
|
|
|
if hasattr(self, attr):
|
|
|
|
|
|
setattr(self, attr, None)
|
|
|
|
|
|
|
|
|
|
|
|
self.log_gui.log(" ✓ 所有中间数据已清空")
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
self.log_gui.log(f"⚠️ 清理数据时出错: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 5.2 清空图表显示 ==========
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.clear_chart()
|
|
|
|
|
|
self.log_gui.log("✓ 图表已清空")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
self.log_gui.log(f"⚠️ 清空图表时出错: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.clear_custom_template_results()
|
|
|
|
|
|
self.log_gui.log("✓ 客户模板结果表格已清空")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
self.log_gui.log(f"⚠️ 清空客户模板结果表格失败: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 5.2.5 跳转到色域图Tab(第一个Tab)==========
|
|
|
|
|
|
try:
|
|
|
|
|
|
if hasattr(self, "chart_notebook"):
|
2026-04-20 10:16:31 +08:00
|
|
|
|
self.chart_notebook.select(self.gamut_chart_frame)
|
2026-04-16 16:51:05 +08:00
|
|
|
|
self.root.update_idletasks() # ← 刷新界面
|
|
|
|
|
|
self.log_gui.log("✓ 已跳转到色域图界面")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
self.log_gui.log(f"⚠️ 跳转到色域图失败: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 5.3 更新UI状态 ==========
|
|
|
|
|
|
self.set_custom_result_table_locked(False)
|
|
|
|
|
|
self.start_btn.config(state=tk.NORMAL)
|
|
|
|
|
|
self.stop_btn.config(state=tk.DISABLED)
|
|
|
|
|
|
self.save_btn.config(state=tk.DISABLED)
|
|
|
|
|
|
self.clear_config_btn.config(state=tk.NORMAL)
|
|
|
|
|
|
if hasattr(self, "custom_btn"):
|
|
|
|
|
|
self.custom_btn.config(state=tk.NORMAL)
|
|
|
|
|
|
self.status_var.set("测试已停止 - 数据已清空")
|
|
|
|
|
|
self.log_gui.log("✓ UI状态已更新")
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 5.4 恢复配置项按钮 ==========
|
|
|
|
|
|
if hasattr(self, "config_panel_frame"):
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.config_panel_frame.btn.configure(state="normal")
|
|
|
|
|
|
self.log_gui.log("✓ 配置项已恢复")
|
|
|
|
|
|
except:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 5.4.5 禁用色域参考标准下拉框 ==========
|
|
|
|
|
|
try:
|
|
|
|
|
|
if hasattr(self, "screen_gamut_combo"):
|
|
|
|
|
|
self.screen_gamut_combo.configure(state="disabled")
|
|
|
|
|
|
if hasattr(self, "sdr_gamut_combo"):
|
|
|
|
|
|
self.sdr_gamut_combo.configure(state="disabled")
|
|
|
|
|
|
if hasattr(self, "hdr_gamut_combo"):
|
|
|
|
|
|
self.hdr_gamut_combo.configure(state="disabled")
|
|
|
|
|
|
self.log_gui.log("✓ 色域参考标准已禁用")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
self.log_gui.log(f"禁用色域参考标准失败: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 5.5 隐藏所有重新计算按钮 ==========
|
|
|
|
|
|
try:
|
|
|
|
|
|
button_hidden_count = 0
|
|
|
|
|
|
for btn_attr in [
|
|
|
|
|
|
"recalc_cct_btn",
|
|
|
|
|
|
"sdr_recalc_cct_btn",
|
|
|
|
|
|
"hdr_recalc_cct_btn",
|
|
|
|
|
|
"recalc_gamut_btn", # ✅ 新增
|
|
|
|
|
|
"sdr_recalc_gamut_btn", # ✅ 新增
|
|
|
|
|
|
"hdr_recalc_gamut_btn", # ✅ 新增
|
|
|
|
|
|
]:
|
|
|
|
|
|
if hasattr(self, btn_attr):
|
|
|
|
|
|
try:
|
|
|
|
|
|
getattr(self, btn_attr).grid_remove()
|
|
|
|
|
|
button_hidden_count += 1
|
|
|
|
|
|
except:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
if button_hidden_count > 0:
|
|
|
|
|
|
self.log_gui.log(f"✓ 已隐藏 {button_hidden_count} 个重新计算按钮")
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
self.log_gui.log(f"⚠️ 隐藏按钮时出错: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 5.6 禁用并隐藏单步调试 ==========
|
|
|
|
|
|
if hasattr(self, "debug_panel"):
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.debug_panel.disable_all_debug()
|
|
|
|
|
|
self.log_gui.log("✓ 单步调试已禁用")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
self.log_gui.log(f"⚠️ 禁用单步调试失败: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
# ✅ 隐藏调试面板
|
|
|
|
|
|
if hasattr(self, "debug_container"):
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.debug_container.pack_forget()
|
|
|
|
|
|
self.log_gui.log("✓ 单步调试面板已隐藏")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
self.log_gui.log(f"⚠️ 隐藏调试面板失败: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 5.7 最终日志 ==========
|
|
|
|
|
|
self.log_gui.log("=" * 50)
|
|
|
|
|
|
self.log_gui.log("✓ 测试已停止,所有数据已清空")
|
|
|
|
|
|
self.log_gui.log("=" * 50)
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 5.8 显示提示信息 ==========
|
|
|
|
|
|
messagebox.showinfo(
|
|
|
|
|
|
"测试已停止",
|
|
|
|
|
|
"测试已停止,本次测试数据已清空。\n\n可以重新开始新的测试。",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 延迟1秒后执行清理 ==========
|
|
|
|
|
|
self.root.after(1000, cleanup_and_finish)
|
|
|
|
|
|
|
|
|
|
|
|
def save_results(self):
|
|
|
|
|
|
"""保存测试结果(图片 + Excel)"""
|
|
|
|
|
|
save_dir = filedialog.askdirectory(title="选择保存测试结果的目录")
|
|
|
|
|
|
if not save_dir:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
|
|
|
|
test_type = self.get_test_type_name(self.test_type_var.get())
|
|
|
|
|
|
result_dir = os.path.join(save_dir, f"{test_type}_{timestamp}")
|
|
|
|
|
|
os.makedirs(result_dir, exist_ok=True)
|
|
|
|
|
|
|
|
|
|
|
|
# ========== ✅ 获取当前测试类型和已选测试项 ==========
|
|
|
|
|
|
current_test_type = self.test_type_var.get()
|
|
|
|
|
|
selected_items = self.get_selected_test_items()
|
|
|
|
|
|
|
|
|
|
|
|
self.log_gui.log(f"保存测试类型: {current_test_type}")
|
|
|
|
|
|
self.log_gui.log(f"已选测试项: {selected_items}")
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 保存图片 ==========
|
|
|
|
|
|
if "gamut" in selected_items and hasattr(self, "gamut_fig"):
|
|
|
|
|
|
gamut_path = os.path.join(result_dir, "色域测试结果.png")
|
|
|
|
|
|
self.gamut_fig.savefig(gamut_path, dpi=300)
|
|
|
|
|
|
self.log_gui.log(f"✓ 已保存: 色域测试结果.png")
|
|
|
|
|
|
|
|
|
|
|
|
if current_test_type in ["screen_module", "sdr_movie"]:
|
|
|
|
|
|
if "gamma" in selected_items and hasattr(self, "gamma_fig"):
|
|
|
|
|
|
gamma_path = os.path.join(result_dir, "Gamma曲线测试结果.png")
|
|
|
|
|
|
self.gamma_fig.savefig(gamma_path, dpi=300)
|
|
|
|
|
|
self.log_gui.log(f"✓ 已保存: Gamma曲线测试结果.png")
|
|
|
|
|
|
|
|
|
|
|
|
if current_test_type == "hdr_movie":
|
|
|
|
|
|
if "eotf" in selected_items and hasattr(self, "eotf_fig"):
|
|
|
|
|
|
eotf_path = os.path.join(result_dir, "EOTF曲线测试结果.png")
|
|
|
|
|
|
self.eotf_fig.savefig(eotf_path, dpi=300)
|
|
|
|
|
|
self.log_gui.log(f"✓ 已保存: EOTF曲线测试结果.png")
|
|
|
|
|
|
|
|
|
|
|
|
if "cct" in selected_items and hasattr(self, "cct_fig"):
|
|
|
|
|
|
cct_path = os.path.join(result_dir, "色度一致性测试结果.png")
|
|
|
|
|
|
self.cct_fig.savefig(cct_path, dpi=300)
|
|
|
|
|
|
self.log_gui.log(f"✓ 已保存: 色度一致性测试结果.png")
|
|
|
|
|
|
|
|
|
|
|
|
if "contrast" in selected_items and hasattr(self, "contrast_fig"):
|
|
|
|
|
|
contrast_path = os.path.join(result_dir, "对比度测试结果.png")
|
|
|
|
|
|
self.contrast_fig.savefig(contrast_path, dpi=300, bbox_inches="tight")
|
|
|
|
|
|
self.log_gui.log(f"✓ 已保存: 对比度测试结果.png")
|
|
|
|
|
|
|
|
|
|
|
|
if current_test_type in ["sdr_movie", "hdr_movie"]:
|
|
|
|
|
|
if "accuracy" in selected_items and hasattr(self, "accuracy_fig"):
|
|
|
|
|
|
accuracy_path = os.path.join(result_dir, "色准测试结果.png")
|
|
|
|
|
|
self.accuracy_fig.savefig(accuracy_path, dpi=300)
|
|
|
|
|
|
self.log_gui.log(f"✓ 已保存: 色准测试结果.png")
|
|
|
|
|
|
|
|
|
|
|
|
# ========== ✅ 屏模组测试 Excel 导出 ==========
|
|
|
|
|
|
if (
|
|
|
|
|
|
current_test_type == "screen_module"
|
|
|
|
|
|
and hasattr(self, "results")
|
|
|
|
|
|
and self.results
|
|
|
|
|
|
):
|
|
|
|
|
|
try:
|
|
|
|
|
|
import openpyxl
|
|
|
|
|
|
from openpyxl.styles import (
|
|
|
|
|
|
Font,
|
|
|
|
|
|
Alignment,
|
|
|
|
|
|
PatternFill,
|
|
|
|
|
|
Border,
|
|
|
|
|
|
Side,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
self.log_gui.log("=" * 60)
|
|
|
|
|
|
self.log_gui.log("开始生成屏模组 Excel 数据报告...")
|
|
|
|
|
|
|
|
|
|
|
|
wb = openpyxl.Workbook()
|
|
|
|
|
|
ws = wb.active
|
|
|
|
|
|
ws.title = "测试数据"
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 样式定义 ==========
|
|
|
|
|
|
title_font = Font(
|
|
|
|
|
|
name="微软雅黑", size=16, bold=True, color="FFFFFF"
|
|
|
|
|
|
)
|
|
|
|
|
|
title_fill = PatternFill(
|
|
|
|
|
|
start_color="4472C4", end_color="4472C4", fill_type="solid"
|
|
|
|
|
|
)
|
|
|
|
|
|
title_alignment = Alignment(horizontal="center", vertical="center")
|
|
|
|
|
|
|
|
|
|
|
|
section_font = Font(
|
|
|
|
|
|
name="微软雅黑", size=13, bold=True, color="FFFFFF"
|
|
|
|
|
|
)
|
|
|
|
|
|
section_fill = PatternFill(
|
|
|
|
|
|
start_color="5B9BD5", end_color="5B9BD5", fill_type="solid"
|
|
|
|
|
|
)
|
|
|
|
|
|
section_alignment = Alignment(
|
|
|
|
|
|
horizontal="center", vertical="center"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
header_font = Font(
|
|
|
|
|
|
name="微软雅黑", size=10, bold=True, color="FFFFFF"
|
|
|
|
|
|
)
|
|
|
|
|
|
header_fill = PatternFill(
|
|
|
|
|
|
start_color="70AD47", end_color="70AD47", fill_type="solid"
|
|
|
|
|
|
)
|
|
|
|
|
|
header_alignment = Alignment(
|
|
|
|
|
|
horizontal="center", vertical="center", wrap_text=True
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
data_font = Font(name="微软雅黑", size=10)
|
|
|
|
|
|
data_alignment = Alignment(horizontal="center", vertical="center")
|
|
|
|
|
|
|
|
|
|
|
|
label_font = Font(name="微软雅黑", size=10, bold=True)
|
|
|
|
|
|
|
|
|
|
|
|
thin_border = Border(
|
|
|
|
|
|
left=Side(style="thin"),
|
|
|
|
|
|
right=Side(style="thin"),
|
|
|
|
|
|
top=Side(style="thin"),
|
|
|
|
|
|
bottom=Side(style="thin"),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 总标题 ==========
|
|
|
|
|
|
ws.merge_cells("A1:G1")
|
|
|
|
|
|
ws["A1"] = "屏模组性能测试数据报告"
|
|
|
|
|
|
ws["A1"].font = title_font
|
|
|
|
|
|
ws["A1"].fill = title_fill
|
|
|
|
|
|
ws["A1"].alignment = title_alignment
|
|
|
|
|
|
ws.row_dimensions[1].height = 35
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 测试基本信息 ==========
|
|
|
|
|
|
row = 3
|
|
|
|
|
|
ws.merge_cells(f"A{row}:B{row}")
|
|
|
|
|
|
ws[f"A{row}"] = "📋 测试基本信息"
|
|
|
|
|
|
ws[f"A{row}"].font = section_font
|
|
|
|
|
|
ws[f"A{row}"].fill = section_fill
|
|
|
|
|
|
ws[f"A{row}"].alignment = section_alignment
|
|
|
|
|
|
ws.row_dimensions[row].height = 25
|
|
|
|
|
|
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
info_items = [
|
|
|
|
|
|
(
|
|
|
|
|
|
"测试时间",
|
|
|
|
|
|
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
|
|
|
|
),
|
|
|
|
|
|
("测试类型", "屏模组"),
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
for label, value in info_items:
|
|
|
|
|
|
ws[f"A{row}"] = label
|
|
|
|
|
|
ws[f"B{row}"] = value
|
|
|
|
|
|
ws[f"A{row}"].font = label_font
|
|
|
|
|
|
ws[f"B{row}"].font = data_font
|
|
|
|
|
|
ws[f"A{row}"].border = thin_border
|
|
|
|
|
|
ws[f"B{row}"].border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
row += 1 # 空行
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 1. 色域数据 ==========
|
|
|
|
|
|
if "gamut" in selected_items:
|
|
|
|
|
|
rgb_data = self.results.get_intermediate_data("gamut", "rgb")
|
|
|
|
|
|
gamut_final_result = None
|
|
|
|
|
|
if "gamut" in self.results.test_items:
|
|
|
|
|
|
gamut_final_result = self.results.test_items[
|
|
|
|
|
|
"gamut"
|
|
|
|
|
|
].final_result
|
|
|
|
|
|
|
|
|
|
|
|
if rgb_data and len(rgb_data) >= 3:
|
|
|
|
|
|
# 分区标题
|
|
|
|
|
|
ws.merge_cells(f"A{row}:G{row}")
|
|
|
|
|
|
ws[f"A{row}"] = "🎨 色域测试数据"
|
|
|
|
|
|
ws[f"A{row}"].font = section_font
|
|
|
|
|
|
ws[f"A{row}"].fill = section_fill
|
|
|
|
|
|
ws[f"A{row}"].alignment = section_alignment
|
|
|
|
|
|
ws.row_dimensions[row].height = 25
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
if gamut_final_result:
|
|
|
|
|
|
# 第一行:参考标准
|
|
|
|
|
|
ws[f"A{row}"] = "参考标准"
|
|
|
|
|
|
ws[f"B{row}"] = gamut_final_result.get(
|
|
|
|
|
|
"reference", "DCI-P3"
|
|
|
|
|
|
)
|
|
|
|
|
|
ws[f"A{row}"].font = label_font
|
|
|
|
|
|
ws[f"B{row}"].font = data_font
|
|
|
|
|
|
ws[f"A{row}"].border = thin_border
|
|
|
|
|
|
ws[f"B{row}"].border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
# 第二行:XY 覆盖率 | UV 覆盖率
|
|
|
|
|
|
xy_coverage = gamut_final_result.get("coverage", 0)
|
|
|
|
|
|
uv_coverage = (
|
|
|
|
|
|
gamut_final_result.get("uv_coverage", 0)
|
|
|
|
|
|
or gamut_final_result.get("uv_space_coverage", 0)
|
|
|
|
|
|
or gamut_final_result.get("coverage_uv", 0)
|
|
|
|
|
|
or 0
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"A{row}"] = "XY 色域覆盖率"
|
|
|
|
|
|
ws[f"B{row}"] = f"{xy_coverage:.2f}%"
|
|
|
|
|
|
ws[f"C{row}"] = "UV 色域覆盖率"
|
|
|
|
|
|
ws[f"D{row}"] = f"{uv_coverage:.2f}%"
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"A{row}"].font = label_font
|
|
|
|
|
|
ws[f"B{row}"].font = data_font
|
|
|
|
|
|
ws[f"C{row}"].font = label_font
|
|
|
|
|
|
ws[f"D{row}"].font = data_font
|
|
|
|
|
|
|
|
|
|
|
|
for col in ["A", "B", "C", "D"]:
|
|
|
|
|
|
ws[f"{col}{row}"].border = thin_border
|
|
|
|
|
|
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
# RGB 数据表格
|
|
|
|
|
|
headers = [
|
|
|
|
|
|
"点位",
|
|
|
|
|
|
"x 坐标",
|
|
|
|
|
|
"y 坐标",
|
|
|
|
|
|
"亮度 (cd/m²)",
|
|
|
|
|
|
"",
|
|
|
|
|
|
"",
|
|
|
|
|
|
"",
|
|
|
|
|
|
]
|
|
|
|
|
|
for col_idx, header in enumerate(headers, start=1):
|
|
|
|
|
|
cell = ws.cell(row=row, column=col_idx)
|
|
|
|
|
|
cell.value = header
|
|
|
|
|
|
cell.font = header_font
|
|
|
|
|
|
cell.fill = header_fill
|
|
|
|
|
|
cell.alignment = header_alignment
|
|
|
|
|
|
cell.border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
rgb_labels = ["Red", "Green", "Blue"]
|
|
|
|
|
|
for i, result in enumerate(rgb_data[:3]):
|
|
|
|
|
|
x, y, lv = result[0], result[1], result[2]
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"A{row}"] = rgb_labels[i]
|
|
|
|
|
|
ws[f"B{row}"] = x
|
|
|
|
|
|
ws[f"C{row}"] = y
|
|
|
|
|
|
ws[f"D{row}"] = lv
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"A{row}"].font = data_font
|
|
|
|
|
|
ws[f"A{row}"].alignment = data_alignment
|
|
|
|
|
|
ws[f"A{row}"].border = thin_border
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"B{row}"].number_format = "0.0000"
|
|
|
|
|
|
ws[f"B{row}"].font = data_font
|
|
|
|
|
|
ws[f"B{row}"].alignment = data_alignment
|
|
|
|
|
|
ws[f"B{row}"].border = thin_border
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"C{row}"].number_format = "0.0000"
|
|
|
|
|
|
ws[f"C{row}"].font = data_font
|
|
|
|
|
|
ws[f"C{row}"].alignment = data_alignment
|
|
|
|
|
|
ws[f"C{row}"].border = thin_border
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"D{row}"].number_format = "0.00"
|
|
|
|
|
|
ws[f"D{row}"].font = data_font
|
|
|
|
|
|
ws[f"D{row}"].alignment = data_alignment
|
|
|
|
|
|
ws[f"D{row}"].border = thin_border
|
|
|
|
|
|
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
row += 1 # 空行
|
|
|
|
|
|
self.log_gui.log(" ✓ 添加色域数据")
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 2. Gamma 数据 ==========
|
|
|
|
|
|
if "gamma" in selected_items:
|
|
|
|
|
|
gray_data = self.results.get_intermediate_data("shared", "gray")
|
|
|
|
|
|
if not gray_data:
|
|
|
|
|
|
gray_data = self.results.get_intermediate_data(
|
|
|
|
|
|
"gamma", "gray"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
gamma_final_result = None
|
|
|
|
|
|
if "gamma" in self.results.test_items:
|
|
|
|
|
|
gamma_final_result = self.results.test_items[
|
|
|
|
|
|
"gamma"
|
|
|
|
|
|
].final_result
|
|
|
|
|
|
|
|
|
|
|
|
if gray_data and len(gray_data) > 0 and gamma_final_result:
|
|
|
|
|
|
gamma_list = gamma_final_result.get("gamma", [])
|
|
|
|
|
|
L_bar_list = gamma_final_result.get("L_bar", [])
|
|
|
|
|
|
|
|
|
|
|
|
# 分区标题
|
|
|
|
|
|
ws.merge_cells(f"A{row}:G{row}")
|
|
|
|
|
|
ws[f"A{row}"] = "📊 Gamma 曲线数据"
|
|
|
|
|
|
ws[f"A{row}"].font = section_font
|
|
|
|
|
|
ws[f"A{row}"].fill = section_fill
|
|
|
|
|
|
ws[f"A{row}"].alignment = section_alignment
|
|
|
|
|
|
ws.row_dimensions[row].height = 25
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
# Gamma 统计信息
|
|
|
|
|
|
valid_gamma = []
|
|
|
|
|
|
if gamma_list:
|
|
|
|
|
|
for item in gamma_list:
|
|
|
|
|
|
if (
|
|
|
|
|
|
isinstance(item, (list, tuple))
|
|
|
|
|
|
and len(item) >= 4
|
|
|
|
|
|
):
|
|
|
|
|
|
gamma_val = item[3]
|
|
|
|
|
|
if 0.5 < gamma_val < 5.0:
|
|
|
|
|
|
valid_gamma.append(gamma_val)
|
|
|
|
|
|
|
|
|
|
|
|
if valid_gamma:
|
|
|
|
|
|
avg_gamma = sum(valid_gamma) / len(valid_gamma)
|
|
|
|
|
|
max_gamma = max(valid_gamma)
|
|
|
|
|
|
min_gamma = min(valid_gamma)
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"A{row}"] = "平均 Gamma"
|
|
|
|
|
|
ws[f"B{row}"] = f"{avg_gamma:.3f}"
|
|
|
|
|
|
ws[f"C{row}"] = "最大 Gamma"
|
|
|
|
|
|
ws[f"D{row}"] = f"{max_gamma:.3f}"
|
|
|
|
|
|
ws[f"E{row}"] = "最小 Gamma"
|
|
|
|
|
|
ws[f"F{row}"] = f"{min_gamma:.3f}"
|
|
|
|
|
|
|
|
|
|
|
|
for col in ["A", "B", "C", "D", "E", "F"]:
|
|
|
|
|
|
ws[f"{col}{row}"].font = (
|
|
|
|
|
|
label_font
|
|
|
|
|
|
if col in ["A", "C", "E"]
|
|
|
|
|
|
else data_font
|
|
|
|
|
|
)
|
|
|
|
|
|
ws[f"{col}{row}"].border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
# Gamma 数据表格
|
|
|
|
|
|
headers = [
|
|
|
|
|
|
"灰阶 (%)",
|
|
|
|
|
|
"x 坐标",
|
|
|
|
|
|
"y 坐标",
|
|
|
|
|
|
"实测亮度\n(cd/m²)",
|
|
|
|
|
|
"归一化亮度\n(L_bar)",
|
|
|
|
|
|
"Gamma 值",
|
|
|
|
|
|
"",
|
|
|
|
|
|
]
|
|
|
|
|
|
for col_idx, header in enumerate(headers, start=1):
|
|
|
|
|
|
cell = ws.cell(row=row, column=col_idx)
|
|
|
|
|
|
cell.value = header
|
|
|
|
|
|
cell.font = header_font
|
|
|
|
|
|
cell.fill = header_fill
|
|
|
|
|
|
cell.alignment = header_alignment
|
|
|
|
|
|
cell.border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
total_points = len(gray_data)
|
|
|
|
|
|
for i in range(total_points - 1, -1, -1):
|
|
|
|
|
|
gray_level = (
|
|
|
|
|
|
100 - int(i * 100 / (total_points - 1))
|
|
|
|
|
|
if total_points > 1
|
|
|
|
|
|
else 0
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
x, y, lv = (
|
|
|
|
|
|
gray_data[i][0],
|
|
|
|
|
|
gray_data[i][1],
|
|
|
|
|
|
gray_data[i][2],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
L_bar_val = L_bar_list[i] if i < len(L_bar_list) else 0
|
|
|
|
|
|
|
|
|
|
|
|
gamma_val = None
|
|
|
|
|
|
if (
|
|
|
|
|
|
i < len(gamma_list)
|
|
|
|
|
|
and isinstance(gamma_list[i], (list, tuple))
|
|
|
|
|
|
and len(gamma_list[i]) >= 4
|
|
|
|
|
|
):
|
|
|
|
|
|
gamma_val = gamma_list[i][3]
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"A{row}"] = gray_level
|
|
|
|
|
|
ws[f"B{row}"] = x
|
|
|
|
|
|
ws[f"C{row}"] = y
|
|
|
|
|
|
ws[f"D{row}"] = lv
|
|
|
|
|
|
ws[f"E{row}"] = L_bar_val
|
|
|
|
|
|
|
|
|
|
|
|
if gamma_val is not None and 0.5 < gamma_val < 5.0:
|
|
|
|
|
|
ws[f"F{row}"] = gamma_val
|
|
|
|
|
|
ws[f"F{row}"].number_format = "0.000"
|
|
|
|
|
|
else:
|
|
|
|
|
|
ws[f"F{row}"] = "N/A"
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"A{row}"].number_format = "0"
|
|
|
|
|
|
ws[f"B{row}"].number_format = "0.0000"
|
|
|
|
|
|
ws[f"C{row}"].number_format = "0.0000"
|
|
|
|
|
|
ws[f"D{row}"].number_format = "0.00"
|
|
|
|
|
|
ws[f"E{row}"].number_format = "0.0000"
|
|
|
|
|
|
|
|
|
|
|
|
for col in ["A", "B", "C", "D", "E", "F"]:
|
|
|
|
|
|
ws[f"{col}{row}"].font = data_font
|
|
|
|
|
|
ws[f"{col}{row}"].alignment = data_alignment
|
|
|
|
|
|
ws[f"{col}{row}"].border = thin_border
|
|
|
|
|
|
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
self.log_gui.log(" ✓ 添加 Gamma 数据")
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 3. 色度一致性数据 ==========
|
|
|
|
|
|
if "cct" in selected_items:
|
|
|
|
|
|
gray_data = self.results.get_intermediate_data("shared", "gray")
|
|
|
|
|
|
if not gray_data:
|
|
|
|
|
|
gray_data = self.results.get_intermediate_data(
|
|
|
|
|
|
"cct", "gray"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if gray_data and len(gray_data) > 1:
|
|
|
|
|
|
gray_data_no_black = gray_data[:-1]
|
|
|
|
|
|
|
|
|
|
|
|
# 分区标题
|
|
|
|
|
|
ws.merge_cells(f"A{row}:G{row}")
|
|
|
|
|
|
ws[f"A{row}"] = "🌈 色度一致性数据"
|
|
|
|
|
|
ws[f"A{row}"].font = section_font
|
|
|
|
|
|
ws[f"A{row}"].fill = section_fill
|
|
|
|
|
|
ws[f"A{row}"].alignment = section_alignment
|
|
|
|
|
|
ws.row_dimensions[row].height = 25
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
# 色度波动信息
|
|
|
|
|
|
x_coords = [d[0] for d in gray_data_no_black]
|
|
|
|
|
|
y_coords = [d[1] for d in gray_data_no_black]
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"A{row}"] = "x 坐标范围"
|
|
|
|
|
|
ws[f"B{row}"] = f"{min(x_coords):.4f} ~ {max(x_coords):.4f}"
|
|
|
|
|
|
ws[f"C{row}"] = "y 坐标范围"
|
|
|
|
|
|
ws[f"D{row}"] = f"{min(y_coords):.4f} ~ {max(y_coords):.4f}"
|
|
|
|
|
|
|
|
|
|
|
|
for col in ["A", "B", "C", "D"]:
|
|
|
|
|
|
ws[f"{col}{row}"].font = (
|
|
|
|
|
|
label_font if col in ["A", "C"] else data_font
|
|
|
|
|
|
)
|
|
|
|
|
|
ws[f"{col}{row}"].border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
# 数据表格
|
|
|
|
|
|
headers = [
|
|
|
|
|
|
"灰阶 (%)",
|
|
|
|
|
|
"x 坐标",
|
|
|
|
|
|
"y 坐标",
|
|
|
|
|
|
"亮度 (cd/m²)",
|
|
|
|
|
|
"",
|
|
|
|
|
|
"",
|
|
|
|
|
|
"",
|
|
|
|
|
|
]
|
|
|
|
|
|
for col_idx, header in enumerate(headers, start=1):
|
|
|
|
|
|
cell = ws.cell(row=row, column=col_idx)
|
|
|
|
|
|
cell.value = header
|
|
|
|
|
|
cell.font = header_font
|
|
|
|
|
|
cell.fill = header_fill
|
|
|
|
|
|
cell.alignment = header_alignment
|
|
|
|
|
|
cell.border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
total_points = len(gray_data)
|
|
|
|
|
|
for i in range(len(gray_data_no_black) - 1, -1, -1):
|
|
|
|
|
|
x, y, lv = (
|
|
|
|
|
|
gray_data_no_black[i][0],
|
|
|
|
|
|
gray_data_no_black[i][1],
|
|
|
|
|
|
gray_data_no_black[i][2],
|
|
|
|
|
|
)
|
|
|
|
|
|
gray_level = (
|
|
|
|
|
|
100 - int(i * 100 / (total_points - 1))
|
|
|
|
|
|
if total_points > 1
|
|
|
|
|
|
else 0
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"A{row}"] = gray_level
|
|
|
|
|
|
ws[f"B{row}"] = x
|
|
|
|
|
|
ws[f"C{row}"] = y
|
|
|
|
|
|
ws[f"D{row}"] = lv
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"A{row}"].number_format = "0"
|
|
|
|
|
|
ws[f"B{row}"].number_format = "0.0000"
|
|
|
|
|
|
ws[f"C{row}"].number_format = "0.0000"
|
|
|
|
|
|
ws[f"D{row}"].number_format = "0.00"
|
|
|
|
|
|
|
|
|
|
|
|
for col in ["A", "B", "C", "D"]:
|
|
|
|
|
|
ws[f"{col}{row}"].font = data_font
|
|
|
|
|
|
ws[f"{col}{row}"].alignment = data_alignment
|
|
|
|
|
|
ws[f"{col}{row}"].border = thin_border
|
|
|
|
|
|
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
self.log_gui.log(" ✓ 添加色度一致性数据")
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 4. 对比度数据 ==========
|
|
|
|
|
|
if "contrast" in selected_items:
|
|
|
|
|
|
contrast_final_result = None
|
|
|
|
|
|
if "contrast" in self.results.test_items:
|
|
|
|
|
|
contrast_final_result = self.results.test_items[
|
|
|
|
|
|
"contrast"
|
|
|
|
|
|
].final_result
|
|
|
|
|
|
|
|
|
|
|
|
if contrast_final_result:
|
|
|
|
|
|
# 分区标题
|
|
|
|
|
|
ws.merge_cells(f"A{row}:G{row}")
|
|
|
|
|
|
ws[f"A{row}"] = "⚫⚪ 对比度测试数据"
|
|
|
|
|
|
ws[f"A{row}"].font = section_font
|
|
|
|
|
|
ws[f"A{row}"].fill = section_fill
|
|
|
|
|
|
ws[f"A{row}"].alignment = section_alignment
|
|
|
|
|
|
ws.row_dimensions[row].height = 25
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
max_lv = contrast_final_result.get("max_luminance", 0)
|
|
|
|
|
|
min_lv = contrast_final_result.get("min_luminance", 0)
|
|
|
|
|
|
contrast_ratio = contrast_final_result.get(
|
|
|
|
|
|
"contrast_ratio", 0
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
info_items = [
|
|
|
|
|
|
("最大亮度(白场)", f"{max_lv:.2f} cd/m²"),
|
|
|
|
|
|
("最小亮度(黑场)", f"{min_lv:.4f} cd/m²"),
|
|
|
|
|
|
("对比度", f"{contrast_ratio:.0f}:1"),
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
for label, value in info_items:
|
|
|
|
|
|
ws[f"A{row}"] = label
|
|
|
|
|
|
ws[f"B{row}"] = value
|
|
|
|
|
|
ws[f"A{row}"].font = label_font
|
|
|
|
|
|
ws[f"B{row}"].font = data_font
|
|
|
|
|
|
ws[f"A{row}"].border = thin_border
|
|
|
|
|
|
ws[f"B{row}"].border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
self.log_gui.log(" ✓ 添加对比度数据")
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 调整列宽 ==========
|
|
|
|
|
|
ws.column_dimensions["A"].width = 18
|
|
|
|
|
|
ws.column_dimensions["B"].width = 18
|
|
|
|
|
|
ws.column_dimensions["C"].width = 18
|
|
|
|
|
|
ws.column_dimensions["D"].width = 18
|
|
|
|
|
|
ws.column_dimensions["E"].width = 18
|
|
|
|
|
|
ws.column_dimensions["F"].width = 15
|
|
|
|
|
|
ws.column_dimensions["G"].width = 15
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 保存 Excel ==========
|
|
|
|
|
|
excel_path = os.path.join(result_dir, "测试数据.xlsx")
|
|
|
|
|
|
wb.save(excel_path)
|
|
|
|
|
|
|
|
|
|
|
|
self.log_gui.log(f"✓ 已保存: 测试数据.xlsx")
|
|
|
|
|
|
self.log_gui.log("=" * 60)
|
|
|
|
|
|
|
|
|
|
|
|
except ImportError:
|
|
|
|
|
|
self.log_gui.log("⚠️ 未安装 openpyxl 库,跳过 Excel 导出")
|
|
|
|
|
|
self.log_gui.log(" 安装方法: pip install openpyxl")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
self.log_gui.log(f"⚠️ Excel 导出失败: {str(e)}")
|
|
|
|
|
|
import traceback
|
|
|
|
|
|
|
|
|
|
|
|
self.log_gui.log(traceback.format_exc())
|
|
|
|
|
|
|
|
|
|
|
|
# ========== ✅ SDR Movie 测试 Excel 导出 ==========
|
|
|
|
|
|
elif (
|
|
|
|
|
|
current_test_type == "sdr_movie"
|
|
|
|
|
|
and hasattr(self, "results")
|
|
|
|
|
|
and self.results
|
|
|
|
|
|
):
|
|
|
|
|
|
try:
|
|
|
|
|
|
import openpyxl
|
|
|
|
|
|
from openpyxl.styles import (
|
|
|
|
|
|
Font,
|
|
|
|
|
|
Alignment,
|
|
|
|
|
|
PatternFill,
|
|
|
|
|
|
Border,
|
|
|
|
|
|
Side,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
self.log_gui.log("=" * 60)
|
|
|
|
|
|
self.log_gui.log("开始生成 SDR Movie Excel 数据报告...")
|
|
|
|
|
|
|
|
|
|
|
|
wb = openpyxl.Workbook()
|
|
|
|
|
|
ws = wb.active
|
|
|
|
|
|
ws.title = "测试数据"
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 样式定义 ==========
|
|
|
|
|
|
title_font = Font(
|
|
|
|
|
|
name="微软雅黑", size=16, bold=True, color="FFFFFF"
|
|
|
|
|
|
)
|
|
|
|
|
|
title_fill = PatternFill(
|
|
|
|
|
|
start_color="4472C4", end_color="4472C4", fill_type="solid"
|
|
|
|
|
|
)
|
|
|
|
|
|
title_alignment = Alignment(horizontal="center", vertical="center")
|
|
|
|
|
|
|
|
|
|
|
|
section_font = Font(
|
|
|
|
|
|
name="微软雅黑", size=13, bold=True, color="FFFFFF"
|
|
|
|
|
|
)
|
|
|
|
|
|
section_fill = PatternFill(
|
|
|
|
|
|
start_color="5B9BD5", end_color="5B9BD5", fill_type="solid"
|
|
|
|
|
|
)
|
|
|
|
|
|
section_alignment = Alignment(
|
|
|
|
|
|
horizontal="center", vertical="center"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
header_font = Font(
|
|
|
|
|
|
name="微软雅黑", size=10, bold=True, color="FFFFFF"
|
|
|
|
|
|
)
|
|
|
|
|
|
header_fill = PatternFill(
|
|
|
|
|
|
start_color="70AD47", end_color="70AD47", fill_type="solid"
|
|
|
|
|
|
)
|
|
|
|
|
|
header_alignment = Alignment(
|
|
|
|
|
|
horizontal="center", vertical="center", wrap_text=True
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
data_font = Font(name="微软雅黑", size=10)
|
|
|
|
|
|
data_alignment = Alignment(horizontal="center", vertical="center")
|
|
|
|
|
|
label_font = Font(name="微软雅黑", size=10, bold=True)
|
|
|
|
|
|
|
|
|
|
|
|
thin_border = Border(
|
|
|
|
|
|
left=Side(style="thin"),
|
|
|
|
|
|
right=Side(style="thin"),
|
|
|
|
|
|
top=Side(style="thin"),
|
|
|
|
|
|
bottom=Side(style="thin"),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 总标题 ==========
|
|
|
|
|
|
ws.merge_cells("A1:G1")
|
|
|
|
|
|
ws["A1"] = "SDR Movie 性能测试数据报告"
|
|
|
|
|
|
ws["A1"].font = title_font
|
|
|
|
|
|
ws["A1"].fill = title_fill
|
|
|
|
|
|
ws["A1"].alignment = title_alignment
|
|
|
|
|
|
ws.row_dimensions[1].height = 35
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 测试基本信息 ==========
|
|
|
|
|
|
row = 3
|
|
|
|
|
|
ws.merge_cells(f"A{row}:B{row}")
|
|
|
|
|
|
ws[f"A{row}"] = "📋 测试基本信息"
|
|
|
|
|
|
ws[f"A{row}"].font = section_font
|
|
|
|
|
|
ws[f"A{row}"].fill = section_fill
|
|
|
|
|
|
ws[f"A{row}"].alignment = section_alignment
|
|
|
|
|
|
ws.row_dimensions[row].height = 25
|
|
|
|
|
|
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
info_items = [
|
|
|
|
|
|
(
|
|
|
|
|
|
"测试时间",
|
|
|
|
|
|
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
|
|
|
|
),
|
|
|
|
|
|
("测试类型", "SDR Movie"),
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
for label, value in info_items:
|
|
|
|
|
|
ws[f"A{row}"] = label
|
|
|
|
|
|
ws[f"B{row}"] = value
|
|
|
|
|
|
ws[f"A{row}"].font = label_font
|
|
|
|
|
|
ws[f"B{row}"].font = data_font
|
|
|
|
|
|
ws[f"A{row}"].border = thin_border
|
|
|
|
|
|
ws[f"B{row}"].border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
row += 1 # 空行
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 1. 色域数据 ==========
|
|
|
|
|
|
if "gamut" in selected_items:
|
|
|
|
|
|
rgb_data = self.results.get_intermediate_data("gamut", "rgb")
|
|
|
|
|
|
gamut_final_result = None
|
|
|
|
|
|
if "gamut" in self.results.test_items:
|
|
|
|
|
|
gamut_final_result = self.results.test_items[
|
|
|
|
|
|
"gamut"
|
|
|
|
|
|
].final_result
|
|
|
|
|
|
|
|
|
|
|
|
if rgb_data and len(rgb_data) >= 3:
|
|
|
|
|
|
ws.merge_cells(f"A{row}:G{row}")
|
|
|
|
|
|
ws[f"A{row}"] = "🎨 色域测试数据"
|
|
|
|
|
|
ws[f"A{row}"].font = section_font
|
|
|
|
|
|
ws[f"A{row}"].fill = section_fill
|
|
|
|
|
|
ws[f"A{row}"].alignment = section_alignment
|
|
|
|
|
|
ws.row_dimensions[row].height = 25
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
if gamut_final_result:
|
|
|
|
|
|
xy_coverage = gamut_final_result.get("coverage", 0)
|
|
|
|
|
|
uv_coverage = (
|
|
|
|
|
|
gamut_final_result.get("uv_coverage", 0)
|
|
|
|
|
|
or gamut_final_result.get("uv_space_coverage", 0)
|
|
|
|
|
|
or 0
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"A{row}"] = "参考标准"
|
|
|
|
|
|
ws[f"B{row}"] = gamut_final_result.get(
|
|
|
|
|
|
"reference", "DCI-P3"
|
|
|
|
|
|
)
|
|
|
|
|
|
ws[f"A{row}"].font = label_font
|
|
|
|
|
|
ws[f"B{row}"].font = data_font
|
|
|
|
|
|
ws[f"A{row}"].border = thin_border
|
|
|
|
|
|
ws[f"B{row}"].border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"A{row}"] = "XY 色域覆盖率"
|
|
|
|
|
|
ws[f"B{row}"] = f"{xy_coverage:.2f}%"
|
|
|
|
|
|
ws[f"C{row}"] = "UV 色域覆盖率"
|
|
|
|
|
|
ws[f"D{row}"] = f"{uv_coverage:.2f}%"
|
|
|
|
|
|
|
|
|
|
|
|
for col in ["A", "B", "C", "D"]:
|
|
|
|
|
|
ws[f"{col}{row}"].font = (
|
|
|
|
|
|
label_font if col in ["A", "C"] else data_font
|
|
|
|
|
|
)
|
|
|
|
|
|
ws[f"{col}{row}"].border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
# RGB 数据表格
|
|
|
|
|
|
headers = [
|
|
|
|
|
|
"点位",
|
|
|
|
|
|
"x 坐标",
|
|
|
|
|
|
"y 坐标",
|
|
|
|
|
|
"亮度 (cd/m²)",
|
|
|
|
|
|
"",
|
|
|
|
|
|
"",
|
|
|
|
|
|
"",
|
|
|
|
|
|
]
|
|
|
|
|
|
for col_idx, header in enumerate(headers, start=1):
|
|
|
|
|
|
cell = ws.cell(row=row, column=col_idx)
|
|
|
|
|
|
cell.value = header
|
|
|
|
|
|
cell.font = header_font
|
|
|
|
|
|
cell.fill = header_fill
|
|
|
|
|
|
cell.alignment = header_alignment
|
|
|
|
|
|
cell.border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
rgb_labels = ["Red", "Green", "Blue"]
|
|
|
|
|
|
for i, result in enumerate(rgb_data[:3]):
|
|
|
|
|
|
x, y, lv = result[0], result[1], result[2]
|
|
|
|
|
|
ws[f"A{row}"] = rgb_labels[i]
|
|
|
|
|
|
ws[f"B{row}"] = x
|
|
|
|
|
|
ws[f"C{row}"] = y
|
|
|
|
|
|
ws[f"D{row}"] = lv
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"B{row}"].number_format = "0.0000"
|
|
|
|
|
|
ws[f"C{row}"].number_format = "0.0000"
|
|
|
|
|
|
ws[f"D{row}"].number_format = "0.00"
|
|
|
|
|
|
|
|
|
|
|
|
for col in ["A", "B", "C", "D"]:
|
|
|
|
|
|
ws[f"{col}{row}"].font = data_font
|
|
|
|
|
|
ws[f"{col}{row}"].alignment = data_alignment
|
|
|
|
|
|
ws[f"{col}{row}"].border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
self.log_gui.log(" ✓ 添加色域数据")
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 2. Gamma 数据 ==========
|
|
|
|
|
|
if "gamma" in selected_items:
|
|
|
|
|
|
gray_data = self.results.get_intermediate_data("shared", "gray")
|
|
|
|
|
|
if not gray_data:
|
|
|
|
|
|
gray_data = self.results.get_intermediate_data(
|
|
|
|
|
|
"gamma", "gray"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
gamma_final_result = None
|
|
|
|
|
|
if "gamma" in self.results.test_items:
|
|
|
|
|
|
gamma_final_result = self.results.test_items[
|
|
|
|
|
|
"gamma"
|
|
|
|
|
|
].final_result
|
|
|
|
|
|
|
|
|
|
|
|
if gray_data and gamma_final_result:
|
|
|
|
|
|
gamma_list = gamma_final_result.get("gamma", [])
|
|
|
|
|
|
L_bar_list = gamma_final_result.get("L_bar", [])
|
|
|
|
|
|
|
|
|
|
|
|
ws.merge_cells(f"A{row}:G{row}")
|
|
|
|
|
|
ws[f"A{row}"] = "📊 Gamma 曲线数据"
|
|
|
|
|
|
ws[f"A{row}"].font = section_font
|
|
|
|
|
|
ws[f"A{row}"].fill = section_fill
|
|
|
|
|
|
ws[f"A{row}"].alignment = section_alignment
|
|
|
|
|
|
ws.row_dimensions[row].height = 25
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
# Gamma 统计
|
|
|
|
|
|
valid_gamma = [
|
|
|
|
|
|
item[3]
|
|
|
|
|
|
for item in gamma_list
|
|
|
|
|
|
if isinstance(item, (list, tuple))
|
|
|
|
|
|
and len(item) >= 4
|
|
|
|
|
|
and 0.5 < item[3] < 5.0
|
|
|
|
|
|
]
|
|
|
|
|
|
if valid_gamma:
|
|
|
|
|
|
avg_gamma = sum(valid_gamma) / len(valid_gamma)
|
|
|
|
|
|
ws[f"A{row}"] = "平均 Gamma"
|
|
|
|
|
|
ws[f"B{row}"] = f"{avg_gamma:.3f}"
|
|
|
|
|
|
ws[f"C{row}"] = "最大 Gamma"
|
|
|
|
|
|
ws[f"D{row}"] = f"{max(valid_gamma):.3f}"
|
|
|
|
|
|
ws[f"E{row}"] = "最小 Gamma"
|
|
|
|
|
|
ws[f"F{row}"] = f"{min(valid_gamma):.3f}"
|
|
|
|
|
|
|
|
|
|
|
|
for col in ["A", "B", "C", "D", "E", "F"]:
|
|
|
|
|
|
ws[f"{col}{row}"].font = (
|
|
|
|
|
|
label_font
|
|
|
|
|
|
if col in ["A", "C", "E"]
|
|
|
|
|
|
else data_font
|
|
|
|
|
|
)
|
|
|
|
|
|
ws[f"{col}{row}"].border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
# Gamma 数据表格
|
|
|
|
|
|
headers = [
|
|
|
|
|
|
"灰阶 (%)",
|
|
|
|
|
|
"x 坐标",
|
|
|
|
|
|
"y 坐标",
|
|
|
|
|
|
"实测亮度\n(cd/m²)",
|
|
|
|
|
|
"归一化亮度\n(L_bar)",
|
|
|
|
|
|
"Gamma 值",
|
|
|
|
|
|
"",
|
|
|
|
|
|
]
|
|
|
|
|
|
for col_idx, header in enumerate(headers, start=1):
|
|
|
|
|
|
cell = ws.cell(row=row, column=col_idx)
|
|
|
|
|
|
cell.value = header
|
|
|
|
|
|
cell.font = header_font
|
|
|
|
|
|
cell.fill = header_fill
|
|
|
|
|
|
cell.alignment = header_alignment
|
|
|
|
|
|
cell.border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
total_points = len(gray_data)
|
|
|
|
|
|
for i in range(total_points - 1, -1, -1):
|
|
|
|
|
|
gray_level = (
|
|
|
|
|
|
100 - int(i * 100 / (total_points - 1))
|
|
|
|
|
|
if total_points > 1
|
|
|
|
|
|
else 0
|
|
|
|
|
|
)
|
|
|
|
|
|
x, y, lv = (
|
|
|
|
|
|
gray_data[i][0],
|
|
|
|
|
|
gray_data[i][1],
|
|
|
|
|
|
gray_data[i][2],
|
|
|
|
|
|
)
|
|
|
|
|
|
L_bar_val = L_bar_list[i] if i < len(L_bar_list) else 0
|
|
|
|
|
|
|
|
|
|
|
|
gamma_val = None
|
|
|
|
|
|
if (
|
|
|
|
|
|
i < len(gamma_list)
|
|
|
|
|
|
and isinstance(gamma_list[i], (list, tuple))
|
|
|
|
|
|
and len(gamma_list[i]) >= 4
|
|
|
|
|
|
):
|
|
|
|
|
|
gamma_val = gamma_list[i][3]
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"A{row}"] = gray_level
|
|
|
|
|
|
ws[f"B{row}"] = x
|
|
|
|
|
|
ws[f"C{row}"] = y
|
|
|
|
|
|
ws[f"D{row}"] = lv
|
|
|
|
|
|
ws[f"E{row}"] = L_bar_val
|
|
|
|
|
|
|
|
|
|
|
|
if gamma_val is not None and 0.5 < gamma_val < 5.0:
|
|
|
|
|
|
ws[f"F{row}"] = gamma_val
|
|
|
|
|
|
ws[f"F{row}"].number_format = "0.000"
|
|
|
|
|
|
else:
|
|
|
|
|
|
ws[f"F{row}"] = "N/A"
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"A{row}"].number_format = "0"
|
|
|
|
|
|
ws[f"B{row}"].number_format = "0.0000"
|
|
|
|
|
|
ws[f"C{row}"].number_format = "0.0000"
|
|
|
|
|
|
ws[f"D{row}"].number_format = "0.00"
|
|
|
|
|
|
ws[f"E{row}"].number_format = "0.0000"
|
|
|
|
|
|
|
|
|
|
|
|
for col in ["A", "B", "C", "D", "E", "F"]:
|
|
|
|
|
|
ws[f"{col}{row}"].font = data_font
|
|
|
|
|
|
ws[f"{col}{row}"].alignment = data_alignment
|
|
|
|
|
|
ws[f"{col}{row}"].border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
self.log_gui.log(" ✓ 添加 Gamma 数据")
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 3. 色度一致性数据 ==========
|
|
|
|
|
|
if "cct" in selected_items:
|
|
|
|
|
|
gray_data = self.results.get_intermediate_data("shared", "gray")
|
|
|
|
|
|
if not gray_data:
|
|
|
|
|
|
gray_data = self.results.get_intermediate_data(
|
|
|
|
|
|
"cct", "gray"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if gray_data and len(gray_data) > 1:
|
|
|
|
|
|
gray_data_no_black = gray_data[:-1]
|
|
|
|
|
|
|
|
|
|
|
|
ws.merge_cells(f"A{row}:G{row}")
|
|
|
|
|
|
ws[f"A{row}"] = "🌈 色度一致性数据"
|
|
|
|
|
|
ws[f"A{row}"].font = section_font
|
|
|
|
|
|
ws[f"A{row}"].fill = section_fill
|
|
|
|
|
|
ws[f"A{row}"].alignment = section_alignment
|
|
|
|
|
|
ws.row_dimensions[row].height = 25
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
x_coords = [d[0] for d in gray_data_no_black]
|
|
|
|
|
|
y_coords = [d[1] for d in gray_data_no_black]
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"A{row}"] = "x 坐标范围"
|
|
|
|
|
|
ws[f"B{row}"] = f"{min(x_coords):.4f} ~ {max(x_coords):.4f}"
|
|
|
|
|
|
ws[f"C{row}"] = "y 坐标范围"
|
|
|
|
|
|
ws[f"D{row}"] = f"{min(y_coords):.4f} ~ {max(y_coords):.4f}"
|
|
|
|
|
|
|
|
|
|
|
|
for col in ["A", "B", "C", "D"]:
|
|
|
|
|
|
ws[f"{col}{row}"].font = (
|
|
|
|
|
|
label_font if col in ["A", "C"] else data_font
|
|
|
|
|
|
)
|
|
|
|
|
|
ws[f"{col}{row}"].border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
headers = [
|
|
|
|
|
|
"灰阶 (%)",
|
|
|
|
|
|
"x 坐标",
|
|
|
|
|
|
"y 坐标",
|
|
|
|
|
|
"亮度 (cd/m²)",
|
|
|
|
|
|
"",
|
|
|
|
|
|
"",
|
|
|
|
|
|
"",
|
|
|
|
|
|
]
|
|
|
|
|
|
for col_idx, header in enumerate(headers, start=1):
|
|
|
|
|
|
cell = ws.cell(row=row, column=col_idx)
|
|
|
|
|
|
cell.value = header
|
|
|
|
|
|
cell.font = header_font
|
|
|
|
|
|
cell.fill = header_fill
|
|
|
|
|
|
cell.alignment = header_alignment
|
|
|
|
|
|
cell.border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
total_points = len(gray_data)
|
|
|
|
|
|
for i in range(len(gray_data_no_black) - 1, -1, -1):
|
|
|
|
|
|
x, y, lv = (
|
|
|
|
|
|
gray_data_no_black[i][0],
|
|
|
|
|
|
gray_data_no_black[i][1],
|
|
|
|
|
|
gray_data_no_black[i][2],
|
|
|
|
|
|
)
|
|
|
|
|
|
gray_level = (
|
|
|
|
|
|
100 - int(i * 100 / (total_points - 1))
|
|
|
|
|
|
if total_points > 1
|
|
|
|
|
|
else 0
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"A{row}"] = gray_level
|
|
|
|
|
|
ws[f"B{row}"] = x
|
|
|
|
|
|
ws[f"C{row}"] = y
|
|
|
|
|
|
ws[f"D{row}"] = lv
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"A{row}"].number_format = "0"
|
|
|
|
|
|
ws[f"B{row}"].number_format = "0.0000"
|
|
|
|
|
|
ws[f"C{row}"].number_format = "0.0000"
|
|
|
|
|
|
ws[f"D{row}"].number_format = "0.00"
|
|
|
|
|
|
|
|
|
|
|
|
for col in ["A", "B", "C", "D"]:
|
|
|
|
|
|
ws[f"{col}{row}"].font = data_font
|
|
|
|
|
|
ws[f"{col}{row}"].alignment = data_alignment
|
|
|
|
|
|
ws[f"{col}{row}"].border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
self.log_gui.log(" ✓ 添加色度一致性数据")
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 4. 对比度数据 ==========
|
|
|
|
|
|
if "contrast" in selected_items:
|
|
|
|
|
|
contrast_final_result = None
|
|
|
|
|
|
if "contrast" in self.results.test_items:
|
|
|
|
|
|
contrast_final_result = self.results.test_items[
|
|
|
|
|
|
"contrast"
|
|
|
|
|
|
].final_result
|
|
|
|
|
|
|
|
|
|
|
|
if contrast_final_result:
|
|
|
|
|
|
ws.merge_cells(f"A{row}:G{row}")
|
|
|
|
|
|
ws[f"A{row}"] = "⚫⚪ 对比度测试数据"
|
|
|
|
|
|
ws[f"A{row}"].font = section_font
|
|
|
|
|
|
ws[f"A{row}"].fill = section_fill
|
|
|
|
|
|
ws[f"A{row}"].alignment = section_alignment
|
|
|
|
|
|
ws.row_dimensions[row].height = 25
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
max_lv = contrast_final_result.get("max_luminance", 0)
|
|
|
|
|
|
min_lv = contrast_final_result.get("min_luminance", 0)
|
|
|
|
|
|
contrast_ratio = contrast_final_result.get(
|
|
|
|
|
|
"contrast_ratio", 0
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
info_items = [
|
|
|
|
|
|
("最大亮度(白场)", f"{max_lv:.2f} cd/m²"),
|
|
|
|
|
|
("最小亮度(黑场)", f"{min_lv:.4f} cd/m²"),
|
|
|
|
|
|
("对比度", f"{contrast_ratio:.0f}:1"),
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
for label, value in info_items:
|
|
|
|
|
|
ws[f"A{row}"] = label
|
|
|
|
|
|
ws[f"B{row}"] = value
|
|
|
|
|
|
ws[f"A{row}"].font = label_font
|
|
|
|
|
|
ws[f"B{row}"].font = data_font
|
|
|
|
|
|
ws[f"A{row}"].border = thin_border
|
|
|
|
|
|
ws[f"B{row}"].border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
self.log_gui.log(" ✓ 添加对比度数据")
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 5. 色准数据(SDR 特有)==========
|
|
|
|
|
|
if "accuracy" in selected_items:
|
|
|
|
|
|
accuracy_final_result = None
|
|
|
|
|
|
if "accuracy" in self.results.test_items:
|
|
|
|
|
|
accuracy_final_result = self.results.test_items[
|
|
|
|
|
|
"accuracy"
|
|
|
|
|
|
].final_result
|
|
|
|
|
|
|
|
|
|
|
|
if accuracy_final_result:
|
|
|
|
|
|
ws.merge_cells(f"A{row}:G{row}")
|
|
|
|
|
|
ws[f"A{row}"] = "🎯 色准测试数据"
|
|
|
|
|
|
ws[f"A{row}"].font = section_font
|
|
|
|
|
|
ws[f"A{row}"].fill = section_fill
|
|
|
|
|
|
ws[f"A{row}"].alignment = section_alignment
|
|
|
|
|
|
ws.row_dimensions[row].height = 25
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
# 色准统计信息
|
|
|
|
|
|
avg_delta_e = accuracy_final_result.get("avg_delta_e", 0)
|
|
|
|
|
|
max_delta_e = accuracy_final_result.get("max_delta_e", 0)
|
|
|
|
|
|
min_delta_e = accuracy_final_result.get("min_delta_e", 0)
|
|
|
|
|
|
excellent_count = accuracy_final_result.get(
|
|
|
|
|
|
"excellent_count", 0
|
|
|
|
|
|
)
|
|
|
|
|
|
good_count = accuracy_final_result.get("good_count", 0)
|
|
|
|
|
|
poor_count = accuracy_final_result.get("poor_count", 0)
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"A{row}"] = "平均 ΔE"
|
|
|
|
|
|
ws[f"B{row}"] = f"{avg_delta_e:.2f}"
|
|
|
|
|
|
ws[f"C{row}"] = "最大 ΔE"
|
|
|
|
|
|
ws[f"D{row}"] = f"{max_delta_e:.2f}"
|
|
|
|
|
|
ws[f"E{row}"] = "最小 ΔE"
|
|
|
|
|
|
ws[f"F{row}"] = f"{min_delta_e:.2f}"
|
|
|
|
|
|
|
|
|
|
|
|
for col in ["A", "B", "C", "D", "E", "F"]:
|
|
|
|
|
|
ws[f"{col}{row}"].font = (
|
|
|
|
|
|
label_font if col in ["A", "C", "E"] else data_font
|
|
|
|
|
|
)
|
|
|
|
|
|
ws[f"{col}{row}"].border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
# 第二行统计
|
|
|
|
|
|
ws[f"A{row}"] = "优秀 (ΔE<3)"
|
|
|
|
|
|
ws[f"B{row}"] = f"{excellent_count} 个"
|
|
|
|
|
|
ws[f"C{row}"] = "良好 (3≤ΔE<5)"
|
|
|
|
|
|
ws[f"D{row}"] = f"{good_count} 个"
|
|
|
|
|
|
ws[f"E{row}"] = "偏差 (ΔE≥5)"
|
|
|
|
|
|
ws[f"F{row}"] = f"{poor_count} 个"
|
|
|
|
|
|
|
|
|
|
|
|
for col in ["A", "B", "C", "D", "E", "F"]:
|
|
|
|
|
|
ws[f"{col}{row}"].font = (
|
|
|
|
|
|
label_font if col in ["A", "C", "E"] else data_font
|
|
|
|
|
|
)
|
|
|
|
|
|
ws[f"{col}{row}"].border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 色准详细数据表格(带 xy 坐标和亮度)==========
|
|
|
|
|
|
color_patches = accuracy_final_result.get(
|
|
|
|
|
|
"color_patches", []
|
|
|
|
|
|
)
|
|
|
|
|
|
delta_e_values = accuracy_final_result.get(
|
|
|
|
|
|
"delta_e_values", []
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# ✅ 获取原始测量数据(包含 xy 和亮度)
|
|
|
|
|
|
color_measurements = accuracy_final_result.get(
|
|
|
|
|
|
"color_measurements", []
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if color_patches and delta_e_values:
|
|
|
|
|
|
# 表头
|
|
|
|
|
|
headers = [
|
|
|
|
|
|
"序号",
|
|
|
|
|
|
"颜色名称",
|
|
|
|
|
|
"x 坐标",
|
|
|
|
|
|
"y 坐标",
|
|
|
|
|
|
"亮度 (cd/m²)",
|
|
|
|
|
|
"ΔE 2000",
|
|
|
|
|
|
"等级",
|
|
|
|
|
|
]
|
|
|
|
|
|
for col_idx, header in enumerate(headers, start=1):
|
|
|
|
|
|
cell = ws.cell(row=row, column=col_idx)
|
|
|
|
|
|
cell.value = header
|
|
|
|
|
|
cell.font = header_font
|
|
|
|
|
|
cell.fill = header_fill
|
|
|
|
|
|
cell.alignment = header_alignment
|
|
|
|
|
|
cell.border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
# 数据行
|
|
|
|
|
|
for idx, (color_name, delta_e) in enumerate(
|
|
|
|
|
|
zip(color_patches, delta_e_values), start=1
|
|
|
|
|
|
):
|
|
|
|
|
|
# 判断等级
|
|
|
|
|
|
if delta_e < 3:
|
|
|
|
|
|
grade = "优秀"
|
|
|
|
|
|
elif delta_e < 5:
|
|
|
|
|
|
grade = "良好"
|
|
|
|
|
|
else:
|
|
|
|
|
|
grade = "偏差"
|
|
|
|
|
|
|
|
|
|
|
|
# ✅ 获取测量数据(x, y, 亮度)
|
|
|
|
|
|
x_val = "N/A"
|
|
|
|
|
|
y_val = "N/A"
|
|
|
|
|
|
lv_val = "N/A"
|
|
|
|
|
|
|
|
|
|
|
|
if color_measurements and idx - 1 < len(
|
|
|
|
|
|
color_measurements
|
|
|
|
|
|
):
|
|
|
|
|
|
measurement = color_measurements[idx - 1]
|
|
|
|
|
|
if len(measurement) >= 3:
|
|
|
|
|
|
x_val = measurement[0]
|
|
|
|
|
|
y_val = measurement[1]
|
|
|
|
|
|
lv_val = measurement[2]
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"A{row}"] = idx
|
|
|
|
|
|
ws[f"B{row}"] = color_name
|
|
|
|
|
|
ws[f"C{row}"] = x_val
|
|
|
|
|
|
ws[f"D{row}"] = y_val
|
|
|
|
|
|
ws[f"E{row}"] = lv_val
|
|
|
|
|
|
ws[f"F{row}"] = delta_e
|
|
|
|
|
|
ws[f"G{row}"] = grade
|
|
|
|
|
|
|
|
|
|
|
|
# 数字格式
|
|
|
|
|
|
ws[f"A{row}"].number_format = "0"
|
|
|
|
|
|
if isinstance(x_val, (int, float)):
|
|
|
|
|
|
ws[f"C{row}"].number_format = "0.0000"
|
|
|
|
|
|
if isinstance(y_val, (int, float)):
|
|
|
|
|
|
ws[f"D{row}"].number_format = "0.0000"
|
|
|
|
|
|
if isinstance(lv_val, (int, float)):
|
|
|
|
|
|
ws[f"E{row}"].number_format = "0.00"
|
|
|
|
|
|
ws[f"F{row}"].number_format = "0.00"
|
|
|
|
|
|
|
|
|
|
|
|
for col in ["A", "B", "C", "D", "E", "F", "G"]:
|
|
|
|
|
|
ws[f"{col}{row}"].font = data_font
|
|
|
|
|
|
ws[f"{col}{row}"].alignment = data_alignment
|
|
|
|
|
|
ws[f"{col}{row}"].border = thin_border
|
|
|
|
|
|
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
self.log_gui.log(" ✓ 添加色准数据(含 xy 坐标和亮度)")
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 调整列宽 ==========
|
|
|
|
|
|
for col in ["A", "B", "C", "D", "E", "F", "G"]:
|
|
|
|
|
|
ws.column_dimensions[col].width = 18
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 保存 Excel ==========
|
|
|
|
|
|
excel_path = os.path.join(result_dir, "测试数据.xlsx")
|
|
|
|
|
|
wb.save(excel_path)
|
|
|
|
|
|
|
|
|
|
|
|
self.log_gui.log(f"✓ 已保存: 测试数据.xlsx")
|
|
|
|
|
|
self.log_gui.log("=" * 60)
|
|
|
|
|
|
|
|
|
|
|
|
except ImportError:
|
|
|
|
|
|
self.log_gui.log("⚠️ 未安装 openpyxl 库,跳过 Excel 导出")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
self.log_gui.log(f"⚠️ Excel 导出失败: {str(e)}")
|
|
|
|
|
|
import traceback
|
|
|
|
|
|
|
|
|
|
|
|
self.log_gui.log(traceback.format_exc())
|
|
|
|
|
|
|
|
|
|
|
|
# ========== ✅ HDR Movie 测试 Excel 导出 ==========
|
|
|
|
|
|
elif (
|
|
|
|
|
|
current_test_type == "hdr_movie"
|
|
|
|
|
|
and hasattr(self, "results")
|
|
|
|
|
|
and self.results
|
|
|
|
|
|
):
|
|
|
|
|
|
try:
|
|
|
|
|
|
import openpyxl
|
|
|
|
|
|
from openpyxl.styles import (
|
|
|
|
|
|
Font,
|
|
|
|
|
|
Alignment,
|
|
|
|
|
|
PatternFill,
|
|
|
|
|
|
Border,
|
|
|
|
|
|
Side,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
self.log_gui.log("=" * 60)
|
|
|
|
|
|
self.log_gui.log("开始生成 HDR Movie Excel 数据报告...")
|
|
|
|
|
|
|
|
|
|
|
|
wb = openpyxl.Workbook()
|
|
|
|
|
|
ws = wb.active
|
|
|
|
|
|
ws.title = "测试数据"
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 样式定义 ==========
|
|
|
|
|
|
title_font = Font(
|
|
|
|
|
|
name="微软雅黑", size=16, bold=True, color="FFFFFF"
|
|
|
|
|
|
)
|
|
|
|
|
|
title_fill = PatternFill(
|
|
|
|
|
|
start_color="4472C4", end_color="4472C4", fill_type="solid"
|
|
|
|
|
|
)
|
|
|
|
|
|
title_alignment = Alignment(horizontal="center", vertical="center")
|
|
|
|
|
|
|
|
|
|
|
|
section_font = Font(
|
|
|
|
|
|
name="微软雅黑", size=13, bold=True, color="FFFFFF"
|
|
|
|
|
|
)
|
|
|
|
|
|
section_fill = PatternFill(
|
|
|
|
|
|
start_color="5B9BD5", end_color="5B9BD5", fill_type="solid"
|
|
|
|
|
|
)
|
|
|
|
|
|
section_alignment = Alignment(
|
|
|
|
|
|
horizontal="center", vertical="center"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
header_font = Font(
|
|
|
|
|
|
name="微软雅黑", size=10, bold=True, color="FFFFFF"
|
|
|
|
|
|
)
|
|
|
|
|
|
header_fill = PatternFill(
|
|
|
|
|
|
start_color="70AD47", end_color="70AD47", fill_type="solid"
|
|
|
|
|
|
)
|
|
|
|
|
|
header_alignment = Alignment(
|
|
|
|
|
|
horizontal="center", vertical="center", wrap_text=True
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
data_font = Font(name="微软雅黑", size=10)
|
|
|
|
|
|
data_alignment = Alignment(horizontal="center", vertical="center")
|
|
|
|
|
|
label_font = Font(name="微软雅黑", size=10, bold=True)
|
|
|
|
|
|
|
|
|
|
|
|
thin_border = Border(
|
|
|
|
|
|
left=Side(style="thin"),
|
|
|
|
|
|
right=Side(style="thin"),
|
|
|
|
|
|
top=Side(style="thin"),
|
|
|
|
|
|
bottom=Side(style="thin"),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 总标题 ==========
|
|
|
|
|
|
ws.merge_cells("A1:G1")
|
|
|
|
|
|
ws["A1"] = "HDR Movie 性能测试数据报告"
|
|
|
|
|
|
ws["A1"].font = title_font
|
|
|
|
|
|
ws["A1"].fill = title_fill
|
|
|
|
|
|
ws["A1"].alignment = title_alignment
|
|
|
|
|
|
ws.row_dimensions[1].height = 35
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 测试基本信息 ==========
|
|
|
|
|
|
row = 3
|
|
|
|
|
|
ws.merge_cells(f"A{row}:B{row}")
|
|
|
|
|
|
ws[f"A{row}"] = "📋 测试基本信息"
|
|
|
|
|
|
ws[f"A{row}"].font = section_font
|
|
|
|
|
|
ws[f"A{row}"].fill = section_fill
|
|
|
|
|
|
ws[f"A{row}"].alignment = section_alignment
|
|
|
|
|
|
ws.row_dimensions[row].height = 25
|
|
|
|
|
|
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
info_items = [
|
|
|
|
|
|
(
|
|
|
|
|
|
"测试时间",
|
|
|
|
|
|
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
|
|
|
|
),
|
|
|
|
|
|
("测试类型", "HDR Movie"),
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
for label, value in info_items:
|
|
|
|
|
|
ws[f"A{row}"] = label
|
|
|
|
|
|
ws[f"B{row}"] = value
|
|
|
|
|
|
ws[f"A{row}"].font = label_font
|
|
|
|
|
|
ws[f"B{row}"].font = data_font
|
|
|
|
|
|
ws[f"A{row}"].border = thin_border
|
|
|
|
|
|
ws[f"B{row}"].border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 1. 色域数据 ==========
|
|
|
|
|
|
if "gamut" in selected_items:
|
|
|
|
|
|
rgb_data = self.results.get_intermediate_data("gamut", "rgb")
|
|
|
|
|
|
gamut_final_result = None
|
|
|
|
|
|
if "gamut" in self.results.test_items:
|
|
|
|
|
|
gamut_final_result = self.results.test_items[
|
|
|
|
|
|
"gamut"
|
|
|
|
|
|
].final_result
|
|
|
|
|
|
|
|
|
|
|
|
if rgb_data and len(rgb_data) >= 3:
|
|
|
|
|
|
ws.merge_cells(f"A{row}:G{row}")
|
|
|
|
|
|
ws[f"A{row}"] = "🎨 色域测试数据"
|
|
|
|
|
|
ws[f"A{row}"].font = section_font
|
|
|
|
|
|
ws[f"A{row}"].fill = section_fill
|
|
|
|
|
|
ws[f"A{row}"].alignment = section_alignment
|
|
|
|
|
|
ws.row_dimensions[row].height = 25
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
if gamut_final_result:
|
|
|
|
|
|
xy_coverage = gamut_final_result.get("coverage", 0)
|
|
|
|
|
|
uv_coverage = (
|
|
|
|
|
|
gamut_final_result.get("uv_coverage", 0)
|
|
|
|
|
|
or gamut_final_result.get("uv_space_coverage", 0)
|
|
|
|
|
|
or 0
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"A{row}"] = "参考标准"
|
|
|
|
|
|
ws[f"B{row}"] = gamut_final_result.get(
|
|
|
|
|
|
"reference", "DCI-P3"
|
|
|
|
|
|
)
|
|
|
|
|
|
ws[f"A{row}"].font = label_font
|
|
|
|
|
|
ws[f"B{row}"].font = data_font
|
|
|
|
|
|
ws[f"A{row}"].border = thin_border
|
|
|
|
|
|
ws[f"B{row}"].border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"A{row}"] = "XY 色域覆盖率"
|
|
|
|
|
|
ws[f"B{row}"] = f"{xy_coverage:.2f}%"
|
|
|
|
|
|
ws[f"C{row}"] = "UV 色域覆盖率"
|
|
|
|
|
|
ws[f"D{row}"] = f"{uv_coverage:.2f}%"
|
|
|
|
|
|
|
|
|
|
|
|
for col in ["A", "B", "C", "D"]:
|
|
|
|
|
|
ws[f"{col}{row}"].font = (
|
|
|
|
|
|
label_font if col in ["A", "C"] else data_font
|
|
|
|
|
|
)
|
|
|
|
|
|
ws[f"{col}{row}"].border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
# RGB 数据表格
|
|
|
|
|
|
headers = [
|
|
|
|
|
|
"点位",
|
|
|
|
|
|
"x 坐标",
|
|
|
|
|
|
"y 坐标",
|
|
|
|
|
|
"亮度 (cd/m²)",
|
|
|
|
|
|
"",
|
|
|
|
|
|
"",
|
|
|
|
|
|
"",
|
|
|
|
|
|
]
|
|
|
|
|
|
for col_idx, header in enumerate(headers, start=1):
|
|
|
|
|
|
cell = ws.cell(row=row, column=col_idx)
|
|
|
|
|
|
cell.value = header
|
|
|
|
|
|
cell.font = header_font
|
|
|
|
|
|
cell.fill = header_fill
|
|
|
|
|
|
cell.alignment = header_alignment
|
|
|
|
|
|
cell.border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
rgb_labels = ["Red", "Green", "Blue"]
|
|
|
|
|
|
for i, result in enumerate(rgb_data[:3]):
|
|
|
|
|
|
x, y, lv = result[0], result[1], result[2]
|
|
|
|
|
|
ws[f"A{row}"] = rgb_labels[i]
|
|
|
|
|
|
ws[f"B{row}"] = x
|
|
|
|
|
|
ws[f"C{row}"] = y
|
|
|
|
|
|
ws[f"D{row}"] = lv
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"B{row}"].number_format = "0.0000"
|
|
|
|
|
|
ws[f"C{row}"].number_format = "0.0000"
|
|
|
|
|
|
ws[f"D{row}"].number_format = "0.00"
|
|
|
|
|
|
|
|
|
|
|
|
for col in ["A", "B", "C", "D"]:
|
|
|
|
|
|
ws[f"{col}{row}"].font = data_font
|
|
|
|
|
|
ws[f"{col}{row}"].alignment = data_alignment
|
|
|
|
|
|
ws[f"{col}{row}"].border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
self.log_gui.log(" ✓ 添加色域数据")
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 2. EOTF 数据(HDR 特有)==========
|
|
|
|
|
|
if "eotf" in selected_items:
|
|
|
|
|
|
gray_data = self.results.get_intermediate_data("shared", "gray")
|
|
|
|
|
|
if not gray_data:
|
|
|
|
|
|
gray_data = self.results.get_intermediate_data(
|
|
|
|
|
|
"eotf", "gray"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
eotf_final_result = None
|
|
|
|
|
|
if "eotf" in self.results.test_items:
|
|
|
|
|
|
eotf_final_result = self.results.test_items[
|
|
|
|
|
|
"eotf"
|
|
|
|
|
|
].final_result
|
|
|
|
|
|
|
|
|
|
|
|
if gray_data and len(gray_data) > 0 and eotf_final_result:
|
|
|
|
|
|
eotf_list = eotf_final_result.get("eotf", [])
|
|
|
|
|
|
L_bar_list = eotf_final_result.get("L_bar", [])
|
|
|
|
|
|
|
|
|
|
|
|
ws.merge_cells(f"A{row}:G{row}")
|
|
|
|
|
|
ws[f"A{row}"] = "📊 EOTF 曲线数据"
|
|
|
|
|
|
ws[f"A{row}"].font = section_font
|
|
|
|
|
|
ws[f"A{row}"].fill = section_fill
|
|
|
|
|
|
ws[f"A{row}"].alignment = section_alignment
|
|
|
|
|
|
ws.row_dimensions[row].height = 25
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
# ✅ EOTF 统计信息(类似 Gamma 统计)
|
|
|
|
|
|
valid_eotf = []
|
|
|
|
|
|
if eotf_list:
|
|
|
|
|
|
for item in eotf_list:
|
|
|
|
|
|
if (
|
|
|
|
|
|
isinstance(item, (list, tuple))
|
|
|
|
|
|
and len(item) >= 4
|
|
|
|
|
|
):
|
|
|
|
|
|
eotf_val = item[3]
|
|
|
|
|
|
if 0.5 < eotf_val < 5.0:
|
|
|
|
|
|
valid_eotf.append(eotf_val)
|
|
|
|
|
|
|
|
|
|
|
|
if valid_eotf:
|
|
|
|
|
|
avg_eotf = sum(valid_eotf) / len(valid_eotf)
|
|
|
|
|
|
max_eotf = max(valid_eotf)
|
|
|
|
|
|
min_eotf = min(valid_eotf)
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"A{row}"] = "平均 EOTF"
|
|
|
|
|
|
ws[f"B{row}"] = f"{avg_eotf:.3f}"
|
|
|
|
|
|
ws[f"C{row}"] = "最大 EOTF"
|
|
|
|
|
|
ws[f"D{row}"] = f"{max_eotf:.3f}"
|
|
|
|
|
|
ws[f"E{row}"] = "最小 EOTF"
|
|
|
|
|
|
ws[f"F{row}"] = f"{min_eotf:.3f}"
|
|
|
|
|
|
|
|
|
|
|
|
for col in ["A", "B", "C", "D", "E", "F"]:
|
|
|
|
|
|
ws[f"{col}{row}"].font = (
|
|
|
|
|
|
label_font
|
|
|
|
|
|
if col in ["A", "C", "E"]
|
|
|
|
|
|
else data_font
|
|
|
|
|
|
)
|
|
|
|
|
|
ws[f"{col}{row}"].border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
# ✅ EOTF 数据表格(与 Gamma 表格完全一致)
|
|
|
|
|
|
headers = [
|
|
|
|
|
|
"灰阶 (%)",
|
|
|
|
|
|
"x 坐标",
|
|
|
|
|
|
"y 坐标",
|
|
|
|
|
|
"实测亮度\n(cd/m²)",
|
|
|
|
|
|
"归一化亮度\n(L_bar)",
|
|
|
|
|
|
"EOTF 值",
|
|
|
|
|
|
"",
|
|
|
|
|
|
]
|
|
|
|
|
|
for col_idx, header in enumerate(headers, start=1):
|
|
|
|
|
|
cell = ws.cell(row=row, column=col_idx)
|
|
|
|
|
|
cell.value = header
|
|
|
|
|
|
cell.font = header_font
|
|
|
|
|
|
cell.fill = header_fill
|
|
|
|
|
|
cell.alignment = header_alignment
|
|
|
|
|
|
cell.border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
total_points = len(gray_data)
|
|
|
|
|
|
for i in range(total_points - 1, -1, -1):
|
|
|
|
|
|
gray_level = (
|
|
|
|
|
|
100 - int(i * 100 / (total_points - 1))
|
|
|
|
|
|
if total_points > 1
|
|
|
|
|
|
else 0
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
x, y, lv = (
|
|
|
|
|
|
gray_data[i][0],
|
|
|
|
|
|
gray_data[i][1],
|
|
|
|
|
|
gray_data[i][2],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
L_bar_val = L_bar_list[i] if i < len(L_bar_list) else 0
|
|
|
|
|
|
|
|
|
|
|
|
eotf_val = None
|
|
|
|
|
|
if (
|
|
|
|
|
|
i < len(eotf_list)
|
|
|
|
|
|
and isinstance(eotf_list[i], (list, tuple))
|
|
|
|
|
|
and len(eotf_list[i]) >= 4
|
|
|
|
|
|
):
|
|
|
|
|
|
eotf_val = eotf_list[i][3]
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"A{row}"] = gray_level
|
|
|
|
|
|
ws[f"B{row}"] = x
|
|
|
|
|
|
ws[f"C{row}"] = y
|
|
|
|
|
|
ws[f"D{row}"] = lv
|
|
|
|
|
|
ws[f"E{row}"] = L_bar_val
|
|
|
|
|
|
|
|
|
|
|
|
if eotf_val is not None and 0.5 < eotf_val < 5.0:
|
|
|
|
|
|
ws[f"F{row}"] = eotf_val
|
|
|
|
|
|
ws[f"F{row}"].number_format = "0.000"
|
|
|
|
|
|
else:
|
|
|
|
|
|
ws[f"F{row}"] = "N/A"
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"A{row}"].number_format = "0"
|
|
|
|
|
|
ws[f"B{row}"].number_format = "0.0000"
|
|
|
|
|
|
ws[f"C{row}"].number_format = "0.0000"
|
|
|
|
|
|
ws[f"D{row}"].number_format = "0.00"
|
|
|
|
|
|
ws[f"E{row}"].number_format = "0.0000"
|
|
|
|
|
|
|
|
|
|
|
|
for col in ["A", "B", "C", "D", "E", "F"]:
|
|
|
|
|
|
ws[f"{col}{row}"].font = data_font
|
|
|
|
|
|
ws[f"{col}{row}"].alignment = data_alignment
|
|
|
|
|
|
ws[f"{col}{row}"].border = thin_border
|
|
|
|
|
|
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
self.log_gui.log(" ✓ 添加 EOTF 数据")
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 3. 色度一致性数据 ==========
|
|
|
|
|
|
if "cct" in selected_items:
|
|
|
|
|
|
gray_data = self.results.get_intermediate_data("shared", "gray")
|
|
|
|
|
|
if not gray_data:
|
|
|
|
|
|
gray_data = self.results.get_intermediate_data(
|
|
|
|
|
|
"cct", "gray"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if gray_data and len(gray_data) > 1:
|
|
|
|
|
|
gray_data_no_black = gray_data[:-1]
|
|
|
|
|
|
|
|
|
|
|
|
ws.merge_cells(f"A{row}:G{row}")
|
|
|
|
|
|
ws[f"A{row}"] = "🌈 色度一致性数据"
|
|
|
|
|
|
ws[f"A{row}"].font = section_font
|
|
|
|
|
|
ws[f"A{row}"].fill = section_fill
|
|
|
|
|
|
ws[f"A{row}"].alignment = section_alignment
|
|
|
|
|
|
ws.row_dimensions[row].height = 25
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
x_coords = [d[0] for d in gray_data_no_black]
|
|
|
|
|
|
y_coords = [d[1] for d in gray_data_no_black]
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"A{row}"] = "x 坐标范围"
|
|
|
|
|
|
ws[f"B{row}"] = f"{min(x_coords):.4f} ~ {max(x_coords):.4f}"
|
|
|
|
|
|
ws[f"C{row}"] = "y 坐标范围"
|
|
|
|
|
|
ws[f"D{row}"] = f"{min(y_coords):.4f} ~ {max(y_coords):.4f}"
|
|
|
|
|
|
|
|
|
|
|
|
for col in ["A", "B", "C", "D"]:
|
|
|
|
|
|
ws[f"{col}{row}"].font = (
|
|
|
|
|
|
label_font if col in ["A", "C"] else data_font
|
|
|
|
|
|
)
|
|
|
|
|
|
ws[f"{col}{row}"].border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
headers = [
|
|
|
|
|
|
"灰阶 (%)",
|
|
|
|
|
|
"x 坐标",
|
|
|
|
|
|
"y 坐标",
|
|
|
|
|
|
"亮度 (cd/m²)",
|
|
|
|
|
|
"",
|
|
|
|
|
|
"",
|
|
|
|
|
|
"",
|
|
|
|
|
|
]
|
|
|
|
|
|
for col_idx, header in enumerate(headers, start=1):
|
|
|
|
|
|
cell = ws.cell(row=row, column=col_idx)
|
|
|
|
|
|
cell.value = header
|
|
|
|
|
|
cell.font = header_font
|
|
|
|
|
|
cell.fill = header_fill
|
|
|
|
|
|
cell.alignment = header_alignment
|
|
|
|
|
|
cell.border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
total_points = len(gray_data)
|
|
|
|
|
|
for i in range(len(gray_data_no_black) - 1, -1, -1):
|
|
|
|
|
|
x, y, lv = (
|
|
|
|
|
|
gray_data_no_black[i][0],
|
|
|
|
|
|
gray_data_no_black[i][1],
|
|
|
|
|
|
gray_data_no_black[i][2],
|
|
|
|
|
|
)
|
|
|
|
|
|
gray_level = (
|
|
|
|
|
|
100 - int(i * 100 / (total_points - 1))
|
|
|
|
|
|
if total_points > 1
|
|
|
|
|
|
else 0
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"A{row}"] = gray_level
|
|
|
|
|
|
ws[f"B{row}"] = x
|
|
|
|
|
|
ws[f"C{row}"] = y
|
|
|
|
|
|
ws[f"D{row}"] = lv
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"A{row}"].number_format = "0"
|
|
|
|
|
|
ws[f"B{row}"].number_format = "0.0000"
|
|
|
|
|
|
ws[f"C{row}"].number_format = "0.0000"
|
|
|
|
|
|
ws[f"D{row}"].number_format = "0.00"
|
|
|
|
|
|
|
|
|
|
|
|
for col in ["A", "B", "C", "D"]:
|
|
|
|
|
|
ws[f"{col}{row}"].font = data_font
|
|
|
|
|
|
ws[f"{col}{row}"].alignment = data_alignment
|
|
|
|
|
|
ws[f"{col}{row}"].border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
self.log_gui.log(" ✓ 添加色度一致性数据")
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 4. 对比度数据 ==========
|
|
|
|
|
|
if "contrast" in selected_items:
|
|
|
|
|
|
contrast_final_result = None
|
|
|
|
|
|
if "contrast" in self.results.test_items:
|
|
|
|
|
|
contrast_final_result = self.results.test_items[
|
|
|
|
|
|
"contrast"
|
|
|
|
|
|
].final_result
|
|
|
|
|
|
|
|
|
|
|
|
if contrast_final_result:
|
|
|
|
|
|
ws.merge_cells(f"A{row}:G{row}")
|
|
|
|
|
|
ws[f"A{row}"] = "⚫⚪ 对比度测试数据"
|
|
|
|
|
|
ws[f"A{row}"].font = section_font
|
|
|
|
|
|
ws[f"A{row}"].fill = section_fill
|
|
|
|
|
|
ws[f"A{row}"].alignment = section_alignment
|
|
|
|
|
|
ws.row_dimensions[row].height = 25
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
max_lv = contrast_final_result.get("max_luminance", 0)
|
|
|
|
|
|
min_lv = contrast_final_result.get("min_luminance", 0)
|
|
|
|
|
|
contrast_ratio = contrast_final_result.get(
|
|
|
|
|
|
"contrast_ratio", 0
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
info_items = [
|
|
|
|
|
|
("最大亮度(白场)", f"{max_lv:.2f} cd/m²"),
|
|
|
|
|
|
("最小亮度(黑场)", f"{min_lv:.4f} cd/m²"),
|
|
|
|
|
|
("对比度", f"{contrast_ratio:.0f}:1"),
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
for label, value in info_items:
|
|
|
|
|
|
ws[f"A{row}"] = label
|
|
|
|
|
|
ws[f"B{row}"] = value
|
|
|
|
|
|
ws[f"A{row}"].font = label_font
|
|
|
|
|
|
ws[f"B{row}"].font = data_font
|
|
|
|
|
|
ws[f"A{row}"].border = thin_border
|
|
|
|
|
|
ws[f"B{row}"].border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
self.log_gui.log(" ✓ 添加对比度数据")
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 5. 色准数据(HDR 特有)==========
|
|
|
|
|
|
if "accuracy" in selected_items:
|
|
|
|
|
|
accuracy_final_result = None
|
|
|
|
|
|
if "accuracy" in self.results.test_items:
|
|
|
|
|
|
accuracy_final_result = self.results.test_items[
|
|
|
|
|
|
"accuracy"
|
|
|
|
|
|
].final_result
|
|
|
|
|
|
|
|
|
|
|
|
if accuracy_final_result:
|
|
|
|
|
|
ws.merge_cells(f"A{row}:G{row}")
|
|
|
|
|
|
ws[f"A{row}"] = "🎯 色准测试数据"
|
|
|
|
|
|
ws[f"A{row}"].font = section_font
|
|
|
|
|
|
ws[f"A{row}"].fill = section_fill
|
|
|
|
|
|
ws[f"A{row}"].alignment = section_alignment
|
|
|
|
|
|
ws.row_dimensions[row].height = 25
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
# 色准统计信息
|
|
|
|
|
|
avg_delta_e = accuracy_final_result.get("avg_delta_e", 0)
|
|
|
|
|
|
max_delta_e = accuracy_final_result.get("max_delta_e", 0)
|
|
|
|
|
|
min_delta_e = accuracy_final_result.get("min_delta_e", 0)
|
|
|
|
|
|
excellent_count = accuracy_final_result.get(
|
|
|
|
|
|
"excellent_count", 0
|
|
|
|
|
|
)
|
|
|
|
|
|
good_count = accuracy_final_result.get("good_count", 0)
|
|
|
|
|
|
poor_count = accuracy_final_result.get("poor_count", 0)
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"A{row}"] = "平均 ΔE"
|
|
|
|
|
|
ws[f"B{row}"] = f"{avg_delta_e:.2f}"
|
|
|
|
|
|
ws[f"C{row}"] = "最大 ΔE"
|
|
|
|
|
|
ws[f"D{row}"] = f"{max_delta_e:.2f}"
|
|
|
|
|
|
ws[f"E{row}"] = "最小 ΔE"
|
|
|
|
|
|
ws[f"F{row}"] = f"{min_delta_e:.2f}"
|
|
|
|
|
|
|
|
|
|
|
|
for col in ["A", "B", "C", "D", "E", "F"]:
|
|
|
|
|
|
ws[f"{col}{row}"].font = (
|
|
|
|
|
|
label_font if col in ["A", "C", "E"] else data_font
|
|
|
|
|
|
)
|
|
|
|
|
|
ws[f"{col}{row}"].border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
# 第二行统计
|
|
|
|
|
|
ws[f"A{row}"] = "优秀 (ΔE<3)"
|
|
|
|
|
|
ws[f"B{row}"] = f"{excellent_count} 个"
|
|
|
|
|
|
ws[f"C{row}"] = "良好 (3≤ΔE<5)"
|
|
|
|
|
|
ws[f"D{row}"] = f"{good_count} 个"
|
|
|
|
|
|
ws[f"E{row}"] = "偏差 (ΔE≥5)"
|
|
|
|
|
|
ws[f"F{row}"] = f"{poor_count} 个"
|
|
|
|
|
|
|
|
|
|
|
|
for col in ["A", "B", "C", "D", "E", "F"]:
|
|
|
|
|
|
ws[f"{col}{row}"].font = (
|
|
|
|
|
|
label_font if col in ["A", "C", "E"] else data_font
|
|
|
|
|
|
)
|
|
|
|
|
|
ws[f"{col}{row}"].border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 色准详细数据表格(带 xy 坐标和亮度)==========
|
|
|
|
|
|
color_patches = accuracy_final_result.get(
|
|
|
|
|
|
"color_patches", []
|
|
|
|
|
|
)
|
|
|
|
|
|
delta_e_values = accuracy_final_result.get(
|
|
|
|
|
|
"delta_e_values", []
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# ✅ 获取原始测量数据(包含 xy 和亮度)
|
|
|
|
|
|
color_measurements = accuracy_final_result.get(
|
|
|
|
|
|
"color_measurements", []
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if color_patches and delta_e_values:
|
|
|
|
|
|
# 表头
|
|
|
|
|
|
headers = [
|
|
|
|
|
|
"序号",
|
|
|
|
|
|
"颜色名称",
|
|
|
|
|
|
"x 坐标",
|
|
|
|
|
|
"y 坐标",
|
|
|
|
|
|
"亮度 (cd/m²)",
|
|
|
|
|
|
"ΔE 2000",
|
|
|
|
|
|
"等级",
|
|
|
|
|
|
]
|
|
|
|
|
|
for col_idx, header in enumerate(headers, start=1):
|
|
|
|
|
|
cell = ws.cell(row=row, column=col_idx)
|
|
|
|
|
|
cell.value = header
|
|
|
|
|
|
cell.font = header_font
|
|
|
|
|
|
cell.fill = header_fill
|
|
|
|
|
|
cell.alignment = header_alignment
|
|
|
|
|
|
cell.border = thin_border
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
# 数据行
|
|
|
|
|
|
for idx, (color_name, delta_e) in enumerate(
|
|
|
|
|
|
zip(color_patches, delta_e_values), start=1
|
|
|
|
|
|
):
|
|
|
|
|
|
# 判断等级
|
|
|
|
|
|
if delta_e < 3:
|
|
|
|
|
|
grade = "优秀"
|
|
|
|
|
|
elif delta_e < 5:
|
|
|
|
|
|
grade = "良好"
|
|
|
|
|
|
else:
|
|
|
|
|
|
grade = "偏差"
|
|
|
|
|
|
|
|
|
|
|
|
# ✅ 获取测量数据(x, y, 亮度)
|
|
|
|
|
|
x_val = "N/A"
|
|
|
|
|
|
y_val = "N/A"
|
|
|
|
|
|
lv_val = "N/A"
|
|
|
|
|
|
|
|
|
|
|
|
if color_measurements and idx - 1 < len(
|
|
|
|
|
|
color_measurements
|
|
|
|
|
|
):
|
|
|
|
|
|
measurement = color_measurements[idx - 1]
|
|
|
|
|
|
if len(measurement) >= 3:
|
|
|
|
|
|
x_val = measurement[0]
|
|
|
|
|
|
y_val = measurement[1]
|
|
|
|
|
|
lv_val = measurement[2]
|
|
|
|
|
|
|
|
|
|
|
|
ws[f"A{row}"] = idx
|
|
|
|
|
|
ws[f"B{row}"] = color_name
|
|
|
|
|
|
ws[f"C{row}"] = x_val
|
|
|
|
|
|
ws[f"D{row}"] = y_val
|
|
|
|
|
|
ws[f"E{row}"] = lv_val
|
|
|
|
|
|
ws[f"F{row}"] = delta_e
|
|
|
|
|
|
ws[f"G{row}"] = grade
|
|
|
|
|
|
|
|
|
|
|
|
# 数字格式
|
|
|
|
|
|
ws[f"A{row}"].number_format = "0"
|
|
|
|
|
|
if isinstance(x_val, (int, float)):
|
|
|
|
|
|
ws[f"C{row}"].number_format = "0.0000"
|
|
|
|
|
|
if isinstance(y_val, (int, float)):
|
|
|
|
|
|
ws[f"D{row}"].number_format = "0.0000"
|
|
|
|
|
|
if isinstance(lv_val, (int, float)):
|
|
|
|
|
|
ws[f"E{row}"].number_format = "0.00"
|
|
|
|
|
|
ws[f"F{row}"].number_format = "0.00"
|
|
|
|
|
|
|
|
|
|
|
|
for col in ["A", "B", "C", "D", "E", "F", "G"]:
|
|
|
|
|
|
ws[f"{col}{row}"].font = data_font
|
|
|
|
|
|
ws[f"{col}{row}"].alignment = data_alignment
|
|
|
|
|
|
ws[f"{col}{row}"].border = thin_border
|
|
|
|
|
|
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
self.log_gui.log(" ✓ 添加色准数据(含 xy 坐标和亮度)")
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 调整列宽 ==========
|
|
|
|
|
|
for col in ["A", "B", "C", "D", "E", "F", "G"]:
|
|
|
|
|
|
ws.column_dimensions[col].width = 18
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 保存 Excel ==========
|
|
|
|
|
|
excel_path = os.path.join(result_dir, "测试数据.xlsx")
|
|
|
|
|
|
wb.save(excel_path)
|
|
|
|
|
|
|
|
|
|
|
|
self.log_gui.log(f"✓ 已保存: 测试数据.xlsx")
|
|
|
|
|
|
self.log_gui.log("=" * 60)
|
|
|
|
|
|
|
|
|
|
|
|
except ImportError:
|
|
|
|
|
|
self.log_gui.log("⚠️ 未安装 openpyxl 库,跳过 Excel 导出")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
self.log_gui.log(f"⚠️ Excel 导出失败: {str(e)}")
|
|
|
|
|
|
import traceback
|
|
|
|
|
|
|
|
|
|
|
|
self.log_gui.log(traceback.format_exc())
|
|
|
|
|
|
|
|
|
|
|
|
# ========== ✅ 统一的成功提示(在所有 Excel 代码之后)==========
|
|
|
|
|
|
self.log_gui.log(f"=" * 50)
|
|
|
|
|
|
self.log_gui.log(f"✅ 测试结果已保存到目录: {result_dir}")
|
|
|
|
|
|
self.log_gui.log(f"=" * 50)
|
|
|
|
|
|
messagebox.showinfo("成功", f"测试结果已保存到目录:\n{result_dir}")
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
self.log_gui.log(f"❌ 保存测试结果失败: {str(e)}")
|
|
|
|
|
|
import traceback
|
|
|
|
|
|
|
|
|
|
|
|
self.log_gui.log(traceback.format_exc())
|
|
|
|
|
|
messagebox.showerror("错误", f"保存测试结果失败: {str(e)}")
|
|
|
|
|
|
|
2026-04-20 11:13:57 +08:00
|
|
|
|
new_pq_results = _run_new_pq_results
|
|
|
|
|
|
run_test = _run_run_test
|
|
|
|
|
|
run_screen_module_test = _run_run_screen_module_test
|
|
|
|
|
|
run_custom_sdr_test = _run_run_custom_sdr_test
|
|
|
|
|
|
run_sdr_movie_test = _run_run_sdr_movie_test
|
|
|
|
|
|
run_hdr_movie_test = _run_run_hdr_movie_test
|
|
|
|
|
|
send_fix_pattern = _run_send_fix_pattern
|
|
|
|
|
|
test_custom_sdr = _run_test_custom_sdr
|
|
|
|
|
|
test_gamut = _run_test_gamut
|
|
|
|
|
|
test_gamma = _run_test_gamma
|
|
|
|
|
|
test_eotf = _run_test_eotf
|
|
|
|
|
|
test_cct = _run_test_cct
|
|
|
|
|
|
test_contrast = _run_test_contrast
|
|
|
|
|
|
calculate_delta_e_2000 = staticmethod(_calc_delta_e_2000)
|
|
|
|
|
|
|
|
|
|
|
|
test_color_accuracy = _run_test_color_accuracy
|
|
|
|
|
|
get_accuracy_color_standards = staticmethod(_get_accuracy_color_standards)
|
|
|
|
|
|
|
|
|
|
|
|
calculate_gamut_coverage = staticmethod(_calc_gamut_coverage)
|
|
|
|
|
|
|
|
|
|
|
|
calculate_gamma = staticmethod(_calc_gamma)
|
|
|
|
|
|
|
|
|
|
|
|
calculate_color_accuracy = staticmethod(_calc_color_accuracy)
|
|
|
|
|
|
|
|
|
|
|
|
plot_gamut = _plot_gamut
|
|
|
|
|
|
plot_gamma = _plot_gamma
|
|
|
|
|
|
plot_eotf = _plot_eotf
|
|
|
|
|
|
calculate_pq_curve = staticmethod(_calc_pq_curve)
|
|
|
|
|
|
|
|
|
|
|
|
plot_cct = _plot_cct
|
|
|
|
|
|
plot_contrast = _plot_contrast
|
|
|
|
|
|
plot_accuracy = _plot_accuracy
|
|
|
|
|
|
on_test_completed = _run_on_test_completed
|
|
|
|
|
|
on_custom_template_test_completed = _run_on_custom_template_test_completed
|
|
|
|
|
|
get_current_test_result = _run_get_current_test_result
|
|
|
|
|
|
on_test_error = _run_on_test_error
|
2026-04-16 16:51:05 +08:00
|
|
|
|
def get_test_type_name(self, test_type):
|
|
|
|
|
|
"""获取测试类型的显示名称"""
|
|
|
|
|
|
if test_type == "screen_module":
|
|
|
|
|
|
return "屏模组性能测试"
|
|
|
|
|
|
elif test_type == "sdr_movie":
|
|
|
|
|
|
return "SDR Movie测试"
|
|
|
|
|
|
elif test_type == "hdr_movie":
|
|
|
|
|
|
return "HDR Movie测试"
|
|
|
|
|
|
return test_type
|
|
|
|
|
|
|
|
|
|
|
|
def get_selected_test_items(self):
|
|
|
|
|
|
"""获取当前选中的测试项"""
|
|
|
|
|
|
selected_items = []
|
|
|
|
|
|
for var_name, var in self.test_vars.items():
|
|
|
|
|
|
if var.get():
|
|
|
|
|
|
selected_items.append(var_name.split("_")[-1])
|
|
|
|
|
|
return selected_items
|
|
|
|
|
|
|
|
|
|
|
|
def update_config(self, event=None):
|
|
|
|
|
|
"""更新配置"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.config.set_device_config(
|
|
|
|
|
|
self.ca_com_var.get(),
|
|
|
|
|
|
self.ucd_list_var.get(),
|
|
|
|
|
|
self.ca_channel_var.get(),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 保存当前选中的测试项到配置
|
|
|
|
|
|
self.config.set_current_test_items(self.get_selected_test_items())
|
|
|
|
|
|
|
|
|
|
|
|
# 待修改为三种测试类型的timing值
|
|
|
|
|
|
self.config.set_current_timing(self.screen_module_timing_var.get())
|
|
|
|
|
|
|
|
|
|
|
|
# 自动保存配置到文件
|
|
|
|
|
|
self.save_pq_config()
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
self.log_gui.log(f"更新配置失败: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
def update_config_and_tabs(self):
|
|
|
|
|
|
"""更新配置并同步Tab状态"""
|
|
|
|
|
|
self.update_config()
|
|
|
|
|
|
self.update_chart_tabs_state()
|
|
|
|
|
|
|
|
|
|
|
|
# 根据当前测试类型保存对应参数
|
|
|
|
|
|
current_test_type = self.config.current_test_type
|
|
|
|
|
|
selected_items = self.get_selected_test_items()
|
|
|
|
|
|
|
|
|
|
|
|
if current_test_type == "screen_module":
|
|
|
|
|
|
if "cct" in selected_items:
|
|
|
|
|
|
self.save_cct_params()
|
|
|
|
|
|
|
|
|
|
|
|
elif current_test_type == "sdr_movie":
|
|
|
|
|
|
if "cct" in selected_items:
|
|
|
|
|
|
self.save_sdr_cct_params()
|
|
|
|
|
|
|
|
|
|
|
|
elif current_test_type == "hdr_movie":
|
|
|
|
|
|
if "cct" in selected_items:
|
|
|
|
|
|
if hasattr(self, "save_hdr_cct_params"):
|
|
|
|
|
|
self.save_hdr_cct_params()
|
|
|
|
|
|
|
|
|
|
|
|
# 控制参数框的显示
|
|
|
|
|
|
self.toggle_cct_params_frame()
|
|
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
toggle_cct_params_frame = _cct_toggle_cct_params_frame
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
|
|
|
|
|
def on_screen_module_timing_changed(self, event=None):
|
|
|
|
|
|
"""屏模组信号格式改变时的回调"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
selected_timing = self.screen_module_timing_var.get()
|
|
|
|
|
|
|
|
|
|
|
|
# 记录日志
|
|
|
|
|
|
self.log_gui.log(f"屏模组信号格式已更改为: {selected_timing}")
|
|
|
|
|
|
|
|
|
|
|
|
# 解析分辨率和刷新率
|
|
|
|
|
|
import re
|
|
|
|
|
|
|
|
|
|
|
|
match = re.search(r"(\d+)x(\d+)\s*@\s*(\d+)", selected_timing)
|
|
|
|
|
|
if match:
|
|
|
|
|
|
width = int(match.group(1))
|
|
|
|
|
|
height = int(match.group(2))
|
|
|
|
|
|
refresh_rate = int(match.group(3))
|
|
|
|
|
|
|
|
|
|
|
|
self.log_gui.log(f" ├─ 分辨率: {width}x{height}")
|
|
|
|
|
|
self.log_gui.log(f" └─ 刷新率: {refresh_rate}Hz")
|
|
|
|
|
|
|
|
|
|
|
|
# 根据分辨率给出提示
|
|
|
|
|
|
if width >= 3840: # 4K及以上
|
|
|
|
|
|
self.log_gui.log(" ℹ️ 检测到4K分辨率")
|
|
|
|
|
|
|
|
|
|
|
|
if refresh_rate >= 120:
|
|
|
|
|
|
self.log_gui.log(" ℹ️ 检测到高刷新率")
|
|
|
|
|
|
|
|
|
|
|
|
# 更新配置
|
|
|
|
|
|
self.config.set_current_timing(selected_timing)
|
|
|
|
|
|
|
|
|
|
|
|
# 如果正在测试,提示用户
|
|
|
|
|
|
if self.testing:
|
|
|
|
|
|
self.log_gui.log("⚠️ 警告: 测试进行中,信号格式更改将在下次测试时生效")
|
|
|
|
|
|
|
|
|
|
|
|
# 保存配置
|
|
|
|
|
|
self.save_pq_config()
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
self.log_gui.log(f"❌ 屏模组信号格式更改失败: {str(e)}")
|
|
|
|
|
|
|
2026-04-20 11:13:57 +08:00
|
|
|
|
load_pq_config = _cfg_load_pq_config
|
|
|
|
|
|
save_pq_config = _cfg_save_pq_config
|
2026-04-16 16:51:05 +08:00
|
|
|
|
def on_closing(self):
|
|
|
|
|
|
"""窗口关闭时的处理"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
# ✅ 检查是否清理了配置
|
|
|
|
|
|
if not self.config_cleared:
|
|
|
|
|
|
# 保存配置
|
|
|
|
|
|
self.save_pq_config()
|
|
|
|
|
|
else:
|
|
|
|
|
|
print("配置已清理,不再保存")
|
|
|
|
|
|
|
|
|
|
|
|
# 断开设备连接
|
|
|
|
|
|
if self.ucd.status:
|
|
|
|
|
|
self.ucd.close()
|
|
|
|
|
|
if self.ca is not None:
|
|
|
|
|
|
self.ca.close()
|
|
|
|
|
|
|
|
|
|
|
|
# 关闭窗口
|
|
|
|
|
|
self.root.destroy()
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"关闭窗口时出错: {str(e)}")
|
|
|
|
|
|
self.root.destroy()
|
|
|
|
|
|
|
|
|
|
|
|
def on_screen_gamut_ref_changed(self, event=None):
|
|
|
|
|
|
"""屏模组色域参考标准改变时的回调"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
new_ref = self.screen_gamut_ref_var.get()
|
|
|
|
|
|
self.log_gui.log(f"✓ 屏模组色域参考标准已更改为: {new_ref}")
|
|
|
|
|
|
|
|
|
|
|
|
# 保存到配置
|
|
|
|
|
|
if "screen_module" not in self.config.current_test_types:
|
|
|
|
|
|
self.config.current_test_types["screen_module"] = {}
|
|
|
|
|
|
|
|
|
|
|
|
self.config.current_test_types["screen_module"]["gamut_reference"] = new_ref
|
|
|
|
|
|
self.save_pq_config()
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
self.log_gui.log(f"保存屏模组色域参考标准失败: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
def on_sdr_gamut_ref_changed(self, event=None):
|
|
|
|
|
|
"""SDR 色域参考标准改变时的回调"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
new_ref = self.sdr_gamut_ref_var.get()
|
|
|
|
|
|
self.log_gui.log(f"✓ SDR 色域参考标准已更改为: {new_ref}")
|
|
|
|
|
|
|
|
|
|
|
|
# 保存到配置
|
|
|
|
|
|
if "sdr_movie" not in self.config.current_test_types:
|
|
|
|
|
|
self.config.current_test_types["sdr_movie"] = {}
|
|
|
|
|
|
|
|
|
|
|
|
self.config.current_test_types["sdr_movie"]["gamut_reference"] = new_ref
|
|
|
|
|
|
self.save_pq_config()
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
self.log_gui.log(f"保存 SDR 色域参考标准失败: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
def on_hdr_gamut_ref_changed(self, event=None):
|
|
|
|
|
|
"""HDR 色域参考标准改变时的回调"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
new_ref = self.hdr_gamut_ref_var.get()
|
|
|
|
|
|
self.log_gui.log(f"✓ HDR 色域参考标准已更改为: {new_ref}")
|
|
|
|
|
|
|
|
|
|
|
|
# 保存到配置
|
|
|
|
|
|
if "hdr_movie" not in self.config.current_test_types:
|
|
|
|
|
|
self.config.current_test_types["hdr_movie"] = {}
|
|
|
|
|
|
|
|
|
|
|
|
self.config.current_test_types["hdr_movie"]["gamut_reference"] = new_ref
|
|
|
|
|
|
self.save_pq_config()
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
self.log_gui.log(f"保存 HDR 色域参考标准失败: {str(e)}")
|
|
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
toggle_screen_debug_panel = _side_toggle_screen_debug_panel
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
toggle_sdr_debug_panel = _side_toggle_sdr_debug_panel
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:48:38 +08:00
|
|
|
|
toggle_hdr_debug_panel = _side_toggle_hdr_debug_panel
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
2026-04-20 11:13:57 +08:00
|
|
|
|
clear_config_file = _cfg_clear_config_file
|
|
|
|
|
|
start_local_dimming_test = _ld_start_local_dimming_test
|
|
|
|
|
|
update_ld_results = _ld_update_ld_results
|
|
|
|
|
|
stop_local_dimming_test = _ld_stop_local_dimming_test
|
|
|
|
|
|
send_ld_window = _ld_send_ld_window
|
|
|
|
|
|
measure_ld_luminance = _ld_measure_ld_luminance
|
|
|
|
|
|
clear_ld_records = _ld_clear_ld_records
|
|
|
|
|
|
save_local_dimming_results = _ld_save_local_dimming_results
|
2026-04-16 16:51:05 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
|
try:
|
|
|
|
|
|
# root = tk.Tk()
|
|
|
|
|
|
root = ttk.Window(themename="yeti")
|
|
|
|
|
|
app = PQAutomationApp(root)
|
|
|
|
|
|
root.mainloop()
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print("程序发生错误:", e)
|
|
|
|
|
|
traceback.print_exc()
|
|
|
|
|
|
finally:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
main()
|