xsponenta commited on
Commit
6cf3fbd
·
1 Parent(s): 9deb9e1

Boost: COLMAP plane refinement + junction constraints + snap fixes

Browse files

Inference-only improvements (no retraining required) targeting both
corner_f1 and edge_iou:

- Integrate refine_vertices_multiview_plane in main loop. Built but
never called; snaps each vertex onto the roof plane visible at its
2D projection across views, weighted by per-view PCA quality.
Directly targets corner_f1 (currently the largest gap vs leaders).
- Integrate apply_junction_constraints after hybrid_merge: drops
collinear duplicate vertices, antiparallel duplicate edges, and
short leaf edges. Improves edge_iou by removing structurally
invalid topology. Falls back to pre-junction graph if it would
produce zero edges.
- snap_to_point_cloud now includes flashing_end_point (class 3) as
a snap target. Previously only classes 1+2 were used.
- snap_horizontal now only moves a vertex when ALL of its incident
edges are near-horizontal — prevents corruption of sloped edges
that share a vertex with an eave.
- hybrid_merge: accept track edges between two existing predicted
vertices when both are within a tight 0.3 m radius (high-confidence
topology correction). Previously only edges touching new vertices
were accepted.

Each new integration is wrapped in try/except so a failure on any
sample falls back to the previous behaviour.

Files changed (2) hide show
  1. s23dr_2026_example/postprocess_v2.py +33 -8
  2. script.py +64 -11
s23dr_2026_example/postprocess_v2.py CHANGED
@@ -6,7 +6,7 @@ def snap_to_point_cloud(vertices, xyz, class_id, snap_radius=0.5,
6
  target_classes=None):
7
  """Snap vertices to nearby point cloud clusters of specific semantic classes."""
8
  if target_classes is None:
9
- target_classes = [1, 2] # apex, eave_end_point
10
 
11
  snapped = vertices.copy()
12
  mask = np.isin(class_id, target_classes)
@@ -26,14 +26,39 @@ def snap_to_point_cloud(vertices, xyz, class_id, snap_radius=0.5,
26
 
27
 
28
  def snap_horizontal(vertices, edges, max_slope=0.05):
29
- """Snap near-horizontal edges to be exactly horizontal."""
 
 
 
 
30
  verts = vertices.copy()
31
- for a, b in edges:
 
 
 
 
 
 
32
  a, b = int(a), int(b)
33
  dy = abs(verts[a, 1] - verts[b, 1])
34
- dxz = np.sqrt((verts[a, 0] - verts[b, 0])**2 + (verts[a, 2] - verts[b, 2])**2)
35
- if dxz > 0.1 and dy / dxz < max_slope:
36
- avg_y = 0.5 * (verts[a, 1] + verts[b, 1])
37
- verts[a, 1] = avg_y
38
- verts[b, 1] = avg_y
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  return verts
 
6
  target_classes=None):
7
  """Snap vertices to nearby point cloud clusters of specific semantic classes."""
8
  if target_classes is None:
9
+ target_classes = [1, 2, 3] # apex, eave_end_point, flashing_end_point
10
 
11
  snapped = vertices.copy()
12
  mask = np.isin(class_id, target_classes)
 
26
 
27
 
28
  def snap_horizontal(vertices, edges, max_slope=0.05):
29
+ """Snap near-horizontal edges to be exactly horizontal.
30
+
31
+ Only snaps a vertex if ALL of its incident edges are near-horizontal —
32
+ otherwise we would corrupt sloped edges that share the vertex.
33
+ """
34
  verts = vertices.copy()
35
+ n = len(verts)
36
+ if n == 0 or len(edges) == 0:
37
+ return verts
38
+
39
+ edge_is_h = []
40
+ incident = [[] for _ in range(n)]
41
+ for ei, (a, b) in enumerate(edges):
42
  a, b = int(a), int(b)
43
  dy = abs(verts[a, 1] - verts[b, 1])
44
+ dxz = np.sqrt((verts[a, 0] - verts[b, 0]) ** 2 + (verts[a, 2] - verts[b, 2]) ** 2)
45
+ is_h = dxz > 0.1 and dy / dxz < max_slope
46
+ edge_is_h.append(is_h)
47
+ incident[a].append(ei)
48
+ incident[b].append(ei)
49
+
50
+ purely_h = [
51
+ len(incident[v]) > 0 and all(edge_is_h[ei] for ei in incident[v])
52
+ for v in range(n)
53
+ ]
54
+
55
+ for ei, (a, b) in enumerate(edges):
56
+ if not edge_is_h[ei]:
57
+ continue
58
+ a, b = int(a), int(b)
59
+ if not (purely_h[a] and purely_h[b]):
60
+ continue
61
+ avg_y = 0.5 * (verts[a, 1] + verts[b, 1])
62
+ verts[a, 1] = avg_y
63
+ verts[b, 1] = avg_y
64
  return verts
script.py CHANGED
@@ -293,18 +293,38 @@ def hybrid_merge(pred_v, pred_e, track_v, track_e, merge_radius=0.8):
293
  for u, v in final_e:
294
  existing_edges.add((min(u, v), max(u, v)))
295
 
 
 
 
 
296
  for u_t, v_t in track_e:
297
  u_f = track_to_final.get(u_t)
298
  v_f = track_to_final.get(v_t)
299
- if u_f is not None and v_f is not None and u_f != v_f:
300
- e = (min(u_f, v_f), max(u_f, v_f))
301
- if e not in existing_edges:
302
- # ONLY append the tracked edge if it connects to a NEWLY DISCOVERED vertex.
303
- # This prevents the geometric tracker from aggressively re-wiring the learned model's existing topology!
304
- if u_f >= len(pred_v) or v_f >= len(pred_v):
305
- final_e.append(e)
306
- existing_edges.add(e)
307
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
  return np.array(final_v), final_e
309
 
310
  # ---------------------------------------------------------------------------
@@ -397,11 +417,44 @@ if __name__ == "__main__":
397
  from triangulation import predict_wireframe_tracks
398
  # Use min_views=3 for highly precise, conservative geometric tracks
399
  track_v, track_e = predict_wireframe_tracks(sample, min_views=3)
400
-
401
  pred_v, pred_e = hybrid_merge(pred_v, pred_e, track_v, track_e, merge_radius=0.8)
402
  except Exception as track_e_err:
403
  print(f" Track ensemble failed for {order_id}: {track_e_err}")
404
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
405
  except Exception as e:
406
  import traceback
407
  print(f" Predict failed for {order_id}:\n{traceback.format_exc()}")
 
293
  for u, v in final_e:
294
  existing_edges.add((min(u, v), max(u, v)))
295
 
296
+ # Tight-confidence radius for accepting a track edge between two
297
+ # already-existing predicted vertices (topology correction).
298
+ tight_radius = 0.3
299
+
300
  for u_t, v_t in track_e:
301
  u_f = track_to_final.get(u_t)
302
  v_f = track_to_final.get(v_t)
303
+ if u_f is None or v_f is None or u_f == v_f:
304
+ continue
305
+ e = (min(u_f, v_f), max(u_f, v_f))
306
+ if e in existing_edges:
307
+ continue
308
+
309
+ u_is_new = u_f >= len(pred_v)
310
+ v_is_new = v_f >= len(pred_v)
311
+
312
+ if u_is_new or v_is_new:
313
+ # New vertex: trust the track edge.
314
+ final_e.append(e)
315
+ existing_edges.add(e)
316
+ continue
317
+
318
+ # Both endpoints are existing predicted vertices — only accept the
319
+ # track edge as a topology correction when both are very confidently
320
+ # matched (tight distance). This recovers missing edges without
321
+ # letting the tracker rewire the learned topology.
322
+ d_u = dists[u_t] if u_t < len(dists) else np.inf
323
+ d_v = dists[v_t] if v_t < len(dists) else np.inf
324
+ if d_u <= tight_radius and d_v <= tight_radius:
325
+ final_e.append(e)
326
+ existing_edges.add(e)
327
+
328
  return np.array(final_v), final_e
329
 
330
  # ---------------------------------------------------------------------------
 
417
  from triangulation import predict_wireframe_tracks
418
  # Use min_views=3 for highly precise, conservative geometric tracks
419
  track_v, track_e = predict_wireframe_tracks(sample, min_views=3)
420
+
421
  pred_v, pred_e = hybrid_merge(pred_v, pred_e, track_v, track_e, merge_radius=0.8)
422
  except Exception as track_e_err:
423
  print(f" Track ensemble failed for {order_id}: {track_e_err}")
424
+
425
+ # COLMAP multi-view plane refinement: snaps each vertex to the
426
+ # roof plane visible at its 2D projection. Targets corner_f1.
427
+ try:
428
+ if isinstance(pred_v, np.ndarray) and len(pred_v) >= 1:
429
+ from colmap_refine import refine_vertices_multiview_plane
430
+ refined, snapped_mask = refine_vertices_multiview_plane(
431
+ pred_v, sample,
432
+ knn_2d_px=30.0, knn_k=12, min_neighbours=6,
433
+ max_displacement=0.5, min_quality=0.5, min_views=2,
434
+ )
435
+ if snapped_mask.any():
436
+ pred_v = refined
437
+ except Exception as refine_err:
438
+ print(f" COLMAP refine failed for {order_id}: {refine_err}")
439
+
440
+ # Junction constraints: drop collinear/duplicate/short-leaf edges
441
+ try:
442
+ if isinstance(pred_v, np.ndarray) and len(pred_v) >= 2 and len(pred_e) >= 1:
443
+ from junction import apply_junction_constraints
444
+ jv, je = apply_junction_constraints(
445
+ pred_v, pred_e,
446
+ collinear_cos=0.97,
447
+ duplicate_cos=0.985,
448
+ leaf_min_len=0.4,
449
+ max_passes=3,
450
+ )
451
+ # Only accept if at least one edge survives — never let
452
+ # the post-processing reduce us to an empty graph.
453
+ if len(je) >= 1:
454
+ pred_v, pred_e = jv, je
455
+ except Exception as junc_err:
456
+ print(f" Junction constraints failed for {order_id}: {junc_err}")
457
+
458
  except Exception as e:
459
  import traceback
460
  print(f" Predict failed for {order_id}:\n{traceback.format_exc()}")