修改calman灰阶点击异常、修改色准结果显示异常
This commit is contained in:
@@ -293,62 +293,107 @@ def _call_pqtest_upload(file_path: str, timeout: float = API_UPLOAD_TIMEOUT, aut
|
||||
iw, ih = img.size
|
||||
except Exception as exc:
|
||||
raise ValueError(f"无法读取图片: {exc}") from exc
|
||||
|
||||
# 检查大小,如需则缩放
|
||||
|
||||
size = os.path.getsize(file_path)
|
||||
needs_resize = (iw > UPLOAD_MAX_PIXELS or ih > UPLOAD_MAX_PIXELS or size > UPLOAD_MAX_BYTES)
|
||||
|
||||
file_stem = os.path.splitext(os.path.basename(file_path))[0]
|
||||
upload_ext = ext
|
||||
mime = mimetypes.guess_type(file_path)[0] or "application/octet-stream"
|
||||
|
||||
if needs_resize:
|
||||
if not auto_resize:
|
||||
if iw > UPLOAD_MAX_PIXELS or ih > UPLOAD_MAX_PIXELS:
|
||||
raise ValueError(f"分辨率超过 4096×4096(当前 {iw}×{ih})")
|
||||
else:
|
||||
raise ValueError(f"图片超过 10MB 限制(当前 {size/1024/1024:.2f}MB)")
|
||||
|
||||
# 自动缩放:等比例缩放至 4096×4096 以内
|
||||
logger.info("[AIImage][UPLOAD] 自动缩放 %dx%d (%.1fMB) 至 ≤4096×4096",
|
||||
iw, ih, size/1024/1024)
|
||||
scale = min(UPLOAD_MAX_PIXELS / iw, UPLOAD_MAX_PIXELS / ih, 1.0)
|
||||
new_w, new_h = max(1, int(iw * scale)), max(1, int(ih * scale))
|
||||
|
||||
raise ValueError(f"图片超过 10MB 限制(当前 {size/1024/1024:.2f}MB)")
|
||||
|
||||
logger.info(
|
||||
"[AIImage][UPLOAD] 自动处理超限图片 %dx%d (%.2fMB)",
|
||||
iw,
|
||||
ih,
|
||||
size / 1024 / 1024,
|
||||
)
|
||||
|
||||
with Image.open(file_path) as img:
|
||||
img_resized = img.resize((new_w, new_h), Image.LANCZOS)
|
||||
# 重压至 10MB 以下
|
||||
# 首先尝试原格式
|
||||
tmp_io = BytesIO()
|
||||
fmt = "PNG" if ext == ".png" else "JPEG"
|
||||
save_kw = {"format": fmt}
|
||||
img_resized.save(tmp_io, **save_kw)
|
||||
tmp_bytes = tmp_io.getvalue()
|
||||
|
||||
if len(tmp_bytes) <= UPLOAD_MAX_BYTES:
|
||||
file_bytes = tmp_bytes
|
||||
working = img.copy()
|
||||
|
||||
# 先做一次分辨率约束,避免后续压缩开销过大。
|
||||
scale = min(UPLOAD_MAX_PIXELS / max(1, working.width), UPLOAD_MAX_PIXELS / max(1, working.height), 1.0)
|
||||
if scale < 1.0:
|
||||
working = working.resize(
|
||||
(max(1, int(working.width * scale)), max(1, int(working.height * scale))),
|
||||
Image.LANCZOS,
|
||||
)
|
||||
|
||||
best_bytes = b""
|
||||
best_mime = mime
|
||||
best_ext = upload_ext
|
||||
|
||||
# 第一优先:保持原格式。
|
||||
try:
|
||||
raw_io = BytesIO()
|
||||
if ext == ".png":
|
||||
working.save(raw_io, format="PNG", optimize=True)
|
||||
raw_mime, raw_ext = "image/png", ".png"
|
||||
else:
|
||||
# 原格式太大,转换为 JPEG 并压缩
|
||||
logger.info("[AIImage][UPLOAD] 原格式超过限制,转为 JPEG 并压缩")
|
||||
quality = 95
|
||||
while quality >= 50:
|
||||
tmp_io = BytesIO()
|
||||
img_resized.save(tmp_io, format="JPEG", quality=quality, optimize=True)
|
||||
tmp_bytes = tmp_io.getvalue()
|
||||
if len(tmp_bytes) <= UPLOAD_MAX_BYTES:
|
||||
file_bytes = tmp_bytes
|
||||
rgb = working.convert("RGB") if working.mode not in {"RGB", "L"} else working
|
||||
rgb.save(raw_io, format="JPEG", quality=95, optimize=True)
|
||||
raw_mime, raw_ext = "image/jpeg", ".jpg"
|
||||
best_bytes = raw_io.getvalue()
|
||||
best_mime = raw_mime
|
||||
best_ext = raw_ext
|
||||
except Exception as exc:
|
||||
logger.warning("[AIImage][UPLOAD] 原格式编码失败,准备转 JPEG: %s", exc)
|
||||
|
||||
# 仍超限时,转 JPEG + 渐进压缩;如仍超限则继续降分辨率。
|
||||
if len(best_bytes) > UPLOAD_MAX_BYTES:
|
||||
if best_ext != ".jpg":
|
||||
logger.info("[AIImage][UPLOAD] 原格式仍超限,切换 JPEG 压缩")
|
||||
working_jpg = working.convert("RGB") if working.mode != "RGB" else working
|
||||
while True:
|
||||
compressed = b""
|
||||
for q in (95, 90, 85, 80, 75, 70, 65, 60, 55, 50):
|
||||
tmp = BytesIO()
|
||||
working_jpg.save(tmp, format="JPEG", quality=q, optimize=True)
|
||||
data = tmp.getvalue()
|
||||
compressed = data
|
||||
if len(data) <= UPLOAD_MAX_BYTES:
|
||||
break
|
||||
quality -= 5
|
||||
else:
|
||||
# 即使 quality=50 还是太大,也用这个版本上传(后端会处理)
|
||||
file_bytes = tmp_bytes
|
||||
|
||||
logger.info("[AIImage][UPLOAD] 缩放完成 %dx%d (%.1fMB)",
|
||||
new_w, new_h, len(file_bytes)/1024/1024)
|
||||
iw, ih = new_w, new_h
|
||||
best_bytes = compressed
|
||||
best_mime = "image/jpeg"
|
||||
best_ext = ".jpg"
|
||||
if len(best_bytes) <= UPLOAD_MAX_BYTES:
|
||||
break
|
||||
|
||||
next_w = max(256, int(working_jpg.width * 0.9))
|
||||
next_h = max(256, int(working_jpg.height * 0.9))
|
||||
if next_w == working_jpg.width and next_h == working_jpg.height:
|
||||
break
|
||||
if next_w <= 256 or next_h <= 256:
|
||||
break
|
||||
working_jpg = working_jpg.resize((next_w, next_h), Image.LANCZOS)
|
||||
|
||||
if len(best_bytes) > UPLOAD_MAX_BYTES:
|
||||
raise ValueError(
|
||||
f"自动压缩后仍超过 10MB(当前 {len(best_bytes)/1024/1024:.2f}MB),请更换图片"
|
||||
)
|
||||
|
||||
file_bytes = best_bytes
|
||||
mime = best_mime
|
||||
upload_ext = best_ext
|
||||
iw, ih = working.width, working.height
|
||||
logger.info(
|
||||
"[AIImage][UPLOAD] 自动处理完成 %dx%d %.2fMB (%s)",
|
||||
iw,
|
||||
ih,
|
||||
len(file_bytes) / 1024 / 1024,
|
||||
mime,
|
||||
)
|
||||
else:
|
||||
with open(file_path, "rb") as f:
|
||||
file_bytes = f.read()
|
||||
|
||||
mime = mimetypes.guess_type(file_path)[0] or "application/octet-stream"
|
||||
filename = f"{file_stem}{upload_ext}"
|
||||
boundary = "----pqAuto" + uuid.uuid4().hex
|
||||
filename = os.path.basename(file_path)
|
||||
crlf = b"\r\n"
|
||||
body = b"".join([
|
||||
b"--", boundary.encode("ascii"), crlf,
|
||||
|
||||
@@ -21,8 +21,23 @@ class PatternService:
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
||||
def _build_apply_config_error(self, test_type):
|
||||
timing = self.app.config.current_test_types.get(test_type, {}).get("timing", "-")
|
||||
detail = ""
|
||||
try:
|
||||
ctrl = getattr(self.app.signal_service.device, "raw_controller", None)
|
||||
if ctrl is not None:
|
||||
d = getattr(ctrl, "last_error", None)
|
||||
if d:
|
||||
detail = f", detail={d}"
|
||||
except Exception:
|
||||
pass
|
||||
return f"UCD profile apply_config failed for {test_type}, timing={timing}{detail}"
|
||||
|
||||
def prepare_session(self, mode, *, test_type=None, log_details=False):
|
||||
test_type = test_type or self.app.config.current_test_type
|
||||
if hasattr(self.app.config, "set_current_test_type"):
|
||||
self.app.config.set_current_test_type(test_type)
|
||||
if not self.app.config.set_current_pattern(mode):
|
||||
raise ValueError(f"未知的图案模式: {mode}")
|
||||
|
||||
@@ -64,7 +79,8 @@ class PatternService:
|
||||
("Timing", self.app.config.current_test_types[test_type]["timing"]),
|
||||
]:
|
||||
self._log(f" {label}: {value}", "info")
|
||||
self.app.signal_service.apply_config(active_config)
|
||||
if not self.app.signal_service.apply_config(active_config):
|
||||
raise RuntimeError(self._build_apply_config_error(test_type))
|
||||
success = self.app.signal_service.update_signal_format(
|
||||
color_space=color_space,
|
||||
data_range=data_range,
|
||||
@@ -97,7 +113,10 @@ class PatternService:
|
||||
active_config = self.app.config.get_temp_config_with_converted_params(
|
||||
mode=mode, converted_params=converted_params
|
||||
)
|
||||
self.app.signal_service.apply_config(active_config)
|
||||
if hasattr(active_config, "set_current_test_type"):
|
||||
active_config.set_current_test_type(test_type)
|
||||
if not self.app.signal_service.apply_config(active_config):
|
||||
raise RuntimeError(self._build_apply_config_error(test_type))
|
||||
success = self.app.signal_service.update_signal_format(
|
||||
color_space=self.app.sdr_color_space_var.get(),
|
||||
data_range=data_range,
|
||||
@@ -129,7 +148,10 @@ class PatternService:
|
||||
active_config = self.app.config.get_temp_config_with_converted_params(
|
||||
mode=mode, converted_params=converted_params
|
||||
)
|
||||
self.app.signal_service.apply_config(active_config)
|
||||
if hasattr(active_config, "set_current_test_type"):
|
||||
active_config.set_current_test_type(test_type)
|
||||
if not self.app.signal_service.apply_config(active_config):
|
||||
raise RuntimeError(self._build_apply_config_error(test_type))
|
||||
success = self.app.signal_service.update_signal_format(
|
||||
color_space=self.app.hdr_color_space_var.get(),
|
||||
data_range=data_range,
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import contextmanager
|
||||
import logging
|
||||
import sys
|
||||
import threading
|
||||
|
||||
from app.ucd_domain import (
|
||||
@@ -34,6 +36,10 @@ from drivers.ucd_driver import IUcdDevice
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
_LOCK_TIMEOUT_SECONDS = 8.0
|
||||
_DEBUG_LOCK_TIMEOUT_SECONDS = 0.3
|
||||
_PATTERN_LOCK_TIMEOUT_SECONDS = 0.8
|
||||
|
||||
|
||||
# ─── 视图字符串 → 值对象 转换工具 ────────────────────────────────
|
||||
|
||||
@@ -85,6 +91,53 @@ class SignalService:
|
||||
self._dev = device
|
||||
self._bus = bus
|
||||
self._lock = threading.RLock()
|
||||
self._lock_owner_tid: int | None = None
|
||||
self._lock_owner_name: str | None = None
|
||||
|
||||
def _effective_lock_timeout(self, timeout_override: float | None = None) -> float:
|
||||
"""调试模式下缩短锁等待,避免单步时表现为 UI 长时间无响应。"""
|
||||
if timeout_override is not None:
|
||||
return timeout_override
|
||||
if sys.gettrace() is not None:
|
||||
return _DEBUG_LOCK_TIMEOUT_SECONDS
|
||||
return _LOCK_TIMEOUT_SECONDS
|
||||
|
||||
@contextmanager
|
||||
def _acquire_service_lock(self, op_name: str, timeout_override: float | None = None):
|
||||
timeout = self._effective_lock_timeout(timeout_override)
|
||||
current = threading.current_thread()
|
||||
log.info(
|
||||
"SignalService.%s acquiring lock timeout=%.1fs tid=%s thread=%s owner_tid=%s owner_thread=%s",
|
||||
op_name,
|
||||
timeout,
|
||||
threading.get_ident(),
|
||||
current.name,
|
||||
self._lock_owner_tid,
|
||||
self._lock_owner_name,
|
||||
)
|
||||
acquired = self._lock.acquire(timeout=timeout)
|
||||
if not acquired:
|
||||
raise UcdError(
|
||||
"UCD busy: lock timeout in "
|
||||
f"SignalService.{op_name} ({timeout:.1f}s), "
|
||||
f"owner_tid={self._lock_owner_tid}, owner_thread={self._lock_owner_name}"
|
||||
)
|
||||
prev_owner_tid = self._lock_owner_tid
|
||||
prev_owner_name = self._lock_owner_name
|
||||
self._lock_owner_tid = threading.get_ident()
|
||||
self._lock_owner_name = current.name
|
||||
log.info(
|
||||
"SignalService.%s lock acquired tid=%s thread=%s",
|
||||
op_name,
|
||||
self._lock_owner_tid,
|
||||
self._lock_owner_name,
|
||||
)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
self._lock_owner_tid = prev_owner_tid
|
||||
self._lock_owner_name = prev_owner_name
|
||||
self._lock.release()
|
||||
|
||||
# -- 高层接口 ------------------------------------------------
|
||||
|
||||
@@ -100,7 +153,7 @@ class SignalService:
|
||||
Returns:
|
||||
``format_changed``——本次相对上一次 :meth:`apply` 是否变化。
|
||||
"""
|
||||
with self._lock:
|
||||
with self._acquire_service_lock("apply"):
|
||||
log.info(
|
||||
"SignalService.apply signal=%s timing=%s pattern=%s",
|
||||
signal,
|
||||
@@ -114,7 +167,7 @@ class SignalService:
|
||||
|
||||
def send_pattern(self, pattern: PatternSpec) -> None:
|
||||
"""在已 configure 的信号上仅更新图案后 apply。"""
|
||||
with self._lock:
|
||||
with self._acquire_service_lock("send_pattern", _PATTERN_LOCK_TIMEOUT_SECONDS):
|
||||
log.info("SignalService.send_pattern pattern=%s", pattern.kind.value)
|
||||
self._dev.set_pattern(pattern)
|
||||
self._dev.apply()
|
||||
@@ -155,7 +208,7 @@ class SignalService:
|
||||
ctrl = getattr(self._dev, "raw_controller", None)
|
||||
if ctrl is None:
|
||||
raise UcdError("update_signal_format 暂仅支持 UCD323Device")
|
||||
with self._lock:
|
||||
with self._acquire_service_lock("update_signal_format"):
|
||||
return bool(
|
||||
ctrl.apply_signal_format(
|
||||
color_space=color_space,
|
||||
@@ -193,7 +246,7 @@ class SignalService:
|
||||
ctrl = getattr(self._dev, "raw_controller", None)
|
||||
if ctrl is None:
|
||||
raise UcdError("apply_config 暂仅支持 UCD323Device")
|
||||
with self._lock:
|
||||
with self._acquire_service_lock("apply_config"):
|
||||
return bool(ctrl.set_ucd_params(config))
|
||||
|
||||
def send_pattern_params(self, params) -> bool:
|
||||
@@ -201,7 +254,7 @@ class SignalService:
|
||||
ctrl = getattr(self._dev, "raw_controller", None)
|
||||
if ctrl is None:
|
||||
raise UcdError("send_pattern_params 暂仅支持 UCD323Device")
|
||||
with self._lock:
|
||||
with self._acquire_service_lock("send_pattern_params"):
|
||||
return bool(ctrl.send_current_pattern_params(params))
|
||||
|
||||
def apply_and_run(self, config, pattern_params) -> bool:
|
||||
@@ -212,7 +265,7 @@ class SignalService:
|
||||
ctrl = getattr(self._dev, "raw_controller", None)
|
||||
if ctrl is None:
|
||||
raise UcdError("apply_and_run 暂仅支持 UCD323Device")
|
||||
with self._lock:
|
||||
with self._acquire_service_lock("apply_and_run"):
|
||||
if not ctrl.set_ucd_params(config):
|
||||
return False
|
||||
if not ctrl.set_pattern(ctrl.current_pattern, pattern_params):
|
||||
|
||||
Reference in New Issue
Block a user