添加瞬时峰值测试

This commit is contained in:
xinzhu.yin
2026-06-08 11:14:12 +08:00
parent febbb28a4c
commit e4890d9d8d
3 changed files with 264 additions and 11 deletions

View File

@@ -34,6 +34,9 @@ DEFAULT_WINDOW_PERCENTAGES = [1, 2, 5, 10, 18, 25, 50, 75, 100]
DEFAULT_CHESSBOARD_GRID = 5
INSTANT_PEAK_WINDOW_PERCENTAGE = 10
INSTANT_PEAK_CAPTURE_DELAY = 0.5
INSTANT_PEAK_DROP_RATIO = 0.97
INSTANT_PEAK_MIN_DROP_NITS = 2.0
INSTANT_PEAK_SAMPLE_INTERVAL = 0.3
_TEMP_DIR = None
_IMAGE_CACHE = {} # {(width, height, percentage): file_path}
@@ -63,8 +66,8 @@ def _get_temp_dir():
return _TEMP_DIR
def _make_window_image_array(width, height, percentage):
"""生成黑底+居中白窗的 numpy 图像,保持屏幕比例。"""
def _make_window_image_array(width, height, percentage, window_level=255):
"""生成黑底+居中窗口图像,保持屏幕比例。"""
image = np.zeros((height, width, 3), dtype=np.uint8)
if percentage >= 100:
ww, wh = width, height
@@ -74,18 +77,19 @@ def _make_window_image_array(width, height, percentage):
wh = int(height * scale)
x1 = (width - ww) // 2
y1 = (height - wh) // 2
image[y1:y1 + wh, x1:x1 + ww] = 255
image[y1:y1 + wh, x1:x1 + ww] = int(window_level)
return image
def _ensure_window_image(width, height, percentage):
def _ensure_window_image(width, height, percentage, window_level=255):
"""生成或复用缓存的窗口 PNG 文件,返回路径。"""
key = (width, height, percentage)
level = max(0, min(255, int(window_level)))
key = (width, height, percentage, level)
cached = _IMAGE_CACHE.get(key)
if cached and os.path.exists(cached):
return cached
arr = _make_window_image_array(width, height, percentage)
fname = f"window_{width}x{height}_{percentage:03d}percent.png"
arr = _make_window_image_array(width, height, percentage, level)
fname = f"window_{width}x{height}_{percentage:03d}percent_{level:03d}lv.png"
path = os.path.join(_get_temp_dir(), fname)
Image.fromarray(arr, mode="RGB").save(path, format="PNG")
_IMAGE_CACHE[key] = path
@@ -536,6 +540,179 @@ def send_ld_instant_peak(self: "PQAutomationApp"):
threading.Thread(target=send, daemon=True).start()
def start_ld_instant_peak_tracking(self: "PQAutomationApp"):
"""独立瞬时峰值测试:持续采样直到亮度回落或达到最长测量时长。"""
if not self.signal_service.is_connected:
messagebox.showwarning("警告", "请先连接 UCD323 设备")
return
if not self.ca:
messagebox.showwarning("警告", "请先连接 CA410 色度计")
return
if getattr(self, "ld_peak_tracking", False):
messagebox.showinfo("提示", "瞬时峰值测试正在进行中")
return
try:
window_percentage = int(float(self.ld_peak_window_size_var.get()))
if window_percentage < 1 or window_percentage > 100:
raise ValueError("窗口百分比超出范围")
window_luminance_percent = float(self.ld_peak_window_luminance_var.get())
if window_luminance_percent < 1 or window_luminance_percent > 100:
raise ValueError("窗口亮度超出范围")
max_duration = float(self.ld_peak_duration_var.get())
if max_duration <= 0:
raise ValueError("测量时长必须大于 0")
sample_interval = float(
self.ld_peak_sample_interval_var.get()
if hasattr(self, "ld_peak_sample_interval_var")
else INSTANT_PEAK_SAMPLE_INTERVAL
)
if sample_interval <= 0:
raise ValueError("采样间隔必须大于 0")
except Exception as e:
messagebox.showwarning("参数错误", f"请检查瞬时峰值参数: {e}")
return
record_curve = bool(self.ld_peak_record_curve_var.get())
window_level = int(round(window_luminance_percent / 100.0 * 255.0))
pattern_label = f"黑场后切 {window_percentage}%窗口({window_luminance_percent:.0f}%亮度)"
self.ld_peak_tracking = True
self.log_gui.log(
f"⚡ 开始独立瞬时峰值测试: {pattern_label},最长 {max_duration:.1f}s",
level="info",
)
_set_current_ld_pattern(self, "瞬时峰值亮度", pattern_label, window_percentage)
if hasattr(self, "ld_peak_start_btn"):
self.ld_peak_start_btn.configure(state="disabled")
if hasattr(self, "ld_peak_stop_btn"):
self.ld_peak_stop_btn.configure(state="normal")
def run():
peak_lv = None
peak_time = None
drop_time = None
curve_count = 0
try:
if not _apply_ld_ucd_params(self):
return
width, height = self.signal_service.current_resolution()
black_image = _ensure_solid_image(width, height, (0, 0, 0), "black")
peak_image = _ensure_window_image(
width,
height,
window_percentage,
window_level,
)
self.signal_service.send_image(black_image)
time.sleep(INSTANT_PEAK_CAPTURE_DELAY)
self.signal_service.send_image(peak_image)
started = time.time()
while self.ld_peak_tracking:
elapsed = time.time() - started
if elapsed > max_duration:
break
x, y, lv, _X, _Y, _Z = self.read_ca_xyLv()
if lv is None:
time.sleep(sample_interval)
continue
if peak_lv is None or lv > peak_lv:
peak_lv = float(lv)
peak_time = elapsed
if record_curve:
curve_count += 1
self._dispatch_ui(
self.ld_tree.insert,
"",
tk.END,
values=(
"瞬时峰值曲线",
f"{window_percentage}%窗口@{window_luminance_percent:.0f}% t={elapsed:.2f}s",
f"{float(lv):.4f}",
f"{x:.4f}",
f"{y:.4f}",
datetime.datetime.now().strftime("%H:%M:%S"),
),
)
if peak_lv is not None:
drop_threshold = max(
peak_lv * INSTANT_PEAK_DROP_RATIO,
peak_lv - INSTANT_PEAK_MIN_DROP_NITS,
)
if lv < drop_threshold and elapsed > (peak_time or 0):
drop_time = elapsed
break
self._dispatch_ui(
self.ld_result_label.config,
text=(
f"亮度: {lv:.2f} cd/m² | 峰值: {(peak_lv or lv):.2f} cd/m²"
f" | t: {elapsed:.2f}s"
),
)
time.sleep(sample_interval)
if peak_lv is None:
self._dispatch_ui(self.log_gui.log, "瞬时峰值测试未采到有效亮度", "warning")
return
end_time = drop_time if drop_time is not None else (time.time() - started)
sustain_time = max(0.0, end_time - (peak_time or 0.0))
result_label = (
f"峰值={peak_lv:.2f} cd/m², 持续={sustain_time:.2f}s"
if drop_time is not None
else f"峰值={peak_lv:.2f} cd/m², 持续>{sustain_time:.2f}s(未检测到回落)"
)
self._dispatch_ui(
self.ld_tree.insert,
"",
tk.END,
values=(
"瞬时峰值亮度",
pattern_label,
result_label,
"--",
"--",
datetime.datetime.now().strftime("%H:%M:%S"),
),
)
self._dispatch_ui(
self.log_gui.log,
f"瞬时峰值测试完成: {result_label},曲线点 {curve_count}",
"success",
)
except Exception as e:
self._dispatch_ui(self.log_gui.log, f"瞬时峰值测试异常: {e}", "error")
finally:
self.ld_peak_tracking = False
if hasattr(self, "ld_peak_start_btn"):
self._dispatch_ui(self.ld_peak_start_btn.configure, state="normal")
if hasattr(self, "ld_peak_stop_btn"):
self._dispatch_ui(self.ld_peak_stop_btn.configure, state="disabled")
threading.Thread(target=run, daemon=True).start()
def stop_ld_instant_peak_tracking(self: "PQAutomationApp"):
"""停止独立瞬时峰值连续采样。"""
if getattr(self, "ld_peak_tracking", False):
self.ld_peak_tracking = False
self.log_gui.log("已请求停止瞬时峰值测试", level="info")
def measure_ld_luminance(self: "PQAutomationApp"):
"""测量当前显示的亮度并追加一行到 Treeview。"""
if not self.ca:
@@ -585,6 +762,7 @@ def clear_ld_records(self: "PQAutomationApp"):
self.current_ld_percentage = None
self.current_ld_test_item = None
self.current_ld_pattern_label = None
self.ld_peak_tracking = False
self.log_gui.log("测试记录已清空", level="info")
@@ -630,6 +808,8 @@ class LocalDimmingMixin:
send_ld_checkerboard = send_ld_checkerboard
send_ld_black_pattern = send_ld_black_pattern
send_ld_instant_peak = send_ld_instant_peak
start_ld_instant_peak_tracking = start_ld_instant_peak_tracking
stop_ld_instant_peak_tracking = stop_ld_instant_peak_tracking
measure_ld_luminance = measure_ld_luminance
clear_ld_records = clear_ld_records
save_local_dimming_results = save_local_dimming_results

View File

@@ -134,7 +134,79 @@ def create_local_dimming_panel(self: "PQAutomationApp"):
width=12,
).pack(side=tk.LEFT, padx=3)
# ==================== 4. CA410 采集按钮 ====================
# ==================== 4. 独立瞬时峰值连续测试 ====================
peak_frame = ttk.LabelFrame(main_container, text="⚡ 瞬时峰值独立测试", padding=10)
peak_frame.pack(fill=tk.X, pady=(0, 10))
ttk.Label(
peak_frame,
text="先黑场切窗口后连续测亮度,直到回落或到达最长测量时间",
font=("", 9),
style="WarningState.TLabel",
).grid(row=0, column=0, columnspan=8, sticky=tk.W, pady=(0, 8))
self.ld_peak_window_size_var = tk.StringVar(value="10")
self.ld_peak_window_luminance_var = tk.StringVar(value="100")
self.ld_peak_duration_var = tk.StringVar(value="20")
self.ld_peak_sample_interval_var = tk.StringVar(value="0.3")
self.ld_peak_record_curve_var = tk.BooleanVar(value=True)
ttk.Label(peak_frame, text="窗口(%):").grid(row=1, column=0, sticky=tk.W, padx=(0, 4))
ttk.Entry(peak_frame, textvariable=self.ld_peak_window_size_var, width=8).grid(
row=1, column=1, sticky=tk.W, padx=(0, 10)
)
ttk.Label(peak_frame, text="窗口亮度(%):").grid(
row=1, column=2, sticky=tk.W, padx=(0, 4)
)
ttk.Entry(peak_frame, textvariable=self.ld_peak_window_luminance_var, width=8).grid(
row=1, column=3, sticky=tk.W, padx=(0, 10)
)
ttk.Label(peak_frame, text="连续时长(s):").grid(
row=1, column=4, sticky=tk.W, padx=(0, 4)
)
ttk.Entry(peak_frame, textvariable=self.ld_peak_duration_var, width=8).grid(
row=1, column=5, sticky=tk.W, padx=(0, 10)
)
ttk.Label(peak_frame, text="采样间隔(s):").grid(
row=1, column=6, sticky=tk.W, padx=(0, 4)
)
ttk.Entry(peak_frame, textvariable=self.ld_peak_sample_interval_var, width=8).grid(
row=1, column=7, sticky=tk.W
)
ttk.Checkbutton(
peak_frame,
text="记录曲线点到表格",
variable=self.ld_peak_record_curve_var,
bootstyle="round-toggle",
).grid(row=2, column=0, columnspan=3, sticky=tk.W, pady=(8, 0))
peak_btn_row = ttk.Frame(peak_frame)
peak_btn_row.grid(row=2, column=4, columnspan=4, sticky=tk.EW, pady=(8, 0))
self.ld_peak_start_btn = ttk.Button(
peak_btn_row,
text="开始峰值追踪",
command=self.start_ld_instant_peak_tracking,
bootstyle="warning",
width=14,
)
self.ld_peak_start_btn.pack(side=tk.LEFT, padx=(0, 5))
self.ld_peak_stop_btn = ttk.Button(
peak_btn_row,
text="停止",
command=self.stop_ld_instant_peak_tracking,
bootstyle="danger-outline",
width=10,
state="disabled",
)
self.ld_peak_stop_btn.pack(side=tk.LEFT)
# ==================== 5. CA410 采集按钮 ====================
measure_frame = ttk.LabelFrame(main_container, text="📊 CA410 测量", padding=10)
measure_frame.pack(fill=tk.X, pady=(0, 10))
@@ -159,7 +231,7 @@ def create_local_dimming_panel(self: "PQAutomationApp"):
)
self.ld_result_label.pack(side=tk.LEFT, padx=(10, 0))
# ==================== 5. 测试结果表格 ====================
# ==================== 6. 测试结果表格 ====================
result_frame = ttk.LabelFrame(main_container, text="📋 测试记录", padding=10)
result_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
@@ -191,7 +263,7 @@ def create_local_dimming_panel(self: "PQAutomationApp"):
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.ld_tree.configure(yscrollcommand=scrollbar.set)
# ==================== 6. 底部操作按钮 ====================
# ==================== 7. 底部操作按钮 ====================
bottom_frame = ttk.Frame(main_container)
bottom_frame.pack(fill=tk.X)
@@ -228,6 +300,7 @@ def create_local_dimming_panel(self: "PQAutomationApp"):
self.current_ld_percentage = None
self.current_ld_test_item = None
self.current_ld_pattern_label = None
self.ld_peak_tracking = False
def toggle_local_dimming_panel(self: "PQAutomationApp"):