Spaces:
Running on Zero
Running on Zero
File size: 3,144 Bytes
bc4c433 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | """AABB normalization for occupancy XYZ.
Maps a point cloud into a roughly ``[-1, 1]^3`` cube so the MLP sees
comparable coordinates across differently sized meshes.
center = midpoint of the axis-aligned bounding box
scale = maximum half-extent (longest AABB side / 2)
Normalized point: ``(xyz - center) / scale``.
This module does not touch the occupancy model.
"""
from __future__ import annotations
import numpy as np
from numpy.typing import NDArray
PointsArray = NDArray[np.float32]
def compute_center_scale(points: np.ndarray) -> tuple[PointsArray, float]:
"""
AABB center and max half-extent for an ``(N, 3)`` point array.
Parameters
----------
points:
Query XYZ, shape ``(N, 3)``, at least one row.
Returns
-------
center:
``float32`` vector of shape ``(3,)``.
scale:
Positive float (max half-extent). Raises if the cloud has no extent.
"""
pts = np.asarray(points, dtype=np.float32)
if pts.ndim != 2 or pts.shape[1] != 3:
raise ValueError(f"points must have shape (N, 3), got {tuple(pts.shape)}")
if pts.shape[0] == 0:
raise ValueError("points must contain at least one row")
xyz_min = pts.min(axis=0)
xyz_max = pts.max(axis=0)
center = 0.5 * (xyz_min + xyz_max)
half_extents = 0.5 * (xyz_max - xyz_min)
scale = float(np.max(half_extents))
if scale <= 0.0:
raise ValueError(
"scale must be > 0; all points appear to share the same location"
)
return center.astype(np.float32, copy=False), scale
def apply_normalization(
points: np.ndarray,
center: np.ndarray,
scale: float,
) -> PointsArray:
"""
Return ``(points - center) / scale`` as ``float32 (N, 3)``.
Parameters
----------
points:
Query XYZ, shape ``(N, 3)``.
center:
AABB midpoint, shape ``(3,)``.
scale:
Positive max half-extent.
Returns
-------
ndarray
Normalized points, ``float32 (N, 3)``.
"""
if scale <= 0.0:
raise ValueError(f"scale must be > 0, got {scale}")
pts = np.asarray(points, dtype=np.float32)
if pts.ndim != 2 or pts.shape[1] != 3:
raise ValueError(f"points must have shape (N, 3), got {tuple(pts.shape)}")
c = np.asarray(center, dtype=np.float32).reshape(3)
return (pts - c) / np.float32(scale)
if __name__ == "__main__":
from scatteringnet.config import load_config
from scatteringnet.data_npz import load_points_labels
sample = (
load_config().data_dir
/ "exports"
/ "dataset_test"
/ "sphere__raycast_z_raut_s0.15_inout.npz"
)
points, _labels = load_points_labels(sample)
center, scale = compute_center_scale(points)
normed = apply_normalization(points, center, scale)
recovered = normed[:3] * np.float32(scale) + center
print(f"file={sample}")
print(f"center={center.tolist()} scale={scale:.6f}")
print(f"normed_min={normed.min(axis=0).tolist()}")
print(f"normed_max={normed.max(axis=0).tolist()}")
print(f"inverse_ok={np.allclose(recovered, points[:3], rtol=1e-5, atol=1e-5)}")
|