fixed commetn from songxiao

This commit is contained in:
ykcheungab
2026-09-25 17:46:37 +08:00
parent 6effe74172
commit 12781854ca
56 changed files with 4935 additions and 419 deletions
+163
View File
@@ -0,0 +1,163 @@
#!/usr/bin/env python3
"""Put a housing exported in *another* frame into the sensor frame, once, reproducibly.
The finger sensor's design export (``orisys-sensor-design/build/export/finger/r01/compute``)
holds ``mesh.npz`` and ``housing.npz`` (the mounting frame, ``part_names = ['frame']``) in a
**camera/world frame**, while ``compile_scene.py`` compiles from the SDK's asset directory,
whose ``mesh.npz`` is already in the sensor frame (millimetres, the unit stated by
``--meters-per-source-unit``).
The two meshes are the same surface, and their ``faces`` arrays are identical — so vertex *i*
corresponds to vertex *i* and the frame relationship is a similarity transform that can be
**fitted** rather than guessed. This tool:
1. loads both ``mesh.npz`` files,
2. fits the similarity (rotation + uniform scale + translation) from design to sensor vertices,
3. refuses to write anything if the fit does not hold to ``--tolerance`` of the mesh diagonal,
4. applies that transform to the housing vertices and writes a sensor-frame ``housing.npz``
(millimetres, same as the SDK's mesh) ready for ``compile_scene.py --housing``.
Why fit instead of hand-transcribing a matrix: a wrong matrix still produces a plausible
picture, and this one is checked against 1032 known correspondences every time it runs.
python tools/scene_assets/align_housing.py \
--design D:/orisys_ws/orisys-sensor-design/build/export/finger/r01/compute \
--sensor D:/orisys_ws/orisys-sdk/sensors/finger/assets/30fps-0703 \
--output tools/scene_assets/housing/finger_r01_housing.npz
"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
import numpy as np
#: Residual budget, as a fraction of the sensor-frame mesh diagonal. The fit is exact in
#: practice (float noise); anything above this means the two meshes are not the same shape and
#: the housing must not be transformed as if they were.
DEFAULT_TOLERANCE = 1e-4
def load_vertices(path: Path) -> np.ndarray:
with np.load(path, allow_pickle=False) as data:
if "vertices" not in data.files:
raise SystemExit(f"{path} has no 'vertices' array (found {list(data.files)})")
return np.asarray(data["vertices"], dtype=np.float64)
def load_faces(path: Path) -> np.ndarray:
with np.load(path, allow_pickle=False) as data:
return np.asarray(data["faces"])
def fit_similarity(source: np.ndarray, target: np.ndarray) -> tuple[np.ndarray, np.ndarray, float]:
"""Rotation, translation and uniform scale mapping ``source`` onto ``target``.
``target = scale * (source @ rotation) + translation``. Uses the closed-form Kabsch
solution with a scale factor; a reflection is never the answer here.
"""
source_centroid = source.mean(axis=0)
target_centroid = target.mean(axis=0)
source_centered = source - source_centroid
target_centered = target - target_centroid
u, singular, vt = np.linalg.svd(source_centered.T @ target_centered)
rotation = u @ vt
if np.linalg.det(rotation) < 0.0:
u = u.copy()
u[:, -1] *= -1.0
rotation = u @ vt
scale = float(np.sum(singular) / np.sum(source_centered**2))
translation = target_centroid - scale * (source_centroid @ rotation)
return rotation, translation, scale
def apply_similarity(
points: np.ndarray, rotation: np.ndarray, translation: np.ndarray, scale: float
) -> np.ndarray:
return scale * (points @ rotation) + translation
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
parser.add_argument("--design", type=Path, required=True, help="export dir with mesh+housing")
parser.add_argument("--sensor", type=Path, required=True, help="export dir whose mesh is sensor-frame")
parser.add_argument("--output", type=Path, required=True, help="sensor-frame housing npz to write")
parser.add_argument("--tolerance", type=float, default=DEFAULT_TOLERANCE)
parser.add_argument(
"--matrix",
type=Path,
default=None,
help="also write the fitted 4x4 (source -> sensor) as a .npy, for the record",
)
args = parser.parse_args(argv)
design_mesh = args.design / "mesh.npz"
sensor_mesh = args.sensor / "mesh.npz"
housing_path = args.design / "housing.npz"
for path in (design_mesh, sensor_mesh, housing_path):
if not path.is_file():
raise SystemExit(f"missing {path}")
source = load_vertices(design_mesh)
target = load_vertices(sensor_mesh)
if source.shape != target.shape:
raise SystemExit(
f"the meshes disagree: {source.shape} vs {target.shape} — the correspondence the "
"fit relies on does not exist"
)
if not np.array_equal(load_faces(design_mesh), load_faces(sensor_mesh)):
raise SystemExit(
"the faces arrays differ, so vertex i is not vertex i; refusing to fit a transform"
)
rotation, translation, scale = fit_similarity(source, target)
residual = apply_similarity(source, rotation, translation, scale) - target
rms = float(np.sqrt((residual**2).sum(axis=1).mean()))
diagonal = float(np.linalg.norm(target.max(axis=0) - target.min(axis=0)))
relative = rms / max(diagonal, 1e-12)
print(f"fit over {len(source)} vertices: scale={scale:.8g} rms={rms:.3g} "
f"({relative:.3g} of the {diagonal:.3f}-unit diagonal)")
if relative > float(args.tolerance):
raise SystemExit(
f"the fit is off by {relative:.3g} of the mesh diagonal (budget {args.tolerance:g}): "
"these two meshes are not the same shape, so transforming the housing with this "
"matrix would put it in the wrong place"
)
with np.load(housing_path, allow_pickle=False) as data:
housing_vertices = np.asarray(data["vertices"], dtype=np.float64)
housing_faces = np.asarray(data["faces"])
part_names = data["part_names"] if "part_names" in data.files else None
aligned = apply_similarity(housing_vertices, rotation, translation, scale)
args.output.parent.mkdir(parents=True, exist_ok=True)
np.savez(
args.output,
vertices=aligned.astype(np.float32),
faces=housing_faces.astype(np.int32),
# Provenance, so a later reader can see this was fitted rather than exported.
source_frame=np.array(str(args.design)),
sensor_frame=np.array(str(args.sensor)),
fitted_scale=np.array(scale, dtype=np.float64),
fitted_rms=np.array(rms, dtype=np.float64),
part_names=(part_names if part_names is not None else np.array(["housing"])),
)
print(f"wrote {args.output}")
print(f" housing in the sensor frame: low={np.round(aligned.min(axis=0), 3)} "
f"high={np.round(aligned.max(axis=0), 3)}"
f" ({len(housing_faces)} triangles)")
if args.matrix is not None:
matrix = np.eye(4)
matrix[:3, :3] = scale * rotation
matrix[:3, 3] = translation
args.matrix.parent.mkdir(parents=True, exist_ok=True)
np.save(args.matrix, matrix)
print(f"wrote {args.matrix}")
return 0
if __name__ == "__main__":
sys.exit(main())