Add SUM Parts reproduction and Seosan Myeongcheon application pipeline

Reproduces the SUM Parts (CVPR 2025) face-labeling benchmark on a single
consumer GPU, then applies it to drone-photogrammetry road survey meshes.

Verified on RTX 3060 12GB / WSL2 Ubuntu 22.04 / CUDA 11.8 / torch 2.0.1:
- CUDA extensions build (pointnet2_batch, pointops, chamfer_dist, emd,
  subsampling)
- PointNet 100 epochs reaches mIoU 17.19, matching the paper's reported 15.1
- OBJ -> PLY conversion round-trips through the model and yields per-point
  predictions

Four upstream source patches, all idempotent, originals preserved:
- numpy aliases removed in 1.24 (np.long etc.) and collections ABCs moved in
  python 3.10
- the blind test split ships label = -1, which crashed ConfusionMatrix
- mode=val referenced `epoch` before assignment

Documents the traps that cost the most time, including VRAM overflow silently
falling back to host RAM on WSL2 (25-100x slowdown, no OOM) and the colour
scale mismatch between r/g/b float32 and red/green/blue uint8.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
nbright
2026-08-21 10:29:25 +09:00
co-authored by Claude Opus 5
commit 609d9a6972
52 changed files with 5463 additions and 0 deletions
+100
View File
@@ -0,0 +1,100 @@
#!/usr/bin/env python3
"""Check that every compiled extension and import the sumv2 training path needs
is actually present.
Extension import names do not match their directory names, which is an easy way
to waste an hour chasing a build that already succeeded:
openpoints/cpp/pointnet2_batch -> pointnet2_batch_cuda
openpoints/cpp/pointops -> pointops_cuda
openpoints/cpp/chamfer_dist -> chamfer
openpoints/cpp/emd -> emd_cuda (package name: emd_ext)
openpoints/cpp/subsampling -> openpoints.cpp.subsampling.grid_subsampling
Run from PointNeXt_bundle/ (or anywhere, if openpoints is importable).
"""
import os
import sys
from pathlib import Path
def add_openpoints_to_path() -> Path | None:
"""Put PointNeXt_bundle on sys.path.
main.py does this itself with a hardcoded '../../', but this script lives
outside the repo, so walk up from cwd (then from the default clone path)
until a directory containing openpoints/ turns up.
"""
candidates = [Path.cwd(), *Path.cwd().parents,
Path.home() / "sum-parts/semantic_segmentation/PointNeXt_bundle"]
for c in candidates:
if (c / "openpoints" / "__init__.py").exists():
sys.path.insert(0, str(c))
return c
return None
MODULES = [
"torch",
"numpy",
"pointnet2_batch_cuda",
"pointops_cuda",
"chamfer",
"emd_cuda",
"torch_scatter",
"plyfile",
"wandb",
"trimesh",
]
FROM_IMPORTS = [
("openpoints.cpp.subsampling", "grid_subsampling"),
("openpoints.models", "build_model_from_cfg"),
("openpoints.dataset", "build_dataloader_from_cfg"),
]
def main() -> int:
ok = True
root = add_openpoints_to_path()
print(f"openpoints root: {root or 'NOT FOUND'}")
if root is None:
ok = False
try:
import torch
print(f"torch {torch.__version__} | cuda {torch.version.cuda} | "
f"available {torch.cuda.is_available()}")
if torch.cuda.is_available():
print(f"device: {torch.cuda.get_device_name(0)}")
except Exception as e: # noqa: BLE001
print(f"torch import failed: {e}")
return 1
print()
for name in MODULES:
try:
__import__(name)
print(f"{name:32s} OK")
except Exception as e: # noqa: BLE001
ok = False
print(f"{name:32s} FAIL {type(e).__name__}: {e}")
print()
for mod, attr in FROM_IMPORTS:
try:
m = __import__(mod, fromlist=[attr])
getattr(m, attr)
print(f"{mod + '.' + attr:32s} OK")
except Exception as e: # noqa: BLE001
ok = False
print(f"{mod + '.' + attr:32s} FAIL {type(e).__name__}: {e}")
print()
print("ALL OK" if ok else "SOME CHECKS FAILED")
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(main())