修改SDR色准深色异常、修改保存结果深色模式异常
This commit is contained in:
@@ -1,14 +1,19 @@
|
||||
"""CIE 色度图底图渲染与缓存。
|
||||
"""
|
||||
CIE 色度图底图渲染与缓存(工业版)
|
||||
|
||||
将"重型图像渲染"(colour-science 的谱迹颜色填充)与"轻量框架数据层"
|
||||
(参考/实测三角形、标签、覆盖率)解耦。
|
||||
特点:
|
||||
- colour-science 谱迹渲染
|
||||
- numpy RGBA 缓存
|
||||
- 内存 + 磁盘缓存
|
||||
- 支持 light / dark UI
|
||||
- 启动预热
|
||||
- 线程安全
|
||||
|
||||
底图:
|
||||
- 仅在首次调用或缓存失效时通过 colour-science 渲染一次;
|
||||
- 渲染结果保存为 numpy RGBA 数组,同时落盘到 settings/cache/,
|
||||
下次启动直接 imread 加载,避免重新跑色彩科学计算。
|
||||
调用方式:
|
||||
|
||||
bg, bbox = get_cie1931_background(mode="dark")
|
||||
ax.imshow(bg, extent=bbox)
|
||||
|
||||
调用方在每次绘图时只需 `ax.imshow(bg, extent=bbox)`,再叠加自己的矢量层。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -20,91 +25,140 @@ from typing import Tuple
|
||||
|
||||
import numpy as np
|
||||
|
||||
# 谱迹底图分辨率(边长,单位像素)。1024 对于 14 inch 画布足够细腻,
|
||||
# 文件大小 ~1-2MB,单次渲染 ~0.5-1 s,缓存后毫秒级加载。
|
||||
# ----------------------------
|
||||
# 配置
|
||||
# ----------------------------
|
||||
|
||||
# 渲染分辨率
|
||||
_DIAGRAM_RES = 1024
|
||||
|
||||
# 缓存版本号:当渲染参数或风格调整时递增,强制重新生成。
|
||||
_CACHE_VERSION = "v1"
|
||||
# 缓存版本(风格变化时递增)
|
||||
_CACHE_VERSION = "v2"
|
||||
|
||||
_BBox = Tuple[float, float, float, float] # (xmin, xmax, ymin, ymax)
|
||||
# UI 颜色
|
||||
_DARK_BG = "#0f1115"
|
||||
_LIGHT_BG = "#ffffff"
|
||||
|
||||
_BBox = Tuple[float, float, float, float]
|
||||
|
||||
_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
|
||||
# ----------------------------
|
||||
|
||||
def _cache_dir() -> str:
|
||||
# 项目根目录通过本文件位置反推:app/plots/ -> 项目根
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
root = os.path.abspath(os.path.join(here, "..", ".."))
|
||||
|
||||
d = os.path.join(root, "settings", "cache")
|
||||
os.makedirs(d, exist_ok=True)
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def _cache_key(kind: str, bbox: _BBox) -> str:
|
||||
sig = f"{kind}|{bbox}|{_DIAGRAM_RES}|{_CACHE_VERSION}"
|
||||
h = hashlib.md5(sig.encode("utf-8")).hexdigest()[:10]
|
||||
return f"chromaticity_{kind}_{h}.npy"
|
||||
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]
|
||||
|
||||
return f"chromaticity_{kind}_{mode}_{h}.npy"
|
||||
|
||||
|
||||
def _cache_path(kind: str, bbox: _BBox) -> str:
|
||||
return os.path.join(_cache_dir(), _cache_key(kind, bbox))
|
||||
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) -> np.ndarray:
|
||||
"""通过 colour-science 离屏渲染谱迹底图,返回 RGBA float 数组。"""
|
||||
# 延迟导入:仅在缓存未命中时支付 colour.plotting 的加载开销。
|
||||
# ----------------------------
|
||||
# 渲染
|
||||
# ----------------------------
|
||||
|
||||
def _render_chromaticity(kind: str, bbox: _BBox, mode: str) -> np.ndarray:
|
||||
"""
|
||||
通过 colour-science 渲染 chromaticity 图。
|
||||
"""
|
||||
|
||||
import matplotlib
|
||||
|
||||
prev_backend = matplotlib.get_backend()
|
||||
|
||||
try:
|
||||
matplotlib.use("Agg", force=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import colour
|
||||
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
|
||||
|
||||
xmin, xmax, ymin, ymax = bbox
|
||||
|
||||
aspect = (xmax - xmin) / (ymax - ymin)
|
||||
|
||||
height = _DIAGRAM_RES
|
||||
width = int(round(height * aspect))
|
||||
|
||||
fig = plt.figure(figsize=(width / 100.0, height / 100.0), dpi=100)
|
||||
ax = fig.add_axes([0.0, 0.0, 1.0, 1.0])
|
||||
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)
|
||||
|
||||
if kind == "cie1931":
|
||||
|
||||
plot_chromaticity_diagram_CIE1931(
|
||||
axes=ax, show=False, title=False,
|
||||
tight_layout=False, transparent_background=True,
|
||||
axes=ax,
|
||||
show=False,
|
||||
title=False,
|
||||
tight_layout=False,
|
||||
bounding_box=bbox,
|
||||
transparent_background=False,
|
||||
)
|
||||
|
||||
elif kind == "cie1976":
|
||||
|
||||
plot_chromaticity_diagram_CIE1976UCS(
|
||||
axes=ax, show=False, title=False,
|
||||
tight_layout=False, transparent_background=True,
|
||||
axes=ax,
|
||||
show=False,
|
||||
title=False,
|
||||
tight_layout=False,
|
||||
bounding_box=bbox,
|
||||
transparent_background=False,
|
||||
)
|
||||
|
||||
else:
|
||||
plt.close(fig)
|
||||
raise ValueError(f"unknown diagram kind: {kind!r}")
|
||||
raise ValueError(f"unknown diagram kind: {kind}")
|
||||
|
||||
ax.set_xlim(xmin, xmax)
|
||||
ax.set_ylim(ymin, ymax)
|
||||
|
||||
ax.set_axis_off()
|
||||
ax.set_position([0.0, 0.0, 1.0, 1.0])
|
||||
ax.set_position([0, 0, 1, 1])
|
||||
|
||||
fig.canvas.draw()
|
||||
# 从 canvas 抓取 RGBA 数组
|
||||
|
||||
buf = np.asarray(fig.canvas.buffer_rgba()).copy()
|
||||
buf = np.flipud(buf)
|
||||
|
||||
plt.close(fig)
|
||||
|
||||
try:
|
||||
@@ -115,52 +169,107 @@ def _render_chromaticity(kind: str, bbox: _BBox) -> np.ndarray:
|
||||
return buf
|
||||
|
||||
|
||||
def _load_or_render(kind: str, bbox: _BBox) -> np.ndarray:
|
||||
key = _cache_key(kind, bbox)
|
||||
# ----------------------------
|
||||
# load / render
|
||||
# ----------------------------
|
||||
|
||||
def _load_or_render(kind: str, bbox: _BBox, mode: str) -> np.ndarray:
|
||||
|
||||
key = _cache_key(kind, bbox, mode)
|
||||
|
||||
with _lock:
|
||||
|
||||
if key in _memory_cache:
|
||||
return _memory_cache[key]
|
||||
|
||||
disk = _cache_path(kind, bbox)
|
||||
disk = _cache_path(kind, bbox, mode)
|
||||
|
||||
if os.path.isfile(disk):
|
||||
|
||||
try:
|
||||
arr = np.load(disk)
|
||||
|
||||
_memory_cache[key] = arr
|
||||
|
||||
return arr
|
||||
|
||||
except Exception:
|
||||
# 缓存损坏则重新渲染
|
||||
|
||||
try:
|
||||
os.remove(disk)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
arr = _render_chromaticity(kind, bbox)
|
||||
arr = _render_chromaticity(kind, bbox, mode)
|
||||
|
||||
_memory_cache[key] = arr
|
||||
|
||||
try:
|
||||
np.save(disk, arr)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return arr
|
||||
|
||||
|
||||
def get_cie1931_background() -> Tuple[np.ndarray, _BBox]:
|
||||
"""返回 (RGBA 数组, bbox),可直接 ax.imshow(arr, extent=[*bbox])。"""
|
||||
return _load_or_render("cie1931", _CIE1931_BBOX), _CIE1931_BBOX
|
||||
# ----------------------------
|
||||
# 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
|
||||
|
||||
|
||||
def get_cie1976_background() -> Tuple[np.ndarray, _BBox]:
|
||||
return _load_or_render("cie1976", _CIE1976_BBOX), _CIE1976_BBOX
|
||||
def get_cie1976_background(mode: str = "dark") -> Tuple[np.ndarray, _BBox]:
|
||||
|
||||
return _load_or_render("cie1976", _CIE1976_BBOX, mode), _CIE1976_BBOX
|
||||
|
||||
|
||||
# ----------------------------
|
||||
# cache control
|
||||
# ----------------------------
|
||||
|
||||
def clear_cache(*, disk: bool = False) -> None:
|
||||
"""清空内存缓存(可选连同磁盘)。供调试/样式调整时使用。"""
|
||||
"""
|
||||
清空缓存
|
||||
"""
|
||||
|
||||
with _lock:
|
||||
|
||||
_memory_cache.clear()
|
||||
|
||||
if disk:
|
||||
|
||||
d = _cache_dir()
|
||||
|
||||
for name in os.listdir(d):
|
||||
if name.startswith("chromaticity_") and name.endswith(".npy"):
|
||||
|
||||
if name.startswith("chromaticity_"):
|
||||
|
||||
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)
|
||||
Reference in New Issue
Block a user