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>
114 lines
3.9 KiB
Bash
114 lines
3.9 KiB
Bash
#!/usr/bin/env bash
|
|
# SUM Parts - build only the CUDA extensions (rerunnable)
|
|
#
|
|
# Split out of setup_pointnext.sh so a failed compile can be retried without
|
|
# reinstalling every python dependency.
|
|
#
|
|
# ninja note: torch 2.0's cpp_extension shells out to `ninja -v` and reads its
|
|
# output through a pipe. ninja >= 1.12 dies with SIGPIPE there, which torch
|
|
# surfaces only as the useless "Error compiling objects for extension".
|
|
# Pinning ninja to 1.11.x avoids it.
|
|
set -euo pipefail
|
|
|
|
CONDA_ROOT="$HOME/miniconda3"
|
|
ENV_NAME="sumparts"
|
|
REPO="$HOME/sum-parts/semantic_segmentation/PointNeXt_bundle"
|
|
|
|
source "$CONDA_ROOT/etc/profile.d/conda.sh"
|
|
conda activate "$ENV_NAME"
|
|
|
|
export TORCH_CUDA_ARCH_LIST="8.6" # RTX 3060
|
|
export CUDA_HOME="$CONDA_PREFIX"
|
|
export PATH="$CUDA_HOME/bin:$PATH"
|
|
export MAX_JOBS="${MAX_JOBS:-4}"
|
|
|
|
echo "=== pin ninja to 1.11.x ==="
|
|
pip install --no-cache-dir "ninja==1.11.1.1"
|
|
ninja --version
|
|
|
|
# subsampling/setup.py imports numpy.distutils, which imports
|
|
# distutils.msvccompiler. setuptools removed that module in 74.0, so anything
|
|
# newer dies with "No module named 'distutils.msvccompiler'".
|
|
echo "=== pin setuptools to 69.5.1 (numpy.distutils needs it) ==="
|
|
pip install --no-cache-dir "setuptools==69.5.1"
|
|
python -c "import setuptools; print('setuptools', setuptools.__version__)"
|
|
|
|
# Skip extensions that already import cleanly, so a retry does not redo a
|
|
# 5-minute nvcc pass that already succeeded. FORCE=1 rebuilds everything.
|
|
have() { python -c "import $1" 2>/dev/null; }
|
|
|
|
# NOTE: pointnet2_batch's setup.py names the extension pointnet2_batch_cuda,
|
|
# not pointnet2_cuda as in upstream PointNet++ forks.
|
|
if [ "${FORCE:-0}" = "1" ] || ! have pointnet2_batch_cuda; then
|
|
echo "=== build pointnet2_batch ==="
|
|
cd "$REPO/openpoints/cpp/pointnet2_batch"
|
|
rm -rf build ./*.egg-info dist
|
|
python setup.py install
|
|
else
|
|
echo "=== skip pointnet2_batch (already importable) ==="
|
|
fi
|
|
|
|
echo "=== build subsampling ==="
|
|
cd "$REPO/openpoints/cpp/subsampling"
|
|
rm -rf build ./*.so
|
|
python setup.py build_ext --inplace
|
|
|
|
if [ "${FORCE:-0}" = "1" ] || ! have pointops_cuda; then
|
|
echo "=== build pointops ==="
|
|
cd "$REPO/openpoints/cpp/pointops"
|
|
rm -rf build ./*.egg-info dist
|
|
python setup.py install
|
|
else
|
|
echo "=== skip pointops (already importable) ==="
|
|
fi
|
|
|
|
# chamfer_dist and emd only matter for reconstruction tasks, but
|
|
# openpoints/models/__init__.py imports .reconstruction unconditionally, which
|
|
# imports chamfer_dist -- so even a pure segmentation run fails at import time
|
|
# without them. They are not optional in practice.
|
|
if [ "${FORCE:-0}" = "1" ] || ! have chamfer; then
|
|
echo "=== build chamfer_dist ==="
|
|
cd "$REPO/openpoints/cpp/chamfer_dist"
|
|
rm -rf build ./*.egg-info dist
|
|
python setup.py install
|
|
else
|
|
echo "=== skip chamfer_dist (already importable) ==="
|
|
fi
|
|
|
|
# emd/setup.py names the package emd_ext and the extension emd_cuda; plain
|
|
# `emd` is only the python-level alias in openpoints/cpp/emd/__init__.py.
|
|
if [ "${FORCE:-0}" = "1" ] || ! have emd_cuda; then
|
|
echo "=== build emd ==="
|
|
cd "$REPO/openpoints/cpp/emd"
|
|
rm -rf build ./*.egg-info dist
|
|
python setup.py install
|
|
else
|
|
echo "=== skip emd (already importable) ==="
|
|
fi
|
|
|
|
echo "=== verify ==="
|
|
cd "$REPO"
|
|
python - <<'PY'
|
|
import sys
|
|
print("torch :", __import__("torch").__version__,
|
|
"| cuda", __import__("torch").cuda.is_available())
|
|
ok = True
|
|
for m in ["pointnet2_batch_cuda", "pointops_cuda", "chamfer", "emd_cuda",
|
|
"torch_scatter", "plyfile"]:
|
|
try:
|
|
__import__(m)
|
|
print(f"{m:22s}: OK")
|
|
except Exception as e:
|
|
ok = False
|
|
print(f"{m:22s}: FAIL {type(e).__name__}: {e}")
|
|
try:
|
|
from openpoints.cpp.subsampling import grid_subsampling # noqa: F401
|
|
print(f"{'grid_subsampling':22s}: OK")
|
|
except Exception as e:
|
|
ok = False
|
|
print(f"{'grid_subsampling':22s}: FAIL {type(e).__name__}: {e}")
|
|
sys.exit(0 if ok else 1)
|
|
PY
|
|
|
|
echo "BUILD DONE"
|