Files
nbrightandClaude Opus 5 609d9a6972 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>
2026-08-21 10:29:25 +09:00

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