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() |