StarAtNyte1's picture
8192-pt inference: SEQ_LEN 4096→8192, +0.003 dev val HSS (0.3496→0.3530)
8011973
Raw
History Blame Contribute Delete
7.73 kB
"""Structural plane fitting and vertex snapping for S23DR wireframes.
Class IDs (from cache_scenes.py STRUCTURAL_CLASSES, 0-indexed):
0 apex 7 valley
1 eave_end_point 8 flashing
2 flashing_end_pt 9 step_flashing
3 rake 10 roof ← roof face surface
4 ridge 11 other_house ← walls, siding, doors, windows
5 eave 12 non_house ← unlabeled / ADE
6 hip
"""
import numpy as np
ROOF_CLASS = 10 # compressed class_id for "roof" face
WALL_CLASS = 11 # compressed class_id for "other_house" (walls dominate)
def _ransac_plane(pts, dist_thresh=0.12, max_iters=150, rng=None):
"""Fit plane via RANSAC + PCA refit. Returns (normal, d) or None."""
if rng is None:
rng = np.random.RandomState(42)
best_inliers = 0
best_n = best_d = None
for _ in range(max_iters):
idx = rng.choice(len(pts), 3, replace=False)
p1, p2, p3 = pts[idx]
n = np.cross(p2 - p1, p3 - p1)
nrm = np.linalg.norm(n)
if nrm < 1e-10:
continue
n /= nrm
d = float(np.dot(n, p1))
n_in = int((np.abs(pts @ n - d) < dist_thresh).sum())
if n_in > best_inliers:
best_inliers, best_n, best_d = n_in, n, d
if best_n is None or best_inliers < 3:
return None
inliers = pts[np.abs(pts @ best_n - best_d) < dist_thresh]
if len(inliers) < 3:
return (best_n, float(best_d))
centroid = inliers.mean(0)
_, _, Vt = np.linalg.svd(inliers - centroid, full_matrices=False)
n = Vt[-1]
if np.dot(n, best_n) < 0:
n = -n
return (n, float(np.dot(n, centroid)))
def _fit_planes_iterative(pts, n_planes, min_inliers, dist_thresh):
"""Iterative RANSAC on pts. Returns list of (normal, d, centroid)."""
rng = np.random.RandomState(123)
remaining = pts.copy()
planes = []
for _ in range(n_planes):
if len(remaining) < min_inliers:
break
result = _ransac_plane(remaining, dist_thresh=dist_thresh,
max_iters=150, rng=rng)
if result is None:
break
n, d = result
inlier_mask = np.abs(remaining @ n - d) < dist_thresh
if inlier_mask.sum() < min_inliers:
break
planes.append((n, d, remaining[inlier_mask].mean(0)))
remaining = remaining[~inlier_mask]
return planes
def _intersection_line(p1, p2):
"""Line of intersection of two planes. Returns (p0, dir, t_min, t_max) or None."""
n1, d1, c1 = p1
n2, d2, c2 = p2
direction = np.cross(n1, n2)
nrm = np.linalg.norm(direction)
if nrm < 1e-6:
return None
direction /= nrm
A = np.stack([n1, n2, direction])
b = np.array([d1, d2, 0.0])
try:
p0 = np.linalg.solve(A, b)
except np.linalg.LinAlgError:
return None
t1 = float(np.dot(c1 - p0, direction))
t2 = float(np.dot(c2 - p0, direction))
t_min = min(t1, t2) - 3.0
t_max = max(t1, t2) + 3.0
return (p0, direction, t_min, t_max)
def _plane_triple_intersection(p1, p2, p3):
"""Point where 3 planes (n,d,c) each intersect. Returns point or None."""
n1, d1, _ = p1
n2, d2, _ = p2
n3, d3, _ = p3
A = np.stack([n1, n2, n3])
b = np.array([d1, d2, d3], dtype=np.float64)
try:
if abs(np.linalg.det(A)) < 1e-5:
return None
return np.linalg.solve(A, b)
except np.linalg.LinAlgError:
return None
def snap_to_plane_intersections(pv, pe, xyz_world, cid_valid, src_valid=None,
n_planes=10, min_inliers=25, dist_thresh=0.12,
snap_radius=0.45):
"""Snap predicted vertices to nearest roof plane-intersection line.
Uses roof-class COLMAP points (class_id==10). Falls back if <2 planes found.
"""
roof_mask = cid_valid == ROOF_CLASS
if src_valid is not None:
colmap_roof = roof_mask & (src_valid == 0)
pts = xyz_world[colmap_roof] if colmap_roof.sum() >= min_inliers else xyz_world[roof_mask]
else:
pts = xyz_world[roof_mask]
if len(pts) < min_inliers:
return pv
planes = _fit_planes_iterative(pts, n_planes, min_inliers, dist_thresh)
if len(planes) < 2:
return pv
lines = []
for i in range(len(planes)):
for j in range(i + 1, len(planes)):
line = _intersection_line(planes[i], planes[j])
if line is not None:
lines.append(line)
if not lines:
return pv
pv_new = np.array(pv, dtype=np.float64)
for vi in range(len(pv_new)):
v = pv_new[vi]
best_dist = snap_radius
best_pos = None
for p0, d, t_min, t_max in lines:
t = float(np.dot(v - p0, d))
t = max(t_min, min(t_max, t))
proj = p0 + t * d
dist = float(np.linalg.norm(v - proj))
if dist < best_dist:
best_dist = dist
best_pos = proj
if best_pos is not None:
pv_new[vi] = best_pos
return pv_new
def snap_to_structural_intersections(pv, pe, xyz_world, cid_valid, src_valid=None,
n_planes_wall=6, n_planes_roof=6,
min_inliers=20, dist_thresh=0.12,
snap_radius=0.5):
"""Snap vertices to structural plane triple-intersections.
Fits planes to wall (class 11) and roof (class 10) COLMAP points, computes
all 3-plane intersections, snaps each predicted vertex to the nearest one
within snap_radius. Falls back silently if insufficient points/planes.
3-plane intersections correspond to true wireframe vertices:
wall-wall-floor → base corner
wall-wall-ceiling → top corner
wall-roof-roof → eave corner
roof-roof-roof → ridge peak
"""
pv = np.asarray(pv, dtype=np.float64)
if len(pv) < 2:
return pv
# COLMAP points only (more reliable geometry)
if src_valid is not None:
colmap = src_valid == 0
pts_all = xyz_world[colmap]
cids_all = cid_valid[colmap]
else:
pts_all = xyz_world
cids_all = cid_valid
all_planes = []
# Wall planes
wall_pts = pts_all[cids_all == WALL_CLASS]
if len(wall_pts) >= min_inliers:
all_planes.extend(
_fit_planes_iterative(wall_pts, n_planes_wall, min_inliers, dist_thresh)
)
# Roof planes
roof_pts = pts_all[cids_all == ROOF_CLASS]
if len(roof_pts) >= min_inliers:
all_planes.extend(
_fit_planes_iterative(roof_pts, n_planes_roof, min_inliers, dist_thresh)
)
if len(all_planes) < 3:
return pv
# Scene bounding box for filtering implausible intersections
bb_min = pts_all.min(0) - 2.0
bb_max = pts_all.max(0) + 2.0
# All 3-plane intersections
candidates = []
n = len(all_planes)
for i in range(n):
for j in range(i + 1, n):
for k in range(j + 1, n):
pt = _plane_triple_intersection(all_planes[i], all_planes[j], all_planes[k])
if pt is None:
continue
if np.all(pt >= bb_min) and np.all(pt <= bb_max):
candidates.append(pt)
if not candidates:
return pv
cand_arr = np.array(candidates) # (M, 3)
# Snap each vertex to nearest candidate within radius
pv_new = pv.copy()
for vi in range(len(pv_new)):
dists = np.linalg.norm(cand_arr - pv_new[vi], axis=1)
nearest = int(dists.argmin())
if dists[nearest] < snap_radius:
pv_new[vi] = cand_arr[nearest]
return pv_new