Six papers converted to Markdown with the local doc2md tool, figures extracted
and annotated. The tool's venv had a CPU-only torch, so marker-pdf silently ran
on CPU and stalled; swapping in 2.5.1+cu121 dropped a paper from "hung after six
minutes" to three. Gemini then described all 86 figures in place, below each
original caption.
The PDFs themselves are gitignored - 88 MB of public arXiv downloads that
convert_papers.sh regenerates. The .md and figures are tracked, because the
annotations took a separate pass and do not reproduce byte-for-byte.
sum-parts-explained.html gains two tabs:
- PointVector. Why representing a scalar feature as a rotated 3D vector buys
anisotropic aggregation without attention's cost, and why the paper predicts
two independent angles rather than a rotation matrix whose nine elements are
interdependent.
- Bare Earth. Reframes the task as ground vs not-ground, and separates the five
boundaries by their nature. Four of them are cuts; the slope boundary is the
one that must NOT be cut, which is why "horizontal means ground" destroys road
cut and fill. Notes that SUM Parts is flat Helsinki and cannot teach slopes at
all, so that part needs a geometric filter rather than more training.
NEXT.md carries the goal forward: separate bare earth from the rest as OBJ
meshes, then reclassify the remainder. Removing the ground first is sound -
it is 24-40% of the points, and without it the remaining objects fall apart
into separate connected components instead of being joined through the floor.
The gap that blocks step 4 is named: mesh_to_ply.py samples points without
recording which face each came from, so there is no way back to the mesh yet.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds STATUS.md as the handoff document: benchmark numbers, the bare-earth
metrics that actually matter for this project, the Korean-data domain gap that
retraining will not fix, and what to do on the 24 GB machine.
The memory problem was in how a prediction's colours were turned back into
class indices. Every consumer built an (N, 13, 3) float64 temporary:
d = ((rgb[:, None, :] - COLOR_MAP[None, :, :]) ** 2).sum(axis=2)
That is ~250 MB of intermediates per 800k-point tile, several live at once, and
a full 4.7M-point block pushes it into gigabytes. main.py writes exact palette
entries, so an exact hash lookup resolves nearly every point with no large
temporary; only leftovers fall back to a chunked distance search. Peak RSS on a
470k-point tile drops to 61 MB. Extracted to sumparts_palette.py and shared by
coarse_eval.py and split_by_class.py.
Also from this round:
- patch_cm_mutation.sh: ConfusionMatrix.update() rewrote the caller's pred
tensor in place, folding every ignore_index point into class num_classes-1.
test() saves its visualization from that same tensor afterwards, so an
unlabelled tile came out 100% wall and the model looked degenerate when it
was not.
- patch_class_mask.sh: SUMPARTS_MASK_CLASSES drops known-absent classes from
the argmax. Measured on Seosan and it does not help - the runner-up for
"water" is "wall", not "terrain" - but the experiment is worth keeping.
- split_by_class.py now writes .ply alongside .obj. A vertex-only OBJ has zero
faces and most viewers render nothing, which is why the first export looked
broken.
- verify_outputs.sh reads exported files back with a parser, so "here are your
files" can be checked rather than asserted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The target machine's card is busy with someone else's job, so a single
end-to-end script stalls on work that does not actually need a GPU.
Compiling the CUDA extensions needs nvcc, not a device, and downloading 18 GB
of data needs neither. Those are the slow parts (~50 min + ~30 min), so phase A
now runs entirely without the card:
run_setup.sh bootstrap, conda, extensions, patches, data no GPU
run_train.sh voxel_max measurement, training, evaluation GPU
run_setup reports the GPU but never fails on it, and verify_env.py gained
SKIP_CUDA_CHECK so import coverage still runs when no device is visible.
TORCH_CUDA_ARCH_LIST is stated rather than probed, since the card may be
unavailable at build time.
run_train waits for the GPU instead of failing when it is busy: it polls until
enough VRAM frees up (12h default), so it can be queued ahead of time. Past the
deadline it proceeds anyway and lets the measured voxel_max adapt to whatever
is actually free.
keepalive.sh now takes the phase to supervise. Replaces run_all.sh and RUN.md
with SETUP.md and TRAIN.md. Adds selfcheck.sh, which syntax-checks every script
and flags CRLF endings - a shell script with either fails at its first line,
which for an unattended weekend run means losing the weekend.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
SETUP.md walks the phases one at a time, which is what you want when something
broke. It is the wrong document to hand someone leaving for the weekend.
RUN.md is the three commands to run before walking away, what each layer of
retry covers, and what to read on Monday. It states plainly which single step
cannot be automated (the HuggingFace gate needs a browser) and that the gate is
per-account, so copying the token is enough.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Built for running over a weekend with nobody at the keyboard.
run_all.sh chains every phase from bare machine to trained model and retries
each one with exponential backoff. Preflight is deliberately not retried: a
missing HF token or absent GPU will not fix itself, and burning the weekend on
a doomed retry loop is worse than failing in the first minute.
keepalive.sh sits above it and relaunches run_all if the process disappears
entirely (VM restart, OOM kill). Safe because every phase is idempotent -- a
restart re-checks what is already done and continues, and do_train hands the
newest checkpoint to the watchdog so training resumes instead of starting over.
voxel_max is measured rather than assumed: candidates are tried high to low and
the first that actually fits in VRAM wins. On WSL2 an oversized value does not
OOM, it silently spills to host RAM at 25-100x the cost, so peak allocation is
checked instead of trusting that the run worked.
RUN_ALL_DRYRUN runs preflight alone, to prove the checks pass before leaving.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
SETUP.md sent a fresh machine straight to setup_env.sh, which calls conda and
builds inside ~/sum-parts. Neither exists on a clean box, so the first command
failed. This repo only carries our scripts; the benchmark itself is a separate
clone.
bootstrap.sh installs miniconda into $HOME (no sudo), clones
tudelft3d/SUM-Parts-Benchmarks, and reports whether the GPU has room for the
paper's voxel_max=64000 (~16.5 GB). Idempotent - an existing install or clone
is reported and left alone.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>