Skip to content

v0.7.0 — semantic field v0 + change-detection accuracy

Choose a tag to compare

@leiverkus leiverkus released this 15 Jun 12:49
· 7 commits to main since this release

The semantic-field bridge to Structura plus a hardening pass on change-detection accuracy. Mechanism only — Effigies ships the field machinery; the fine-class archaeological material model is a downstream Structura deliverable and is never baked into the MIT image.

Added

  • Semantic orthophoto v0 (helpers/semantic_ortho.py, opt-in --semantic). The
    geometry-derived first increment of the semantic field (ROADMAP v0.7.0): it
    rasterises the OpenPointClass point classes already written into the LAZ onto the
    orthophoto grid — pixel-aligned with odm_dem/dsm.tif — taking the per-cell
    majority class, folded into ground / vegetation / structure, as
    odm_semantic/orthophoto_semantic.tif (Byte GeoTIFF + colour table) plus a legend
    JSON. No trained model: it ships what the cloud classification already knows; the
    fine archaeological material classes (stone/earth/paving/ceramic/mortar) are a
    downstream 2D-model deliverable. Needs a classified cloud (enable --classify);
    self-skips, non-fatally, otherwise. Runs after the orthophoto/DSM so the grid exists.
    Unit-tested (majority + ASPRS→v0 mapping + GeoTIFF round-trip) and image-validated
    end-to-end. This is the bridge's v0 to Structura's vectorisation.
  • Multi-epoch semantic propagation (helpers/semantic_propagate.py). Carries the
    semantic field across daily epochs (runs under --semantic when --align-to gives a
    reference epoch). Because change detection re-lands this epoch into the reference frame,
    its semantic ortho is already co-registered with the reference epoch's; the step writes
    (1) a carry-forward field — odm_semantic/orthophoto_semantic_propagated.tif, where
    this epoch's unobserved cells inherit the reference epoch's class (temporally consistent;
    honest "unobserved = unchanged" assumption) — and (2) a semantic-change raster —
    odm_semantic/semantic_change.tif with per-pixel class transitions + per-transition area
    in odm_report/semantic_change.json (the class complement of the DoD/M3C2 geometric
    change: e.g. structure→ground = a feature removed, vegetation→ground = clearing). The
    reference is resampled nearest-neighbour (categorical). Self-skips without both semantic
    orthos; non-fatal. Unit-tested (carry-forward + transition + stats) and end-to-end
    validated.

Changed

  • Change detection — M3C2 level-of-detection now includes the co-registration
    residual (change_detect.py).
    The per-point LoD previously reflected local
    roughness only; it now passes the post-ICP cloud-to-cloud residual to py4dgeo as
    registration_error (Lague 2013), so a cm-level alignment error is folded into the
    significance test instead of being treated as zero — small (few-cm) changes are no
    longer over-reported as significant. Recorded as registration_error_m in
    odm_report/change_detection.json. Unit-tested: a 5 cm residual lifts the LoD
    median (≈ 0.3 cm → 10 cm). Stable-area-masked ICP remains v2 (see ROADMAP).
  • Change detection — DoD now thresholded at a minimum level-of-detection
    (change_detect.py).
    The DEM-of-Difference changed area and fill/cut volumes now
    count only cells whose |Δz| exceeds a minimum LoD (Wheaton 2010 — a robust noise
    floor of the difference distribution, floored by the co-registration residual;
    min_lod_from_dod), so sub-LoD noise is no longer booked as excavation / back-fill.
    A raw un-thresholded net volume is kept as a cross-check, and the minLoD is recorded
    as min_lod_m in odm_report/change_detection.json. Unit-tested.
  • Change detection — co-registration is now stable-area-masked (change_detect.py).
    ICP runs in two passes: a whole-cloud fit, then a re-fit on only the unchanged
    ground (changed cells dropped via stable_mask), so a localised excavation change no
    longer biases the rigid transform, and the residual over the stable area is a clean
    registration-only error that now feeds the M3C2 LoD and the DoD minLoD
    (coreg_reg_error) instead of the conservative full-cloud C2C. Reports
    stable_fraction + registration_error in change_detection.json; degrades to the
    whole-cloud fit (and records why) when too little stable ground remains. Unit-tested
    (stable_mask separation; the two-pass ICP smoke is pdal-gated).
  • Change detection — --align-to now re-lands the deliverables into the reference
    frame by default (change_detect.py), ODM --align parity.
    The recovered ICP
    transform is applied in place to the delivered mesh OBJ (offset-aware, transform_obj)
    and the LAZ (PDAL; EPT rebuilt); because re-landing runs before the raster stages, the
    DSM/DTM, orthophoto, contours, glTF and 3D Tiles inherit the reference frame natively
    (no re-warp). Previously the alignment was additive-only (deliverables untouched) —
    pass --no-reland to keep that. report["relanded"] lists what moved; non-fatal per
    asset. Camera assets (shots.geojson) are a known re-land gap (v2). Offset-exact
    transform unit-tested; the full raster re-derivation is Docker-validated.
  • Change detection — --align-to now accepts a DEM GeoTIFF as the reference, not
    only a point cloud (change_detect.py).
    A reference .tif (a prior DSM/DEM) is
    read as cell-centre points for the ICP co-registration and M3C2, and used directly
    (resampled onto the shared grid) as the reference DSM for the DEM-of-Difference —
    so a prior epoch's DSM, or any external reference DEM, can drive the comparison.
    is_dem / dem_to_xyz / resample_dem; unit-tested.
  • Change detection — camera assets are re-landed with the rest (camera_exports.py).
    When an --align-to run re-landed this epoch into the reference frame, shots.geojson
    camera centres and orientations are now transformed by the recorded re-land transform
    (read from odm_report/change_detection.json, gated on the relanded marker), so the
    camera positions stay consistent with the re-landed mesh / cloud / orthophoto instead
    of sitting in the old frame. cameras.json is unchanged (intrinsics are
    frame-independent). The re-land gate is unit-tested; the pyproj WGS84 path is
    Docker-validated. Closes the last v2 gap of the change-detection item.
  • Change detection — fix: M3C2 no longer misses deep changes (change_detect.py).
    py4dgeo's M3C2 max_distance (the cylinder search depth along the normal) defaulted
    to 0, which on the auto-scaled small cylinder of a dense cloud is too shallow — a deep
    excavation (a change larger than the cylinder scale) had no matching surface in range
    and came back NaN / not-significant, even where the DoD clearly measured it. It is now
    set generously (max(30·cyl_radius, 3 m)). Found by the new end-to-end smoke
    (scripts/smoke_change_detect.py), which the fix takes from M3C2 0 %→significant on a
    0.4 m block.

Carried forward (post-v0.7.0)

  • The full mesh-classify → z-buffer --semantic path (v0 approximates it via per-cell cloud majority).
  • The fine-class material model (stone / earth / ceramic / mortar) — Structura's deliverable, itself data-blocked.
  • Cross-project contract finalisation.

Test gate: pure-Python unit suite green; the only scripts/test.sh failure is the pre-existing macOS bash-3.2 issue in test_autoscale.sh (passes on the bash-5 Docker image).