xsponenta Claude Opus 4.7 commited on
Commit
3a43227
·
1 Parent(s): 72962c4

Rollback to proven 0.4584 baseline, add plane_wireframe to ensemble

Browse files

Two changes in one commit:

1. ROLLBACK: revert script.py and postprocess_v2.py to commit 56f1ec6 contents
(the highest-scoring submission at hss_mean=0.4584). Removes snap_vertical +
snap_manhattan (regressed edge_iou -0.009 in 72962c4) and the
filter_by_colmap_support step (added in 6ecd5f8 but never independently
evaluated, so its effect is unknown).

2. NEW: wire plane_wireframe.predict_wireframe_planes into the ensemble as a
third recall source after triangulation tracks. RANSAC-segments roof planes
from the COLMAP cloud and intersects them to recover ridge/eave edges the
learned model misses. The module was fully implemented but never wired in.
Uses the same append-only hybrid_merge(radius=0.8) pattern as triangulation,
so it only adds genuinely new vertices/edges; cannot corrupt existing
topology. Wrapped in try/except so any failure falls back silently to the
pre-plane prediction.

Runtime deps open3d and scikit-spatial added via install_if_missing (extended
to accept a separate pip_name for packages whose import name differs).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

Files changed (2) hide show
  1. s23dr_2026_example/postprocess_v2.py +0 -108
  2. script.py +20 -73
s23dr_2026_example/postprocess_v2.py CHANGED
@@ -37,111 +37,3 @@ def snap_horizontal(vertices, edges, max_slope=0.05):
37
  verts[a, 1] = avg_y
38
  verts[b, 1] = avg_y
39
  return verts
40
-
41
-
42
- def snap_vertical(vertices, edges, max_slope=0.05):
43
- """Snap near-vertical edges to be exactly vertical (shared X and Z).
44
-
45
- Mirror of snap_horizontal across the up-axis. A near-vertical edge has
46
- a small horizontal spread (dxz) relative to its vertical spread (dy).
47
- """
48
- verts = vertices.copy()
49
- for a, b in edges:
50
- a, b = int(a), int(b)
51
- dy = abs(verts[a, 1] - verts[b, 1])
52
- dxz = np.sqrt((verts[a, 0] - verts[b, 0]) ** 2 + (verts[a, 2] - verts[b, 2]) ** 2)
53
- if dy > 0.1 and dxz / dy < max_slope:
54
- avg_x = 0.5 * (verts[a, 0] + verts[b, 0])
55
- avg_z = 0.5 * (verts[a, 2] + verts[b, 2])
56
- verts[a, 0] = avg_x
57
- verts[a, 2] = avg_z
58
- verts[b, 0] = avg_x
59
- verts[b, 2] = avg_z
60
- return verts
61
-
62
-
63
- def snap_manhattan(vertices, edges, tol_deg=3.0, min_count=4, n_iters=3):
64
- """Snap near-axis-aligned horizontal edges to a single dominant building direction.
65
-
66
- Detects the dominant XZ-plane direction of near-horizontal edges via a
67
- doubled-angle circular mean (so direction and its 90° rotation reinforce
68
- each other). Edges within ``tol_deg`` of that direction or its perpendicular
69
- are rotated about their midpoint to align exactly. Endpoints shared between
70
- multiple snapped edges are averaged (Gauss-Seidel, ``n_iters`` passes) to
71
- converge to a Manhattan-consistent position without drift.
72
-
73
- Only operates in the XZ plane; Y is preserved (use snap_horizontal /
74
- snap_vertical for Y handling). Returns vertices unchanged if fewer than
75
- ``min_count`` near-horizontal edges exist (insufficient evidence).
76
- """
77
- verts = np.asarray(vertices, dtype=np.float64).copy()
78
- n_verts = len(verts)
79
- n_edges = len(edges)
80
- if n_verts < 2 or n_edges < min_count:
81
- return vertices
82
-
83
- # Classify each edge once: near-horizontal? full-angle in XZ?
84
- edge_data = [] # list of (a, b, full_ang) for near-horizontal edges only
85
- for a, b in edges:
86
- a, b = int(a), int(b)
87
- dx = verts[b, 0] - verts[a, 0]
88
- dy = verts[b, 1] - verts[a, 1]
89
- dz = verts[b, 2] - verts[a, 2]
90
- dxz = np.sqrt(dx * dx + dz * dz)
91
- if dxz < 0.2:
92
- continue
93
- if abs(dy) / dxz > 0.1:
94
- continue
95
- full_ang = np.arctan2(dz, dx)
96
- edge_data.append((a, b, full_ang))
97
-
98
- if len(edge_data) < min_count:
99
- return vertices
100
-
101
- # Dominant Manhattan direction via 4×-angle circular mean: a building grid
102
- # has 90° symmetry, so edges at θ, θ+90°, θ+180°, θ+270° must reinforce each
103
- # other in the estimate. Multiply by 4 to fold [0, π/2) → [0, 2π), take the
104
- # circular mean, divide by 4.
105
- quad = np.array([4.0 * e[2] for e in edge_data])
106
- peak_ang = 0.25 * np.arctan2(np.sin(quad).mean(), np.cos(quad).mean())
107
-
108
- # Identify edges to snap and pick the best axis (peak, peak+π/2, peak+π, peak+3π/2)
109
- tol = np.deg2rad(tol_deg)
110
- snap_targets = [] # (a, b, ux, uz)
111
- candidates = [peak_ang + k * (np.pi / 2.0) for k in range(4)]
112
- for a, b, full_ang in edge_data:
113
- diffs = [abs(((full_ang - c + np.pi) % (2 * np.pi)) - np.pi) for c in candidates]
114
- k = int(np.argmin(diffs))
115
- if diffs[k] >= tol:
116
- continue
117
- target = candidates[k]
118
- snap_targets.append((a, b, float(np.cos(target)), float(np.sin(target))))
119
-
120
- if not snap_targets:
121
- return vertices
122
-
123
- # Gauss-Seidel: each iteration, accumulate target endpoint positions per
124
- # vertex and average. Y is left untouched.
125
- for _ in range(n_iters):
126
- sum_xz = np.zeros((n_verts, 2), dtype=np.float64)
127
- count = np.zeros(n_verts, dtype=np.int64)
128
- for a, b, ux, uz in snap_targets:
129
- mx = 0.5 * (verts[a, 0] + verts[b, 0])
130
- mz = 0.5 * (verts[a, 2] + verts[b, 2])
131
- dx = verts[b, 0] - verts[a, 0]
132
- dz = verts[b, 2] - verts[a, 2]
133
- length = abs(dx * ux + dz * uz) # projection of edge onto snap dir
134
- half = 0.5 * length
135
- sum_xz[a, 0] += mx - ux * half
136
- sum_xz[a, 1] += mz - uz * half
137
- sum_xz[b, 0] += mx + ux * half
138
- sum_xz[b, 1] += mz + uz * half
139
- count[a] += 1
140
- count[b] += 1
141
- moved = count > 0
142
- if not moved.any():
143
- break
144
- verts[moved, 0] = sum_xz[moved, 0] / count[moved]
145
- verts[moved, 2] = sum_xz[moved, 1] / count[moved]
146
-
147
- return verts.astype(np.asarray(vertices).dtype)
 
37
  verts[a, 1] = avg_y
38
  verts[b, 1] = avg_y
39
  return verts
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
script.py CHANGED
@@ -8,14 +8,16 @@ os.environ['KMP_DUPLICATE_LIB_OK'] = 'True'
8
  import subprocess
9
  import sys
10
 
11
- def install_if_missing(package):
12
  try:
13
  __import__(package.split("==")[0])
14
  except ImportError:
15
- subprocess.check_call([sys.executable, "-m", "pip", "install", package])
16
 
17
  install_if_missing("scipy")
18
  install_if_missing("pandas")
 
 
19
 
20
  from pathlib import Path
21
  from tqdm import tqdm
@@ -50,9 +52,7 @@ from s23dr_2026_example.tokenizer import EdgeDepthSequenceConfig
50
  from s23dr_2026_example.model import EdgeDepthSegmentsModel
51
  from s23dr_2026_example.segment_postprocess import merge_vertices_iterative
52
  from s23dr_2026_example.varifold import segments_to_vertices_edges
53
- from s23dr_2026_example.postprocess_v2 import (
54
- snap_to_point_cloud, snap_horizontal, snap_vertical, snap_manhattan,
55
- )
56
 
57
  SEQ_LEN = 4096
58
  COLMAP_QUOTA = 3072
@@ -234,11 +234,8 @@ def predict_sample(sample_dict, model, device):
234
  cid_valid = cid[mask]
235
  pv = snap_to_point_cloud(pv, xyz_world, cid_valid, snap_radius=SNAP_RADIUS)
236
 
237
- # Axis snaps: horizontal, then vertical, then Manhattan-direction in XZ.
238
- # Each pass is a no-op on edges that don't qualify, so order is safe.
239
  pv = snap_horizontal(pv, pe)
240
- pv = snap_vertical(pv, pe)
241
- pv = snap_manhattan(pv, pe)
242
 
243
  if len(pv) < 2 or len(pe) < 1:
244
  return empty_solution()
@@ -312,62 +309,6 @@ def hybrid_merge(pred_v, pred_e, track_v, track_e, merge_radius=0.8):
312
 
313
  return np.array(final_v), final_e
314
 
315
-
316
- def filter_by_colmap_support(pv, pe, sample, support_radius=0.6):
317
- """Drop predicted vertices that have NO COLMAP point within support_radius.
318
-
319
- Hallucinated vertices from the model (predicted in 3D space with no real
320
- geometric evidence) typically appear in regions with no COLMAP point cloud.
321
- Filtering by COLMAP-presence is a precision-only operation: real vertices
322
- survive (the COLMAP cloud covers all reconstructed regions of the building),
323
- spurious model outputs in empty space get dropped.
324
-
325
- Returns the filtered (vertices, edges). On any failure or empty result,
326
- falls back to the unfiltered input to avoid an empty submission.
327
- """
328
- try:
329
- if not isinstance(pv, np.ndarray) or len(pv) < 2 or len(pe) < 1:
330
- return pv, pe
331
- from hoho2025.example_solutions import convert_entry_to_human_readable
332
- good = convert_entry_to_human_readable(sample)
333
- colmap_rec = good.get('colmap') or good.get('colmap_binary')
334
- if colmap_rec is None:
335
- return pv, pe
336
- colmap_xyz = np.array(
337
- [p.xyz for p in colmap_rec.points3D.values()], dtype=np.float64
338
- )
339
- if len(colmap_xyz) < 5:
340
- return pv, pe
341
-
342
- from scipy.spatial import cKDTree
343
- tree = cKDTree(colmap_xyz)
344
- dists, _ = tree.query(np.asarray(pv, dtype=np.float64), k=1)
345
- keep_mask = dists <= support_radius
346
-
347
- if keep_mask.all():
348
- return pv, pe # nothing to filter
349
-
350
- n_keep = int(keep_mask.sum())
351
- # Require at least 2 vertices and 1 edge to remain after filtering.
352
- if n_keep < 2:
353
- return pv, pe
354
-
355
- old_to_new = {int(old): new for new, old in enumerate(np.where(keep_mask)[0])}
356
- new_pv = pv[keep_mask]
357
- new_pe = []
358
- for u, v in pe:
359
- u, v = int(u), int(v)
360
- if u in old_to_new and v in old_to_new and u != v:
361
- new_pe.append((old_to_new[u], old_to_new[v]))
362
-
363
- if len(new_pe) < 1:
364
- return pv, pe # do not drop all edges
365
-
366
- return new_pv, new_pe
367
- except Exception:
368
- return pv, pe
369
-
370
-
371
  # ---------------------------------------------------------------------------
372
  # Main
373
  # ---------------------------------------------------------------------------
@@ -463,14 +404,20 @@ if __name__ == "__main__":
463
  except Exception as track_e_err:
464
  print(f" Track ensemble failed for {order_id}: {track_e_err}")
465
 
466
- # Final precision pass: drop vertices with no nearby COLMAP
467
- # support. These are the model's hallucinations in regions
468
- # with no geometric evidence. Internal fallbacks ensure we
469
- # never end up with fewer than 2 vertices / 1 edge.
470
- pred_v, pred_e = filter_by_colmap_support(
471
- pred_v, pred_e, sample, support_radius=0.6,
472
- )
473
-
 
 
 
 
 
 
474
  except Exception as e:
475
  import traceback
476
  print(f" Predict failed for {order_id}:\n{traceback.format_exc()}")
 
8
  import subprocess
9
  import sys
10
 
11
+ def install_if_missing(package, pip_name=None):
12
  try:
13
  __import__(package.split("==")[0])
14
  except ImportError:
15
+ subprocess.check_call([sys.executable, "-m", "pip", "install", pip_name or package])
16
 
17
  install_if_missing("scipy")
18
  install_if_missing("pandas")
19
+ install_if_missing("open3d")
20
+ install_if_missing("skspatial", pip_name="scikit-spatial")
21
 
22
  from pathlib import Path
23
  from tqdm import tqdm
 
52
  from s23dr_2026_example.model import EdgeDepthSegmentsModel
53
  from s23dr_2026_example.segment_postprocess import merge_vertices_iterative
54
  from s23dr_2026_example.varifold import segments_to_vertices_edges
55
+ from s23dr_2026_example.postprocess_v2 import snap_to_point_cloud, snap_horizontal
 
 
56
 
57
  SEQ_LEN = 4096
58
  COLMAP_QUOTA = 3072
 
234
  cid_valid = cid[mask]
235
  pv = snap_to_point_cloud(pv, xyz_world, cid_valid, snap_radius=SNAP_RADIUS)
236
 
237
+ # Horizontal snap
 
238
  pv = snap_horizontal(pv, pe)
 
 
239
 
240
  if len(pv) < 2 or len(pe) < 1:
241
  return empty_solution()
 
309
 
310
  return np.array(final_v), final_e
311
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
312
  # ---------------------------------------------------------------------------
313
  # Main
314
  # ---------------------------------------------------------------------------
 
404
  except Exception as track_e_err:
405
  print(f" Track ensemble failed for {order_id}: {track_e_err}")
406
 
407
+ # Apply plane-intersection wireframe: RANSAC roof planes from the
408
+ # COLMAP cloud, intersect them to recover ridge/eave edges the
409
+ # learned model may miss. Recall-focused, geometry-only signal.
410
+ # hybrid_merge keeps it append-only at merge_radius=0.8.
411
+ try:
412
+ from plane_wireframe import predict_wireframe_planes
413
+ plane_v, plane_e = predict_wireframe_planes(sample)
414
+ if len(plane_v) > 0 and len(plane_e) > 0:
415
+ pred_v, pred_e = hybrid_merge(
416
+ pred_v, pred_e, plane_v, plane_e, merge_radius=0.8,
417
+ )
418
+ except Exception as plane_err:
419
+ print(f" Plane ensemble failed for {order_id}: {plane_err}")
420
+
421
  except Exception as e:
422
  import traceback
423
  print(f" Predict failed for {order_id}:\n{traceback.format_exc()}")