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>
57 lines
1.8 KiB
Bash
57 lines
1.8 KiB
Bash
#!/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
|