188 lines
5.1 KiB
Python
188 lines
5.1 KiB
Python
|
|
"""离线色准图 Demo。
|
|||
|
|
|
|||
|
|
运行后会在 tools/demo_outputs/ 下生成一张 PNG,
|
|||
|
|
用于在没有 UCD 设备时预览当前色准图表的 Calman 风格布局。
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
import argparse
|
|||
|
|
import math
|
|||
|
|
import sys
|
|||
|
|
from pathlib import Path
|
|||
|
|
|
|||
|
|
import matplotlib
|
|||
|
|
|
|||
|
|
matplotlib.use("Agg")
|
|||
|
|
|
|||
|
|
import matplotlib.pyplot as plt
|
|||
|
|
import numpy as np
|
|||
|
|
|
|||
|
|
plt.rcParams["font.family"] = ["sans-serif"]
|
|||
|
|
plt.rcParams["font.sans-serif"] = ["Microsoft YaHei", "SimHei", "DejaVu Sans"]
|
|||
|
|
plt.rcParams["axes.unicode_minus"] = False
|
|||
|
|
|
|||
|
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|||
|
|
if str(REPO_ROOT) not in sys.path:
|
|||
|
|
sys.path.insert(0, str(REPO_ROOT))
|
|||
|
|
|
|||
|
|
from app.plots.plot_accuracy import plot_accuracy
|
|||
|
|
from app.tests.color_accuracy import (
|
|||
|
|
calculate_delta_e_2000,
|
|||
|
|
get_accuracy_color_standards,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
COLOR_NAMES = [
|
|||
|
|
"White",
|
|||
|
|
"Gray 80",
|
|||
|
|
"Gray 65",
|
|||
|
|
"Gray 50",
|
|||
|
|
"Gray 35",
|
|||
|
|
"Dark Skin",
|
|||
|
|
"Light Skin",
|
|||
|
|
"Blue Sky",
|
|||
|
|
"Foliage",
|
|||
|
|
"Blue Flower",
|
|||
|
|
"Bluish Green",
|
|||
|
|
"Orange",
|
|||
|
|
"Purplish Blue",
|
|||
|
|
"Moderate Red",
|
|||
|
|
"Purple",
|
|||
|
|
"Yellow Green",
|
|||
|
|
"Orange Yellow",
|
|||
|
|
"Blue (Legacy)",
|
|||
|
|
"Green (Legacy)",
|
|||
|
|
"Red (Legacy)",
|
|||
|
|
"Yellow (Legacy)",
|
|||
|
|
"Magenta (Legacy)",
|
|||
|
|
"Cyan (Legacy)",
|
|||
|
|
"100% Red",
|
|||
|
|
"100% Green",
|
|||
|
|
"100% Blue",
|
|||
|
|
"100% Cyan",
|
|||
|
|
"100% Magenta",
|
|||
|
|
"100% Yellow",
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
|
|||
|
|
class _DummyNotebook:
|
|||
|
|
def select(self, *_args, **_kwargs):
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
|
|||
|
|
class _DummyCanvas:
|
|||
|
|
def draw(self):
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
|
|||
|
|
class _DemoApp:
|
|||
|
|
def __init__(self, fig):
|
|||
|
|
self.accuracy_fig = fig
|
|||
|
|
self.accuracy_canvas = _DummyCanvas()
|
|||
|
|
self.chart_notebook = _DummyNotebook()
|
|||
|
|
self.accuracy_chart_frame = object()
|
|||
|
|
|
|||
|
|
def get_test_type_name(self, test_type):
|
|||
|
|
mapping = {
|
|||
|
|
"sdr_movie": "SDR Movie",
|
|||
|
|
"hdr_movie": "HDR Movie",
|
|||
|
|
"screen_module": "屏模组",
|
|||
|
|
}
|
|||
|
|
return mapping.get(test_type, str(test_type))
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _build_demo_data(test_type: str = "sdr_movie"):
|
|||
|
|
standards = get_accuracy_color_standards(test_type)
|
|||
|
|
rng = np.random.default_rng(20260527)
|
|||
|
|
|
|||
|
|
measured = []
|
|||
|
|
color_patches = []
|
|||
|
|
delta_e_values = []
|
|||
|
|
|
|||
|
|
for idx, name in enumerate(COLOR_NAMES):
|
|||
|
|
sx, sy = standards[name]
|
|||
|
|
|
|||
|
|
# 构造一些“看起来像真实测量”的偏移:
|
|||
|
|
# 大部分点轻微偏移,少数点更明显,便于看出方向和等级差异。
|
|||
|
|
if idx < 5:
|
|||
|
|
offset_scale = 0.0012
|
|||
|
|
elif idx < 23:
|
|||
|
|
offset_scale = 0.0028
|
|||
|
|
else:
|
|||
|
|
offset_scale = 0.0045
|
|||
|
|
|
|||
|
|
angle = rng.uniform(0, 2 * math.pi)
|
|||
|
|
radius = offset_scale * (0.55 + 0.85 * rng.random())
|
|||
|
|
dx = math.cos(angle) * radius
|
|||
|
|
dy = math.sin(angle) * radius
|
|||
|
|
|
|||
|
|
# 为了让图上连线不完全随机,给部分饱和色再加一点定向偏移。
|
|||
|
|
if idx >= 23:
|
|||
|
|
dx += 0.002 * (1 if idx % 2 == 0 else -1)
|
|||
|
|
dy += 0.0015 * (1 if idx % 3 == 0 else -1)
|
|||
|
|
|
|||
|
|
mx = min(max(sx + dx, 0.0), 0.8)
|
|||
|
|
my = min(max(sy + dy, 0.0), 0.9)
|
|||
|
|
|
|||
|
|
# 亮度也做一点微小变化,避免所有点完全同一层。
|
|||
|
|
measured_lv = 70.0 + rng.normal(0, 4.0)
|
|||
|
|
measured_lv = max(measured_lv, 1.0)
|
|||
|
|
|
|||
|
|
delta_e = calculate_delta_e_2000(mx, my, measured_lv, sx, sy)
|
|||
|
|
|
|||
|
|
measured.append((mx, my, measured_lv))
|
|||
|
|
color_patches.append(name)
|
|||
|
|
delta_e_values.append(delta_e)
|
|||
|
|
|
|||
|
|
avg_delta_e = float(np.mean(delta_e_values))
|
|||
|
|
max_delta_e = float(np.max(delta_e_values))
|
|||
|
|
min_delta_e = float(np.min(delta_e_values))
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"color_patches": color_patches,
|
|||
|
|
"delta_e_values": delta_e_values,
|
|||
|
|
"color_measurements": measured,
|
|||
|
|
"avg_delta_e": avg_delta_e,
|
|||
|
|
"max_delta_e": max_delta_e,
|
|||
|
|
"min_delta_e": min_delta_e,
|
|||
|
|
"excellent_count": sum(1 for value in delta_e_values if value < 3),
|
|||
|
|
"good_count": sum(1 for value in delta_e_values if 3 <= value < 5),
|
|||
|
|
"poor_count": sum(1 for value in delta_e_values if value >= 5),
|
|||
|
|
"avg_delta_e_gray": float(np.mean(delta_e_values[0:5])),
|
|||
|
|
"avg_delta_e_colorchecker": float(np.mean(delta_e_values[5:23])),
|
|||
|
|
"avg_delta_e_saturated": float(np.mean(delta_e_values[23:29])),
|
|||
|
|
"target_gamma": 2.2,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def main():
|
|||
|
|
parser = argparse.ArgumentParser(description="Generate an offline color accuracy demo PNG.")
|
|||
|
|
parser.add_argument(
|
|||
|
|
"--output",
|
|||
|
|
type=Path,
|
|||
|
|
default=Path(__file__).resolve().parent / "demo_outputs" / "accuracy_demo.png",
|
|||
|
|
help="Output PNG path.",
|
|||
|
|
)
|
|||
|
|
parser.add_argument(
|
|||
|
|
"--test-type",
|
|||
|
|
choices=["sdr_movie", "hdr_movie", "screen_module"],
|
|||
|
|
default="sdr_movie",
|
|||
|
|
help="Test type used for the title and standard color set.",
|
|||
|
|
)
|
|||
|
|
args = parser.parse_args()
|
|||
|
|
|
|||
|
|
args.output.parent.mkdir(parents=True, exist_ok=True)
|
|||
|
|
|
|||
|
|
fig = plt.Figure(figsize=(14, 8), dpi=120, tight_layout=False)
|
|||
|
|
app = _DemoApp(fig)
|
|||
|
|
accuracy_data = _build_demo_data(args.test_type)
|
|||
|
|
|
|||
|
|
plot_accuracy(app, accuracy_data, args.test_type)
|
|||
|
|
fig.savefig(args.output, dpi=220)
|
|||
|
|
|
|||
|
|
print(f"Saved demo image to: {args.output}")
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
main()
|