s23-model / sklearn_submission_diff.txt
IhorIvanyshyn01's picture
Deploy learned baseline + hybrid multi-view tracking ensemble
7df6a88
--- /Users/ihorivanyshyn/Documents/S23DR/s23dr-2026-submission/sklearn_submission.py 2026-04-26 12:52:14
+++ /Users/ihorivanyshyn/Documents/S23DR/handcrafted_submission_2026/sklearn_submission.py 2026-05-06 12:53:15
@@ -84,7 +84,8 @@
# Stage 1 alone regressed in v16, but with DGCNN refinement the surviving
# candidates have median distance ~0.3 m to GT (vs ~1 m raw).
# v17 DGCNN vertex refinement — marginal on 100-sample sweep
-# (ΔHSS +0.001 at best). Disabled by default.
+# (ΔHSS +0.001 at best). Disabled by default. Keep this conservative:
+# adding/removing vertices has a larger blast radius than adding edges.
USE_DGCNN_REFINEMENT = False
DGCNN_CLS_THRESHOLD = 0.5
DGCNN_DEDUP_RADIUS = 0.5
@@ -100,7 +101,15 @@
# t=0.7 +0.0039 (peak) t=0.8 +0.0031
# Clean signal: F1 stable (±0.0006), IoU +0.0065 at t=0.7.
USE_DGCNN_EDGES = True
-DGCNN_EDGE_THRESHOLD = 0.7
+# Ask the edge model for a wider candidate set, then apply our own
+# geometry gates below. This recovers medium-confidence true edges without
+# letting the classifier densify the graph unchecked.
+DGCNN_EDGE_THRESHOLD = 0.55
+DGCNN_EDGE_STRONG_THRESHOLD = 0.70
+DGCNN_EDGE_VERY_STRONG_THRESHOLD = 0.88
+DGCNN_EDGE_MAX_LENGTH = 8.0
+DGCNN_EDGE_MAX_PER_VERTEX = 2
+DGCNN_EDGE_REPROJ_DILATE_PX = 4
# v16: 3D vertex candidates from the S23DR 2025 winner Stage 1 — DISABLED.
# Raw cluster centroids without PointNet Stage 2 refinement have median
@@ -191,7 +200,7 @@
device = "cuda" if _torch.cuda.is_available() else "cpu"
except Exception:
device = "cpu"
- _DGCNN_EDGE_MODEL = load_edge_model("checkpoints/edge_model_dgcnn.pt", device=device)
+ _DGCNN_EDGE_MODEL = load_edge_model("edge_model_dgcnn.pt", device=device)
return _DGCNN_EDGE_MODEL
@@ -214,7 +223,7 @@
device = "cuda" if _torch.cuda.is_available() else "cpu"
except Exception:
device = "cpu"
- _DGCNN_VERTEX_MODEL = load_vertex_model("checkpoints/vertex_model_dgcnn.pt", device=device)
+ _DGCNN_VERTEX_MODEL = load_vertex_model("vertex_model_dgcnn.pt", device=device)
return _DGCNN_VERTEX_MODEL
# v7: ensemble with the standalone tracks-based predictor.
@@ -500,8 +509,96 @@
if ok_views >= min_views:
return True
return ok_views >= min_views
+
+
+def _passes_dgcnn_edge_gates(
+ v1: np.ndarray,
+ v2: np.ndarray,
+ prob: float,
+ all_xyz: np.ndarray,
+ kd_tree=None,
+ masks: dict | None = None,
+ views: dict | None = None,
+) -> bool:
+ """Conservative accept rule for learned edge candidates.
+
+ The DGCNN classifier is useful for recall, but raw learned edges can hurt
+ IoU if accepted without geometry. Strong candidates need COLMAP support;
+ very strong candidates may pass with looser sparse support; medium
+ candidates must also reproject onto gestalt edge pixels.
+ """
+ length = float(np.linalg.norm(v2 - v1))
+ if length < 0.25 or length > DGCNN_EDGE_MAX_LENGTH:
+ return False
+
+ strong_support = validate_edge(
+ v1, v2, all_xyz, kd_tree,
+ n_samples=24, radius=0.45, min_ratio=0.55,
+ )
+ if prob >= DGCNN_EDGE_STRONG_THRESHOLD and strong_support:
+ return True
+
+ loose_support = validate_edge(
+ v1, v2, all_xyz, kd_tree,
+ n_samples=24, radius=0.60, min_ratio=0.35,
+ )
+ if prob >= DGCNN_EDGE_VERY_STRONG_THRESHOLD and loose_support:
+ return True
+
+ if prob >= DGCNN_EDGE_STRONG_THRESHOLD and loose_support and masks and views:
+ return validate_edge_reprojection(
+ v1, v2, masks, views,
+ n_samples=24, min_views=1, min_hit_frac=0.35,
+ )
+
+ return False
+
+
+def _select_dgcnn_edges(
+ final_v: np.ndarray,
+ final_e: list,
+ dgcnn_edges: list,
+ all_xyz: np.ndarray,
+ kd_tree=None,
+ masks: dict | None = None,
+ views: dict | None = None,
+) -> list[tuple[int, int]]:
+ """Filter and degree-cap DGCNN edge proposals.
+ Existing edges are never removed here. At most
+ ``DGCNN_EDGE_MAX_PER_VERTEX`` learned edges are added at each vertex,
+ prioritising higher classifier probabilities.
+ """
+ existing = {tuple(sorted(e)) for e in final_e}
+ candidates = []
+ for i, j, prob in dgcnn_edges:
+ lo, hi = (int(i), int(j)) if i < j else (int(j), int(i))
+ if lo == hi or (lo, hi) in existing:
+ continue
+ prob = float(prob)
+ if _passes_dgcnn_edge_gates(
+ final_v[lo], final_v[hi], prob,
+ all_xyz, kd_tree, masks=masks, views=views,
+ ):
+ candidates.append((prob, lo, hi))
+ candidates.sort(reverse=True)
+ added_per_vertex = np.zeros(len(final_v), dtype=np.int32)
+ accepted: list[tuple[int, int]] = []
+ accepted_set = set()
+ for prob, lo, hi in candidates:
+ if (lo, hi) in accepted_set:
+ continue
+ if (added_per_vertex[lo] >= DGCNN_EDGE_MAX_PER_VERTEX
+ or added_per_vertex[hi] >= DGCNN_EDGE_MAX_PER_VERTEX):
+ continue
+ accepted.append((lo, hi))
+ accepted_set.add((lo, hi))
+ added_per_vertex[lo] += 1
+ added_per_vertex[hi] += 1
+ return accepted
+
+
def validate_edge(v1, v2, all_xyz, kd_tree=None, n_samples=20, radius=0.35, min_ratio=0.70):
"""Check if edge v1→v2 is supported by COLMAP point cloud.
@@ -1051,9 +1148,11 @@
if len(final_v) < 2 or len(final_e) < 1:
return empty_solution()
- # v18: DGCNN edge classifier — placed AFTER prune_not_connected so
- # that the vertex set is already fixed (no ghost vertices rescued by
- # spurious DGCNN edges). Only adds edges between surviving vertices.
+ # v19: guarded DGCNN edge rescue. The learned model is queried at a
+ # recall-friendly threshold, but new edges are accepted only if they
+ # also have sparse-cloud or reprojection evidence, then degree-capped.
+ # This targets the main weakness of v18: useful classifier recall
+ # without raw learned edges turning roofs into dense graphs.
if USE_DGCNN_EDGES and len(final_v) >= 2:
edge_model = _get_dgcnn_edge_model()
if edge_model is not None:
@@ -1075,11 +1174,24 @@
threshold=DGCNN_EDGE_THRESHOLD,
)
if dgcnn_edges:
- existing = set(tuple(sorted(e)) for e in final_e)
- for i, j, prob in dgcnn_edges:
- lo, hi = (i, j) if i < j else (j, i)
- if (lo, hi) not in existing:
- final_e.append((lo, hi))
+ masks, mvs_views = {}, {}
+ try:
+ masks, mvs_views = _build_gestalt_edge_masks(
+ entry, dilate_px=DGCNN_EDGE_REPROJ_DILATE_PX,
+ )
+ except Exception:
+ pass
+ extra = _select_dgcnn_edges(
+ np.asarray(final_v, dtype=np.float64),
+ final_e,
+ dgcnn_edges,
+ all_xyz,
+ kd_tree,
+ masks=masks,
+ views=mvs_views,
+ )
+ if extra:
+ final_e.extend(extra)
except Exception:
pass