SXXXXXXX_ControlPanel/VideoReceiverSFP/core/mfd_state.py
2026-01-13 14:21:21 +01:00

101 lines
4.7 KiB
Python

"""MFD parameters state management for VideoReceiverSFP.
Self-contained state manager for MFD category parameters matching ControlPanel behavior.
"""
import logging
import numpy as np
from typing import Dict, Any
class MfdState:
"""Manages MFD parameters (categories, intensities, colors, raw_map) and LUT generation."""
def __init__(self):
self.mfd_params: Dict[str, Any] = self._initialize_mfd_params()
self.mfd_lut: np.ndarray = np.zeros((256, 3), dtype=np.uint8)
self._rebuild_lut()
def _initialize_mfd_params(self) -> Dict[str, Any]:
"""Initialize MFD parameters matching ControlPanel defaults."""
params = {
"categories": {
"Occlusion": {"color": (0, 0, 0), "intensity": 255, "pixels": [0, 1]},
"Cat A": {"color": (255, 255, 255), "intensity": 255, "pixels": [2]},
"Cat B": {"color": (255, 255, 255), "intensity": 255, "pixels": [3, 18]},
"Cat C": {"color": (255, 255, 255), "intensity": 255, "pixels": [4, 5, 6, 16]},
"Cat C1": {"color": (255, 255, 255), "intensity": 255, "pixels": [7, 8, 9]},
"Cat C2": {"color": (255, 255, 255), "intensity": 255, "pixels": [10, 11, 12]},
"Cat C3": {"color": (255, 255, 255), "intensity": 255, "pixels": [13, 14, 15]},
"Reserved": {"color": (0, 0, 0), "intensity": 255, "pixels": list(range(17, 32))},
},
"raw_map_intensity": 255,
}
# Create reverse mapping pixel_to_category
params["pixel_to_category"] = {
p: cat for cat, data in params["categories"].items() for p in data["pixels"]
}
return params
def update_category_intensity(self, category_name: str, intensity_value: int) -> bool:
"""Update intensity for a category and rebuild LUT. Returns True if updated."""
if category_name not in self.mfd_params["categories"]:
logging.warning(f"[MfdState] Unknown category: {category_name}")
return False
clamped = int(np.clip(intensity_value, 0, 255))
self.mfd_params["categories"][category_name]["intensity"] = clamped
self._rebuild_lut()
return True
def update_category_color(self, category_name: str, bgr_tuple: tuple) -> bool:
"""Update color (BGR) for a category and rebuild LUT. Returns True if updated."""
if category_name not in self.mfd_params["categories"]:
logging.warning(f"[MfdState] Unknown category: {category_name}")
return False
# ensure BGR tuple with clamping
new_bgr = tuple(int(np.clip(c, 0, 255)) for c in bgr_tuple)
self.mfd_params["categories"][category_name]["color"] = new_bgr
self._rebuild_lut()
return True
def update_raw_map_intensity(self, intensity_value: int) -> bool:
"""Update raw_map_intensity and rebuild LUT. Returns True if updated."""
clamped = int(np.clip(intensity_value, 0, 255))
self.mfd_params["raw_map_intensity"] = clamped
self._rebuild_lut()
return True
def _rebuild_lut(self):
"""Rebuild the MFD LUT from current parameters (matches ControlPanel logic)."""
try:
params = self.mfd_params
raw_map_factor = params["raw_map_intensity"] / 255.0
pixel_map = params["pixel_to_category"]
categories = params["categories"]
# Build in BGR order (ControlPanel convention)
bgr_lut = np.zeros((256, 3), dtype=np.uint8)
for idx in range(256):
cat_name = pixel_map.get(idx)
if cat_name:
cat_data = categories[cat_name]
bgr = cat_data["color"]
intensity_factor = cat_data["intensity"] / 255.0
bgr_lut[idx, 0] = int(np.clip(int(round(float(bgr[0]) * intensity_factor)), 0, 255))
bgr_lut[idx, 1] = int(np.clip(int(round(float(bgr[1]) * intensity_factor)), 0, 255))
bgr_lut[idx, 2] = int(np.clip(int(round(float(bgr[2]) * intensity_factor)), 0, 255))
elif 32 <= idx <= 255:
raw_intensity = (float(idx) - 32.0) * (255.0 / 223.0)
final_gray = int(round(np.clip(raw_intensity * raw_map_factor, 0, 255)))
bgr_lut[idx, :] = final_gray
# Convert BGR -> RGB for PIL consumers
self.mfd_lut = bgr_lut[:, ::-1].copy()
except Exception:
logging.exception("[MfdState] Error rebuilding LUT")
self.mfd_lut = np.zeros((256, 3), dtype=np.uint8)
def get_lut(self) -> np.ndarray:
"""Return current MFD LUT (256x3 RGB uint8)."""
return self.mfd_lut