seg3d production 260827 — A1 + A4_5

2026-08-27 · for Miro · Jira AI3D-369 · repo 3dai.iolabs.pointcloud.3dsegmentation @ 22610f0

Summary

Re-produce the pre-annotation LAS for Abschnitt 1 (branch_000 · 131 segs, branch_001 · 11 segs) first, then Abschnitt 4_5 (143 segs). Only the guardrail detector is rerun (main @ ffc9ab5, AI3D-360 V-support separation: posts → class 72, median box tube → class 74). Asphalt edges, ground (TCS), lane-line clusters, signs/trees/delineators and the A1 step-9 meshes are reused byte-for-byte from the 260812 production — all 285 segments verified present on disk.

Fusion defaults change (AI3D-369): 1 cm voxel (was 1/15 m), the voxel→full-resolution back-map npz is always written (already true on main), and every per-segment file gets a date_dataset_branch_segment name, e.g. 260827_A1_b001_segment_005_seg3d.las. Class codes already follow the ReCap scheme (ground 2, asphalt 11, tree 5, 64–70 custom, 72 guardrail_support, 73 vehicle, 74 guardrail_top_rail) — nothing to change there.

Pilot first: A1 branch_001 segment_005 (middle of the short branch) goes through the whole chain and lands on N: before the sweep starts. Two blockers found during exploration: the sign JSON-paint fix (branch fix/AI3D-351-paint-json-sign-detections, commit 6cb0c84) is not in main and conflicts with the AI3D-360 work — without it the 2,285 rail half-posts and rescued delineators vanish again; and 1 cm keeps ~57 % of all points (measured), so outputs grow ~9× (main LAS 14 GB → ~130 GB, ~260 GB with instance split).

Key decisions

TopicDecisionWhy
Reuse vs rerun Reuse /home/ai/seg3d_out/{edges_final_A1B0,edges_final_A1B1,edges_A45,ground_*,signs2_*}, the clusters_mask/ trees in /home/ai/seg3d_stage_260807b, and A1 meshes (mesh_regen_* + N: copy). Rerun guardrails only, then fusion. Per Miro. Verified: 131/11/143 segment dirs in every reused stage (A1B1 edges = 10 by design, seg 010 is beyond the axis).
Do not rerun verticalsigns against the new guardrails signs2 outputs stay as produced 260812 (old guardrail models). railpost.py:434-447 reads every entry of guardrails[] with no type filter; new guardrail_support entries carry post centres as polyline → would be treated as rail centrelines and spawn spurious half-posts. Reusing avoids that silently-wrong result.
Guardrails pin Detached worktree /home/ai/dev/wt-gr-prod-260827 @ ffc9ab5 (= origin/main, v0.3.1), defaults as shipped (all AI3D-360 flags on, lateral gate off by design). No new CLI flags; same invocation as 260809 (--lane-xml, --dump-point-masks). Contains PR #3 zone-replay fix + PR #4/5/6 + round-7b review fixes; working tree clean. /home/ai/dev/gr_r7b is behind — never run from it.
Fusion pin Detached worktree /home/ai/dev/wt-seg3d-fuse-prod-260827 at the AI3D-369 commit after Phase 1. Main checkout is never used for production runs. 260812 trap: an agent git checkout in the editable install silently unpatched every fuse process started afterwards. Verify stats.json.params per segment after each sweep.
Sign JSON paint Rebase the 3 commits of fix/AI3D-351-paint-json-sign-detections onto main (conflicts in fuse.py, masks.py, config.py, fusion_spec.md, test_fusion.py; drop the junk files on that branch). Half-post cylinders paint unclassified points only and must run after guardrail sidecar + JSON paint so they never overwrite 72/74. Not a clean merge (30 commits behind, same fuse.py paint block rewritten by AI3D-360). Without it the LAS repeats the 260809 defect.
Voxel 1 cm default config.py:35 + seg3d.default.json0.01; DEFAULT_VOXEL follows. Keys are a bijective linear index — no collision/overflow at 1 cm. Measured retention 0.554 (A45/066) and 0.599 (A1B0/070) → ~3.6 G decimated points corpus-wide (9×). See Risks for size/RAM.
Naming scheme New --name-prefix (config key name_prefix, {date} → YYMMDD). Files become <prefix>_segment_NNN_seg3d.las|.npz|_classcolor.ply|_seg3d_voxelmap.npz|_stats.json and las_split/<prefix>_segment_NNN_inst0007_guardrail.las. Directory stays segment_NNN/. Production prefixes: 260827_A1_b000, 260827_A1_b001, 260827_A45_b000. ~15 lines in writer.py (one segment_basename() helper) + render.py + cli/config; recap_import/recap_cli are suffix-driven → unaffected. Two globs to fix: scripts/orbit_video.py:30, stage make_feature_table.py:48. Prefix must not contain a class token (sign/wall/tree/gate) — validator.
Class codes No change. Registry already: 1 unclassified · 2 ground · 11 asphalt · 5 tree · 64 solid · 65 dashed · 66 guardrail · 67 wall · 68 sign · 69 gate · 70 delineator · 72 guardrail_support · 74 guardrail_top_rail · 73 vehicle. AI3D-355/360/362 already landed. 71 (old ground) never written. Vehicle (73) is not in Miro's table — see Q5.
Lane XML A1: run7_lanes_branch_000_20260722-092426.xml / …branch_001_20260722-092434.xml — identical to what is already staged (same bytes as the N: 260722_run7_line_width files). A4_5: 260713_ML_Abschnitt_4_5\260721_run7_line_width\run7_lanes_20260721-120038.xml (1,710 <Width>), already staged; a newer 260723_step7_rerun_latest_packages\run7_lanes_20260723-163130.xml also carries widths (Q3). In this pipeline the XML only feeds guardrail lane zones (and is the fallback line source); lines come from clusters_mask/. Width is not read by seg3d.
Delivery N:\…\03_Working folder\260803_recap_annotation_Abschnitt_1\pre_annotation\260827_full_sweep\branch_000|branch_001\segment_NNN\ and …_Abschnitt_4_5\pre_annotation\260827_full_sweep\segments\segment_NNN\, plus README, las_validation.json, offaxis_segments.txt, feature_table.csv, instances.csv, qc_rasters. Pilot → …Abschnitt_1\pre_annotation\260827_pilot_b001_seg005\. Mirrors 260809/260812 layout beside which it lands; old sweeps stay.
Orchestration Fable plans/reviews; code work + QA via Workflow with Opus (rebase, naming) and Sonnet/Grok (mechanical, QA counting); Sol + Opus parallel review, Fable final pass. The sweeps themselves are deterministic run_stage.py shards (new stages guardrails3/fuse3) — no agents in the hot loop. Miro: preserve Fable tokens where frontier intelligence isn't needed.

Phases

Phase 0 — Pin & stage (≈ 30 min) low effort
  • Guardrails: git worktree add --detach /home/ai/dev/wt-gr-prod-260827 ffc9ab5 && uv sync; smoke --segments 005 on A1B1 and diff instance types against out_a45_r7b expectations (rails, supports, top rails; instance_json_index present).
  • Stage tree: keep /home/ai/seg3d_stage_260807b (symlinks into D: + real clusters_mask/). Re-validate the 3 root XML links and that /mnt/d/a123-data/Abschnitt_1/branch_00{0,1}/lane_points and /mnt/d/Data/02_AI 3D modeling/00_data/260416_Abschnitt_4_5/lane_points still resolve.
  • Outputs: /home/ai/seg3d_out/{guardrails3_<ds>,las3_<ds>}, manifests manifest_guardrails3.jsonl / manifest_fuse3.jsonl, logs under logs/. Disk: 562 GB free on /, 916 GB on D: — reserve D: (/mnt/d/seg3d_out_260827) as overflow if the 1 cm estimate is exceeded.
  • Extend run_stage.py: stages guardrails3 (REPOS["guardrails"] → new worktree) and fuse3 (REPOS["fuse"] → new worktree, --name-prefix 260827_<A1|A45>_b00<n>, --guardrail-masks guardrails3_<ds>, --signs-masks signs2_<ds>, edges/ground/mesh dirs as in fuse2, --lines-source clusters, --las-split instance). Fuse concurrency cap 5 (≈14 GB peak/worker at 1 cm), guardrails cap 8 (5.3 GB peak).
Phase 1 — seg3d code changes, AI3D-369 (≈ 3–4 h wall, mostly subagents) medium

TDD, small commits, all under AI3D-369:. Three independent worktree tasks run in one Workflow (Opus, high effort), then a Sol + Opus adversarial review, then a Fable pass.

  1. Rebase sign JSON paint (bb331a4, 4b5fcb4, 6cb0c84) onto main. Resolve fuse.py paint order: sidecar masks (with PAINT_LAST_CLASSES) → guardrail JSON fallback → sign JSON cylinders (unclassified only) → vehicle sweep. Keep the 5 config keys (signs_json_paint_enabled=true, radius_max_m=2.0, z_pad_top_m=0.30, z_pad_bottom_m=0.15, min_points=10), the zero-point-mask guard Sol caught, the 428 test lines. Drop _dump_git_ai3d351.py, tmp_dump_commit.py, dump_diff.ipynb, _obj_bb.txt, ls.
  2. 1 cm default: config.py:35, seg3d.default.json:2, tests/test_config.py mirror, README/fusion_spec/upstream doc "1/15 m" mentions, CLI help. Add a voxel.py test that keys stay unique for a 300×300×100 m extent at 0.01.
  3. Naming: writer.segment_basename(seg_name, name_prefix) used at writer.py:87/115/148/300/370/401/427 and render.py:213/217/249; name_prefix in Seg3dConfig (validator [A-Za-z0-9_-]+, reject class-name tokens); --name-prefix in cli.py with {date} expansion, recorded in stats.json and run_summary; stale top-level *_seg3d.* cleanup on rerun (mirrors the las_split cleanup at writer.py:348). Tests: default names unchanged, prefixed names, dir unprefixed, voxelmap suffix survives, recap class resolution with prefixed stem, geoshift-from-stats with prefix, config validation. Fix scripts/orbit_video.py:30 glob.
  4. optional, cheap member_rows/rep_index int32 in voxelmap.py (halves the largest array; N < 2³¹ always); single argsort in write_las_split (O(M·instances) → O(M log M)); write_ply config toggle (Q4).
  5. Verify on main: JSON↔sidecar join by list index (ed480a0) — present; round-7b defect D is covered.
  6. Run uv run pytest, bump to 0.3.0, commit; create the fuse worktree at that commit; later Nexus publish via /wrap-up (Q8).
Phase 2 — Pilot: A1 branch_001 segment_005 (≈ 45 min after Phase 1) deliverable
  • Guardrails on 005 (≈ 6–12 min; 38.1 M full points). Then fuse3 on 005 (est. 5–10 min at 1 cm: ≈ 22 M vertices, ~0.8 GB main LAS + 0.8 GB split + voxelmap ~120 MB).
  • Checks: validate_las.py (LAS 1.4 / PDRF 7 / EPSG:25832 / codes ⊂ registry), stats params (voxel_size_m=0.01, signs_json_paint_enabled=true, name_prefix), class counts vs the 260812 file (guardrail 130 k @1/15 m → expect guardrail+72+74 ≈ same footprint ×~9), VoxelMap.load() round-trip (rep_index rows carry the vertex class), orbit render + top raster.
  • Copy to N:\…\260803_recap_annotation_Abschnitt_1\pre_annotation\260827_pilot_b001_seg005\ with a 10-line README (what changed, how to isolate 72/74 via point_source_id). Ping Miro.
  • Pilot doubles as the timing/size calibration for the sweep: if the segment exceeds 2 GB total, apply Q4 before continuing.
Phase 3 — A1 sweep (guardrails ≈ 3.5 h · fuse ≈ 2.5 h · QA + copy ≈ 1.5 h)
  • guardrails3 A1B0 + A1B1: 142 segs × ~730 s / 8 workers ≈ 3.6 h (132-type outliers ≈ 1,900 s). Flatten shards, then fuse3 can start per finished shard (no barrier).
  • fuse3 A1B0 + A1B1: previous 80 s/seg at 1/15 m; at 1 cm est. 200–400 s/seg (laspy writes ×9, zlib ~20 MB/s) → ≈ 2.5 h with 5 workers. Watch peak RSS in run_summary/stats.
  • QA (Workflow, cheap models): validate all LAS; params audit; per-segment class-count diff vs las2_* with a flag when (66+72+74)@new ÷ 9 deviates > 25 % from 66@old; instance/feature tables; 5 orbit renders (incl. one with a top rail, one off-axis). Fable only reads the summary.
  • Hardlink delivery tree /home/ai/seg3d_delivery_260827/, copy_to_n.sh (≈ 90 MB/s → ~100 GB ≈ 20 min per 100 GB), README + offaxis list + validation json.
Phase 4 — A4_5 sweep (guardrails ≈ 3.5 h · fuse ≈ 5 h · QA + copy ≈ 1.5 h)
  • Same stages for A45 (143 segs). Fuse is slower here (264 s/seg at 1/15 m; ~20 M pts/seg, run4 road surface path) → est. 5 h with 5 workers.
  • Deliver to …_Abschnitt_4_5\pre_annotation\260827_full_sweep\segments\. Note A4_5 has no real RGB (uniform white sensor RGB; colour lives in classification).
Phase 5 — Wrap-up
  • DELIVERY_README_260827.md in /home/ai/seg3d_out (what changed vs 260812: guardrail v-support classes, 1 cm, naming, sign JSON paint, back-map format + VoxelMap.load snippet).
  • Memory notes: new pins, 1 cm numbers, railpost trap, stage names.
  • Push AI3D-369 branch, PR on Bitbucket; Nexus publish 0.3.0 (Q8).

Risks

Open questions

  1. Pilot segment: "middle of branch 1" = branch_001 (11 segments)?

    default A1 branch_001 / segment_005 (38 M pts, has guardrail 130 k, lines, trees, delineators).

    alt branch_000 / segment_065 (middle of the 131-segment branch; 065/066 were the earlier demo segments).

  2. File naming format?

    default 260827_A1_b001_segment_005_seg3d.las — date = fuse date (YYMMDD), dataset A1/A45, branch b000/b001 (A4_5 gets b000), segment token kept so existing tooling and stats["segment"] still line up; directory stays segment_005/.

    alt A1_b001_seg005_260827.las (dataset first, date last) — same code, different template.

  3. A4_5 lane XML for the guardrail zones?

    default run7_lanes_20260721-120038.xml (the 260721_run7_line_width twin of the A1 file you pointed at; same file the reused edges/signs were produced with → consistent inputs).

    alt run7_lanes_20260723-163130.xml (rerun with latest packages, also has widths, 4 fewer elements).

  4. Outputs at 1 cm — which levers?

    default Keep las_split=instance (annotators isolate objects by it) and the npz (ReCap round-trip), skip the class-colour PLY (new write_ply=false; LAS classification renders in CloudCompare anyway). ≈ 300 GB local / 260 GB on N:.

    alt A 2 cm voxel — measure retention on the pilot first (likely ~30 %, i.e. half the volume).

    alt B las_split=class (14 files/segment instead of up to 151, same bytes).

  5. Vehicle class 73 (corridor unclassified → vehicle sweep) stays on? It is not in your ReCap table.

    default On (as in 260812), listed in the README table.

    alt --set vehicle_enabled=false → those points stay 1.

  6. verticalsigns master gained the AI3D-336 far-from-edge-line delineator filter (on by default; removes 10 A45 + 5 A1B0 + 3 A1B1 far delineators). Reused signs2 outputs do not have it.

    default Accept — reuse as-is (18 delineators corpus-wide, reviewable via point_source_id).

    alt Rerun signs2 on master with models_dir still pointing at the old guardrails (safe) ≈ 5 h extra before fusion.

  7. Delivery folder name inside pre_annotation\?

    default 260827_full_sweep (+ 260827_pilot_b001_seg005); 260809/260812 stay.

    alt 260827_guardrails_v2_1cm.

  8. Version bump + Nexus publish of seg3d (0.2.0 → 0.3.0) and Bitbucket PR at the end?

    default Yes, after the A1 delivery, via /wrap-up.

    alt Commit only; publish later.