Handoff · Zettel: why tablecloth defaults to SMRF over CSF
Continuation target
Write one atomic Zettelkasten note in Miro's vault capturing why the
tablecloth point-cloud ground filter ships mechanism=smrf_numpy as the
production default and keeps csf_cloth as an opt-in A/B path. One idea per note:
the choice was made on controllability and lip safety, not on compute cost, and the empirical
gate that would promote CSF was never run. Link it to the neighbouring notes (tablecloth,
SMRF, CSF, AI3D-337 slope gate, pavement-lip constraint) and cite the primary sources listed below.
Current state
- Research is done. Everything needed for the note was gathered on 2026-09-04 from the repo, the July plan page, the stored run summaries, and one fresh timing measurement. Nothing else needs to be re-derived; the next agent's job is to write, link, and file the note.
- Where the decision lives today:
docs/plans/v1-implementation-plan.mdin the tablecloth repo, sections "Key decisions (locked)" (items 1 to 3), "Why not PDAL" and "Why CSF is optional / off by default". The README's "Mechanisms (A/B toggles)" table restates the default. No ADR or vault note exists yet for this decision. - A related vault note already exists: the July plan page cites a vault note titled "Tablecloth slope-handling deep research" (AI3D-337, 2026-07-17). The new zettel should link to it. Its path in the vault was not located this session (see risks).
- Unrelated in-flight work, do not touch: branch
worktree-ai3d-373-surface-ply(PLY export of the ground surface) is mid-review in the worktree.claude/worktrees/ai3d-373-surface-ply.
The decision, distilled (source material for the note)
Write this in your own words; the bullets are facts, not prose to copy.
What was decided (v1 plan, locked 2026-07-17)
- Production default =
smrf_numpy: a Pingel-2013-style Simple Morphological Filter on a fine min-Z grid (0.20 m cells), NumPy/SciPy only, no native dependencies. - Optional =
csf_cloth: wraps the upstreamcloth-simulation-filterC++/SWIG wheel (Zhang et al. 2016), declared as the[csf]extra, off unless--set mechanism=csf_cloth. - Single string field
mechanism ∈ {none, smrf_numpy, csf_cloth}; no per-mechanism booleans. - Promotion gate written into the plan: "promote CSF only if segment 65 lip acceptance matches or beats SMRF". never run
Why (in the plan's own priority order)
- Lip-first acceptance. The hard requirement is preserving a 1 to 2 cm curb lip or flush
shoulder that the downstream asphalt-edge detector needs. SMRF's parameters map directly onto that
scale:
cell_m=0.20,max_elev_diff_m=0.05,slope_threshold=0.15,smrf_max_window_m=3.0. CSF'sclass_thresholdis a distance to a simulated cloth whose shape depends oncloth_resolution,rigidness,time_stepand iteration count, which is harder to reason about at centimetre scale. Plan wording: "production must be lip-safe". - Concave terrain. A cloth is a stiff lower envelope; it tends to bridge ditches, trenches
and the foot of a curb, and on steep grades needs the upstream
bSloopSmoothpost-process. SMRF's progressive opening with a slope-scaled tolerance follows those features. This is a design argument in the plan, not a measured result. - Extensibility. SMRF exposes an explicit surface and, since AI3D-337, its gradient. That
enabled the slope-scaled gate (
elev_scalar), seed-grid edge padding (smrf_edge_pad_enabled) and pit filling. CSF is a black box with six parameters. - Reproducibility and dependencies. Plan wording: "reproducible without native wheels". SMRF is pure NumPy/SciPy and bit-deterministic. CSF is a compiled wheel and was observed (2026-09-04, Opus review probe) to produce slightly different masks on repeated runs over identical input.
- No PDAL. Both paths were chosen to avoid PDAL, which could not be installed locally or in CI. This ruled out PDAL's SMRF implementation and motivated the NumPy re-implementation.
What the decision was not based on
- Not compute. Measured 2026-09-04 on battlebox, segment 071 Record002, 1,644,491 points, classification only: SMRF 0.41 s, CSF 0.20 s, peak RSS 0.32 GB for both together. CSF cost scales with cloth particles times iterations, barely with point count.
- Not a head-to-head result. All 148 stored production records and all 13 A/B runs in
/home/ai/ai3d-ab/usedsmrf_numpy. The only real-data A/B ever done (AI3D-337) compared SMRF settings against each other. On the one record probed today the two mechanisms agreed on 99.7% of points (SMRF-only kept 3,895, CSF-only kept 1,408).
Trivia worth one line
- The repo name "tablecloth" is the cloth metaphor, but the production "table" is the SMRF min-Z grid after morphological opening, not a simulated cloth. The plan calls CSF "namesake nostalgia".
- Until AI3D-373 the CSF binding silently wrote
cloth_nodes.txtinto the process CWD on every run, because itsdo_filteringdefaultsexportCloth=True.
Evidence table (for the note's "sources" footer)
| Claim | Source | Status |
|---|---|---|
| SMRF default, CSF optional, promotion gate | docs/plans/v1-implementation-plan.md "Key decisions (locked)", "Why CSF is optional / off by default" | verified |
| Lip-first priority and thresholds | README "Pavement-lip constraint"; config.py defaults | verified |
| No PDAL locally | v1 plan "Why not PDAL" | verified |
| Slope gate, edge pad built on SMRF surface | AI3D-337 plan page (URL below); ground.py | verified |
| Timing 0.41 s vs 0.20 s, 99.7% agreement | Fresh measurement 2026-09-04, script reproduced below | measured once |
| CSF nondeterministic run to run | Opus review probe 2026-09-04 (main-vs-main masks differed) | observed, not characterised |
| Cloth bridges ditches / curb foot | Design argument in v1 plan; general CSF literature | not measured on this data |
| Segment 65 gate never run; no CSF results anywhere | All out/*/run_summary.json and /home/ai/ai3d-ab/*/run_summary.json show smrf_numpy | verified |
Timing script used (run from the tablecloth repo, needs the CSF extra):
uv run --extra csf python - <<'EOF'
import time, numpy as np
from iolabs_point_cloud_tablecloth.config import load_config
from iolabs_point_cloud_tablecloth.ground import classify_ground
src = "/home/ai/dev/3dai.lanefinder/data/00_external/260416_Abschnitt_4_5/lane_points/segment_071/25-9166-A4-5Record002_run3_points.npz"
pts = np.load(src)["points"].astype(np.float64)
for mech in ["smrf_numpy", "csf_cloth"]:
cfg = load_config({"mechanism": mech, "elev_scalar": 1.25, "smrf_edge_pad_enabled": True})
t = time.perf_counter(); kept = classify_ground(pts, cfg)
print(mech, f"{time.perf_counter()-t:.2f}s kept={kept.mean():.3f}")
EOF
Key artifacts
- Repo:
/home/ai/dev/3dai.iolabs.pointcloud.tablecloth(branchmain, HEADc9d9f9aon 2026-09-04). Remotegit@bitbucket.org:ioholding/3dai.iolabs.pointcloud.tablecloth.git. - Decision text:
docs/plans/v1-implementation-plan.md. Mechanism table and lip constraint:README.md. - Code:
src/iolabs_point_cloud_tablecloth/ground.py(SMRF core,_csf_classifyadapter),config.py(Mechanismliteral, defaults). - Published AI3D-337 slope-gate plan with A/B renders: miro-plans-battlebox.pages.dev/ai3d-337-slope-handling-20260717 (SMRF vs SMRF only; see its "How to read a render" paragraph).
- A/B run directory (battlebox):
/home/ai/ai3d-ab/(renders, run summaries,profile_plot.py,precision_check.py). - Source data used for the timing:
/home/ai/dev/3dai.lanefinder/data/00_external/260416_Abschnitt_4_5/lane_points/. - Literature handles: Pingel, Clarke & McBride 2013, "An improved simple morphological filter for the
terrain classification of airborne LIDAR data" (SMRF; PDAL default
scalar=1.25). Zhang et al. 2016, "An Easy-to-Use Airborne LiDAR Data Filtering Method Based on Cloth Simulation" (CSF). Verify exact citations before filing. - Tickets: AI3D-337 (v1 + slope gate), AI3D-345 (slope-gate A/B commits), AI3D-373 (PLY export, in progress).
Next steps
- Locate the vault and its conventions: ID scheme (timestamp vs Luhmann), frontmatter fields, tag style,
link syntax. Find the existing note "Tablecloth slope-handling deep research" and mirror its format.
No
.obsidiandirectory was found under/home/ai,/mnt/c/Usersor/mnt/dat depth 4 on 2026-09-04, so ask Miro for the path if it is not obvious. - Draft the note. Suggested skeleton: title as a full-sentence claim ("Tablecloth defaults to SMRF over CSF for controllability and lip safety, not compute"); one paragraph stating the claim; the five reasons as short bullets; a "what would change this" line (run the segment 65 lip gate, or an SMRF-vs-CSF A/B on segments 066/071); links to neighbour notes; sources footer from the evidence table; date and origin.
- Keep it atomic. Anything about the AI3D-337 slope gate itself, the PLY export, or CSF's cwd side effect belongs in separate notes; link, do not inline.
- Mark inferred vs verified claims exactly as the evidence table does. The "cloth bridges ditches" argument and CSF nondeterminism are not measured on this data.
- Optionally verify the two literature citations with the
researchskill before filing. - File the note, update any index or map-of-content note, and report the note's ID and path.
Risks and open questions
- Vault location and format unknown to the session that wrote this handoff. Do not invent a convention; match the existing "Tablecloth slope-handling deep research" note.
- Single-record, single-machine timing. The 0.41 s vs 0.20 s figure is one record on one machine. Present it as an order-of-magnitude fact ("both sub-second, CSF not slower"), not a benchmark.
- The decision is untested against CSF. State plainly that the promotion gate was never run. Miro was offered an SMRF-vs-CSF A/B on segments 066/071 on 2026-09-04; if it has since been run, link the results instead of the offer.
- Do not commit into the tablecloth repo as part of this task. The note lives in the vault.
If Miro later wants an ADR in the repo, that is a separate task on
main.
Suggested skills
domain-modelingif Miro decides the decision should also become a repo ADR.researchonly to confirm the two paper citations.