Files
pqAutomationApp/app/plots/gamut_background.py

275 lines
5.5 KiB
Python
Raw Normal View History

"""
CIE 色度图底图渲染与缓存工业版
特点
- colour-science 谱迹渲染
- numpy RGBA 缓存
- 内存 + 磁盘缓存
- 支持 light / dark UI
- 启动预热
- 线程安全
2026-05-18 15:57:11 +08:00
调用方式
2026-05-18 15:57:11 +08:00
bg, bbox = get_cie1931_background(mode="dark")
ax.imshow(bg, extent=bbox)
2026-05-18 15:57:11 +08:00
"""
from __future__ import annotations
import hashlib
import os
import threading
from typing import Tuple
import numpy as np
# ----------------------------
# 配置
# ----------------------------
# 渲染分辨率
2026-05-18 15:57:11 +08:00
_DIAGRAM_RES = 1024
# 缓存版本(风格变化时递增)
_CACHE_VERSION = "v2"
# UI 颜色
_DARK_BG = "#0f1115"
_LIGHT_BG = "#ffffff"
2026-05-18 15:57:11 +08:00
_BBox = Tuple[float, float, float, float]
2026-05-18 15:57:11 +08:00
_CIE1931_BBOX: _BBox = (0.0, 0.8, 0.0, 0.9)
_CIE1976_BBOX: _BBox = (0.0, 0.65, 0.0, 0.6)
_memory_cache: dict[str, np.ndarray] = {}
_lock = threading.Lock()
# ----------------------------
# cache path
# ----------------------------
2026-05-18 15:57:11 +08:00
def _cache_dir() -> str:
here = os.path.dirname(os.path.abspath(__file__))
root = os.path.abspath(os.path.join(here, "..", ".."))
2026-05-18 15:57:11 +08:00
d = os.path.join(root, "settings", "cache")
os.makedirs(d, exist_ok=True)
2026-05-18 15:57:11 +08:00
return d
def _cache_key(kind: str, bbox: _BBox, mode: str) -> str:
sig = f"{kind}|{bbox}|{mode}|{_DIAGRAM_RES}|{_CACHE_VERSION}"
h = hashlib.md5(sig.encode()).hexdigest()[:10]
2026-05-18 15:57:11 +08:00
return f"chromaticity_{kind}_{mode}_{h}.npy"
2026-05-18 15:57:11 +08:00
def _cache_path(kind: str, bbox: _BBox, mode: str) -> str:
return os.path.join(_cache_dir(), _cache_key(kind, bbox, mode))
# ----------------------------
# 渲染
# ----------------------------
def _render_chromaticity(kind: str, bbox: _BBox, mode: str) -> np.ndarray:
"""
通过 colour-science 渲染 chromaticity
"""
2026-05-18 15:57:11 +08:00
import matplotlib
2026-05-18 15:57:11 +08:00
prev_backend = matplotlib.get_backend()
2026-05-18 15:57:11 +08:00
try:
matplotlib.use("Agg", force=True)
except Exception:
pass
import matplotlib.pyplot as plt
import colour
2026-05-18 15:57:11 +08:00
from colour.plotting import (
plot_chromaticity_diagram_CIE1931,
plot_chromaticity_diagram_CIE1976UCS,
)
if mode == "dark":
colour.plotting.colour_style("dark")
bg_color = _DARK_BG
else:
colour.plotting.colour_style("light")
bg_color = _LIGHT_BG
2026-05-18 15:57:11 +08:00
xmin, xmax, ymin, ymax = bbox
2026-05-18 15:57:11 +08:00
aspect = (xmax - xmin) / (ymax - ymin)
2026-05-18 15:57:11 +08:00
height = _DIAGRAM_RES
width = int(round(height * aspect))
fig = plt.figure(
figsize=(width / 100.0, height / 100.0),
dpi=100
)
fig.patch.set_facecolor(bg_color)
ax = fig.add_axes([0, 0, 1, 1])
ax.set_facecolor(bg_color)
2026-05-18 15:57:11 +08:00
if kind == "cie1931":
2026-05-18 15:57:11 +08:00
plot_chromaticity_diagram_CIE1931(
axes=ax,
show=False,
title=False,
tight_layout=False,
2026-05-18 15:57:11 +08:00
bounding_box=bbox,
transparent_background=False,
2026-05-18 15:57:11 +08:00
)
2026-05-18 15:57:11 +08:00
elif kind == "cie1976":
2026-05-18 15:57:11 +08:00
plot_chromaticity_diagram_CIE1976UCS(
axes=ax,
show=False,
title=False,
tight_layout=False,
2026-05-18 15:57:11 +08:00
bounding_box=bbox,
transparent_background=False,
2026-05-18 15:57:11 +08:00
)
2026-05-18 15:57:11 +08:00
else:
plt.close(fig)
raise ValueError(f"unknown diagram kind: {kind}")
2026-05-18 15:57:11 +08:00
ax.set_xlim(xmin, xmax)
ax.set_ylim(ymin, ymax)
2026-05-18 15:57:11 +08:00
ax.set_axis_off()
ax.set_position([0, 0, 1, 1])
2026-05-18 15:57:11 +08:00
fig.canvas.draw()
2026-05-18 15:57:11 +08:00
buf = np.asarray(fig.canvas.buffer_rgba()).copy()
buf = np.flipud(buf)
2026-05-18 15:57:11 +08:00
plt.close(fig)
try:
matplotlib.use(prev_backend, force=True)
except Exception:
pass
return buf
# ----------------------------
# load / render
# ----------------------------
def _load_or_render(kind: str, bbox: _BBox, mode: str) -> np.ndarray:
key = _cache_key(kind, bbox, mode)
2026-05-18 15:57:11 +08:00
with _lock:
2026-05-18 15:57:11 +08:00
if key in _memory_cache:
return _memory_cache[key]
disk = _cache_path(kind, bbox, mode)
2026-05-18 15:57:11 +08:00
if os.path.isfile(disk):
2026-05-18 15:57:11 +08:00
try:
arr = np.load(disk)
2026-05-18 15:57:11 +08:00
_memory_cache[key] = arr
2026-05-18 15:57:11 +08:00
return arr
2026-05-18 15:57:11 +08:00
except Exception:
2026-05-18 15:57:11 +08:00
try:
os.remove(disk)
except OSError:
pass
arr = _render_chromaticity(kind, bbox, mode)
2026-05-18 15:57:11 +08:00
_memory_cache[key] = arr
2026-05-18 15:57:11 +08:00
try:
np.save(disk, arr)
except Exception:
pass
2026-05-18 15:57:11 +08:00
return arr
# ----------------------------
# public API
# ----------------------------
def get_cie1931_background(mode: str = "dark") -> Tuple[np.ndarray, _BBox]:
"""
获取 CIE1931 背景图
mode:
"dark"
"light"
"""
return _load_or_render("cie1931", _CIE1931_BBOX, mode), _CIE1931_BBOX
2026-05-18 15:57:11 +08:00
def get_cie1976_background(mode: str = "dark") -> Tuple[np.ndarray, _BBox]:
2026-05-18 15:57:11 +08:00
return _load_or_render("cie1976", _CIE1976_BBOX, mode), _CIE1976_BBOX
# ----------------------------
# cache control
# ----------------------------
2026-05-18 15:57:11 +08:00
def clear_cache(*, disk: bool = False) -> None:
"""
清空缓存
"""
2026-05-18 15:57:11 +08:00
with _lock:
2026-05-18 15:57:11 +08:00
_memory_cache.clear()
2026-05-18 15:57:11 +08:00
if disk:
2026-05-18 15:57:11 +08:00
d = _cache_dir()
2026-05-18 15:57:11 +08:00
for name in os.listdir(d):
if name.startswith("chromaticity_"):
2026-05-18 15:57:11 +08:00
try:
os.remove(os.path.join(d, name))
except OSError:
pass
# ----------------------------
# warmup
# ----------------------------
def warmup_cache(mode: str = "dark") -> None:
"""
启动预热缓存
可在软件启动时调用避免首次绘图卡顿
"""
get_cie1931_background(mode)
get_cie1976_background(mode)