"""2D UV flatten remap tables and runtime frame remapping. Stores precomputed map_x/map_y/valid_mask and applies cv2.remap at runtime to convert source camera frames into the UV flatten domain. """ from dataclasses import dataclass, field from typing import Any, Dict, Optional, Tuple import cv2 import numpy as np @dataclass class UVRemap: """Precomputed UV flatten remap tables.""" map_x: np.ndarray map_y: np.ndarray valid_mask: np.ndarray metadata: Dict[str, Any] = field(default_factory=dict) _map1_fixed: Optional[np.ndarray] = field(default=None, repr=False, compare=False) _map2_fixed: Optional[np.ndarray] = field(default=None, repr=False, compare=False) _border_only_invalid: bool = field(default=False, repr=False, compare=False) @property def out_height(self) -> int: return int(self.map_x.shape[0]) @property def out_width(self) -> int: return int(self.map_x.shape[1]) @classmethod def from_dict(cls, remap_dict: dict) -> "UVRemap": """Build UVRemap from a flat dict (e.g. np.load result).""" meta_keys = { "source_width", "source_height", "out_width", "out_height", "flip_v", "edge_band", "edge_expand", "projection_K", "method", } metadata = {k: remap_dict[k] for k in meta_keys if k in remap_dict} remap = cls( map_x=np.asarray(remap_dict["map_x"], dtype=np.float32), map_y=np.asarray(remap_dict["map_y"], dtype=np.float32), valid_mask=np.asarray(remap_dict["valid_mask"], dtype=bool), metadata=metadata, ) remap.ensure_fast_maps() return remap @classmethod def load(cls, path: str) -> "UVRemap": """Load cached remap from .npz file.""" data = np.load(path) return cls.from_dict({key: data[key] for key in data.files}) def to_dict(self) -> dict: """Serialize to dict suitable for np.savez_compressed.""" out = { "map_x": self.map_x.astype(np.float32), "map_y": self.map_y.astype(np.float32), "valid_mask": self.valid_mask.astype(bool), } out.update(self.metadata) return out def save(self, path: str) -> None: """Save remap cache to .npz file.""" np.savez_compressed(path, **self.to_dict()) def ensure_fast_maps(self) -> None: """Build OpenCV CV_16SC2 remap tables once for faster cv2.remap.""" if self._map1_fixed is not None: return self._map1_fixed, self._map2_fixed = cv2.convertMaps( self.map_x, self.map_y, cv2.CV_16SC2, ) invalid = ~self.valid_mask if not np.any(invalid): self._border_only_invalid = True else: self._border_only_invalid = bool( np.all(self.map_x[invalid] <= 0.0) and np.all(self.map_y[invalid] <= 0.0) ) def flatten(self, frame_bgr, border_value=0, dst=None): """Remap a source BGR frame into UV flatten coordinates. frame_bgr: (H_src, W_src, 3) source camera image border_value: fill value for invalid/out-of-domain pixels returns: (H_out, W_out, 3) flattened BGR image """ return flatten_frame(frame_bgr, self, border_value=border_value, dst=dst) def remap_valid_ratio(remap) -> float: """Fraction of output pixels covered by the valid UV domain.""" if isinstance(remap, UVRemap): mask = remap.valid_mask else: mask = remap["valid_mask"] return float(np.mean(mask)) def _cache_matches( remap, source_K, source_width, source_height, out_width, out_height, flip_v, edge_band, edge_expand, ) -> bool: """True if cached remap matches the requested build parameters.""" if isinstance(remap, UVRemap): rd = remap.to_dict() else: rd = remap required = {"map_x", "map_y", "valid_mask", "source_width", "source_height", "out_width", "out_height"} if not required.issubset(rd.keys()): return False if rd["map_x"].shape != (out_height, out_width): return False if int(rd["source_width"]) != source_width or int(rd["source_height"]) != source_height: return False if int(rd["out_width"]) != out_width or int(rd["out_height"]) != out_height: return False if "flip_v" in rd and int(rd["flip_v"]) != int(flip_v): return False if "edge_band" in rd and not np.isclose(float(rd["edge_band"]), float(edge_band), rtol=1e-6, atol=1e-6): return False if "edge_expand" in rd and not np.isclose(float(rd["edge_expand"]), float(edge_expand), rtol=1e-6, atol=1e-6): return False if "projection_K" in rd and not np.allclose(rd["projection_K"], source_K, rtol=1e-6, atol=1e-6): return False return True def _remap_maps(remap) -> Tuple[np.ndarray, np.ndarray, np.ndarray, bool]: if isinstance(remap, UVRemap): remap.ensure_fast_maps() valid_mask = remap.valid_mask skip_invalid_fill = remap._border_only_invalid if remap._map1_fixed is not None: return remap._map1_fixed, remap._map2_fixed, valid_mask, skip_invalid_fill return remap.map_x, remap.map_y, valid_mask, skip_invalid_fill return ( remap["map_x"].astype(np.float32), remap["map_y"].astype(np.float32), remap["valid_mask"].astype(bool), False, ) def flatten_frame(frame, remap, border_value=0, dst=None): """Remap frame using map_x/map_y; zeros invalid UV pixels. frame: (H, W, 3) or (H, W) source image remap: UVRemap or dict with map_x, map_y, valid_mask returns: flattened image, same channels as input """ map1, map2, valid_mask, skip_invalid_fill = _remap_maps(remap) flat = cv2.remap( frame, map1, map2, interpolation=cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT, borderValue=border_value, dst=dst, ) if skip_invalid_fill and border_value == 0: return flat if not flat.flags.writeable: flat = flat.copy() flat[~valid_mask] = border_value return flat