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:
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env bash
|
||||
# SUM Parts - map the per-class IoU array onto class names
|
||||
#
|
||||
# The log prints a bare numpy array. Guessing whether it starts at class 0 or
|
||||
# class 1 by eye is how you end up reporting "car IoU 95.6". Count it.
|
||||
set -uo pipefail
|
||||
|
||||
LOG="${1:-$HOME/sum-parts/runs/val_ab/val_capped.log}"
|
||||
|
||||
source "$HOME/miniconda3/etc/profile.d/conda.sh"
|
||||
conda activate sumparts
|
||||
|
||||
python - "$LOG" <<'PY'
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
CLASSES = ['unclassified', 'terrain', 'high_vegetation', 'facade_surface',
|
||||
'water', 'car', 'boat', 'roof_surface', 'chimney', 'dormer',
|
||||
'balcony', 'roof_installation', 'wall']
|
||||
|
||||
text = Path(sys.argv[1]).read_text(encoding="utf-8", errors="replace")
|
||||
|
||||
m = re.findall(r"iou per cls is:\s*\[([^\]]*)\]", text)
|
||||
if not m:
|
||||
print("no 'iou per cls' line found in", sys.argv[1])
|
||||
raise SystemExit(1)
|
||||
|
||||
vals = [float(x) for x in m[-1].split()]
|
||||
print(f"file : {Path(sys.argv[1]).name}")
|
||||
print(f"entries: {len(vals)} (num_classes = {len(CLASSES)})")
|
||||
print()
|
||||
|
||||
if len(vals) == len(CLASSES):
|
||||
names = CLASSES
|
||||
note = "array covers classes 0..12"
|
||||
elif len(vals) == len(CLASSES) - 1:
|
||||
names = CLASSES[:-1]
|
||||
note = ("array is one short of num_classes. ConfusionMatrix remaps the "
|
||||
"ignore_index into slot num_classes-1, so the last class shares a "
|
||||
"bucket with ignored points and is dropped from the report.")
|
||||
else:
|
||||
names = [f"class_{i}" for i in range(len(vals))]
|
||||
note = "unexpected length -- names are positional only"
|
||||
|
||||
print(f"note : {note}\n")
|
||||
print(f" {'#':>2} {'class':<20} {'IoU':>7}")
|
||||
for i, (n, v) in enumerate(zip(names, vals)):
|
||||
mark = " <-- 0" if v == 0 else ""
|
||||
print(f" {i:>2} {n:<20} {v:>7.2f}{mark}")
|
||||
|
||||
nz = [v for v in vals if v > 0]
|
||||
print()
|
||||
print(f" non-zero classes : {len(nz)}/{len(vals)}")
|
||||
print(f" mean over all : {sum(vals)/len(vals):.2f}")
|
||||
PY
|
||||
Reference in New Issue
Block a user