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

76 lines
3.2 KiB
Python

"""MFD palette LUT helpers for VideoReceiverSFP.
Provides a small, self-contained implementation of the MFD LUT
generation and application compatible with the ControlPanel defaults.
`build_mfd_lut()` returns an (256,3) uint8 array in RGB order suitable
for `Image.fromarray(..., mode='RGB')`.
"""
from typing import Optional
import numpy as np
def build_mfd_lut(raw_map_intensity: int = 255) -> np.ndarray:
"""Build a 256x3 RGB LUT for MFD indices using the same defaults
ControlPanel uses, but return values in RGB order suitable for PIL.
The ControlPanel `update_mfd_lut` builds an array in BGR order from
category definitions. We replicate the category/pixel mapping and
intensity calculation, then convert the resulting BGR -> RGB so the
LUT can be used directly with PIL `Image.fromarray(..., mode='RGB')`.
"""
# Build in BGR order following ControlPanel logic
bgr_lut = np.zeros((256, 3), dtype=np.uint8)
# Categories as defined in controlpanel.app_state._initialize_mfd_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))},
}
pixel_to_category = {p: cat for cat, data in categories.items() for p in data["pixels"]}
raw_map_factor = float(raw_map_intensity) / 255.0
for idx in range(256):
cat_name = pixel_to_category.get(idx)
if cat_name:
cat_data = categories[cat_name]
bgr = cat_data["color"]
intensity_factor = cat_data["intensity"] / 255.0
b = int(np.clip(int(round(float(bgr[0]) * intensity_factor)), 0, 255))
g = int(np.clip(int(round(float(bgr[1]) * intensity_factor)), 0, 255))
r = int(np.clip(int(round(float(bgr[2]) * intensity_factor)), 0, 255))
bgr_lut[idx, 0] = b
bgr_lut[idx, 1] = g
bgr_lut[idx, 2] = r
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, final_gray, final_gray)
# Convert BGR -> RGB for PIL consumers
rgb_lut = bgr_lut[:, ::-1].copy()
return rgb_lut
def apply_mfd_lut(indices: np.ndarray, lut: Optional[np.ndarray]) -> Optional[np.ndarray]:
"""Map an indices array (H,W) to an RGB image (H,W,3) using the LUT.
Returns a uint8 NumPy array or None on error.
"""
if lut is None:
return None
try:
# Use NumPy advanced indexing to map (H,W) -> (H,W,3)
rgb = lut[indices]
return rgb
except Exception:
return None