Skip to content

Releases: leiverkus/effigies

v0.7.2

18 Jun 09:18

Choose a tag to compare

Patch release focused on license and compliance metadata.

Changes:

  • Relicense repository metadata and source headers to AGPL-3.0-or-later.
  • Clarify THIRD_PARTY_LICENSES.md: Effigies' own source is AGPL-3.0-or-later; bundled/orchestrated components keep their own licenses.
  • Add Docker image dependencies to the third-party license table, including Obj2Tiles, OpenPointClass, VCGlib, CGAL, Entwine, and LightGBM.
  • Bump CITATION.cff to 0.7.2 with release date 2026-06-18.

v0.7.1

16 Jun 10:20

Choose a tag to compare

Added

  • Citation metadata for Zenodo archival. CITATION.cff (CFF 1.2.0) with author, keywords and upstream references (COLMAP, OpenMVS, NodeODM), plus .zenodo.json so Zenodo draws release metadata from the repository.

This is the first Zenodo-archived release; the concept DOI it mints will be added to CITATION.cff.

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

15 Jun 12:49

Choose a tag to compare

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).

v0.6.0 — capability parity (buildable gaps vs ODM / Metashape / RealityCapture)

14 Jun 18:11

Choose a tag to compare

v0.6.0 — capability parity (buildable gaps vs ODM / Metashape / RealityCapture)

Closes the buildable capability gaps to the established tools: vector contour
lines (GPKG + DXF), OGC 3D Tiles export (Obj2Tiles), ML multi-class point
classification (OpenPointClass), multi-epoch change detection (ICP
co-registration + DEM-of-difference + M3C2), and orthomosaic radiometric
colour-balance finishing. Plus engine hardening: the dead OpenSfM sparse path
removed and InterfaceCOLMAP resolved through a fail-loud alias lookup, and the
Docker images slimmed to a multi-stage builder->runtime layout (CPU 3.24->1.65 GB,
runtime exercise gate; GPU mirrored in lock-step).

v0.5.0 — scaling to large image sets (split-merge tiling)

14 Jun 18:11

Choose a tag to compare

v0.5.0 — scaling to large image sets (split-merge tiling)

Split-merge spatial tiling for sets that exceed the single-machine RAM wall:
cameras partitioned into overlapping spatial tiles, each run through the full
dense chain independently, then the per-tile meshes/clouds merged back. Enabled
by a streaming, memory-flat multi-view blend (top-K view selection streamed in
one pass; view-major bake keeps at most one source image resident), so peak RSS
is governed by mesh/atlas size, not the image count. Auto-scaling of
matcher/mapper/densify for large sets and a dense worker-thread cap.

v0.3.0 — georeferencing accuracy

14 Jun 18:14

Choose a tag to compare

v0.3.0 — georeferencing accuracy

Multi-view GCP triangulation: marked pixels undistorted through the full COLMAP
lens model (SIMPLE_RADIAL/RADIAL/OPENCV/FULL_OPENCV/OPENCV_FISHEYE) into viewing
rays and intersected in least squares across all images a GCP is marked in, with
parallax + cheirality checks; single-view GCPs fall back to the nearest sparse
point. Solve-quality residuals (RMS 3D/horizontal/vertical, max, count) recorded
in georef_transform.json and surfaced in the log and the quality-report PDF.
Named CRS presets (Israeli TM, Palestine 1923, ETRS89 UTM 32N/33N = Germany's
official grid, OSGB, Swiss LV95) — presets, not defaults; an explicit CRS wins.

v0.2.0 — reproducible source-built engine (COLMAP 4.0.4 + OpenMVS 2.4.0)

11 Jun 21:31
9028ba2

Choose a tag to compare

Added

  • First full end-to-end run on the CPU image. The complete chain — COLMAP
    sparse → image_undistorter → OpenMVS DensifyPointCloudReconstructMesh
    RefineMeshTextureMesh — now runs to completion on the CPU/arm64 image
    against a real 70-image dataset, producing a textured OBJ. This closes the main
    open 0.2.0 validation item (the engine had never been run through on a dataset).
  • Both images unified on COLMAP 4.0.4 + OpenMVS v2.4.0. The CUDA/production
    Dockerfile and the CPU Dockerfile.cpu now build the identical engine from
    the identical pinned sources and recipe — the only differences are the CUDA
    base image and the three -D*CUDA* flags. No image builds COLMAP 3.x any more.
  • OpenMVS bumped to v2.4.0 (both images). v2.3.0's DensifyPointCloud
    corrupts the heap and aborts on arm64 (it falls back off SSE); v2.4.0 swaps the
    FLANN nearest-neighbour code for nanoflann and runs the full dense+mesh chain
    cleanly. 2.4.0 needs two libs newer than Ubuntu 22.04 ships — nanoflann ≥1.5
    and CGAL ≥6.0 — both header-only, so both Dockerfiles vendor pinned releases
    (NANOFLANN_VERSION, CGAL_VERSION) rather than bumping the base off 22.04
    (which would lose PDAL, dropped from 24.04). Two small source patches keep it
    building against jammy's OpenCV (disable the hard libjxl requirement; map the
    one OpenCV-4.7-only JXL write constant to the JPEG one — we emit no JPEG-XL).
  • matcher=vocab_tree now works. Image-retrieval matching for large sets:
    each image bakes in a SHA256-pinned vocabulary tree in the format its COLMAP
    expects (FAISS for COLMAP 4). Previously the option was selectable but always
    aborted with the opaque "Cannot process dataset".

Fixed

  • CPU pipeline could not start: opaque "Cannot process dataset". A chain of
    failures, each masking the next, all surfacing only as NodeODM's generic error:
    • run.sh applied the default use-gpu=true even on the CUDA-less CPU image,
      so COLMAP's SIFT aborted ("Cannot use Sift GPU without CUDA or OpenGL"). It
      now probes for a usable GPU (nvidia-smi -L) and falls back to CPU with a
      loud warning when none is present.
    • COLMAP's CPU SIFT extractor OOM-killed itself fanning out over all cores on
      large images; the CPU SIFT/match thread count is now capped
      (EFFIGIES_CPU_THREADS, default 4).
    • COLMAP's CPU FLANN matcher segfaulted holding a full match block in memory;
      the exhaustive block size is capped on the CPU path
      (EFFIGIES_CPU_MATCH_BLOCK, default 10).
    • InterfaceCOLMAP was fed the raw sparse/0 model instead of an undistorted
      workspace; sparse_colmap.sh now runs colmap image_undistorter and
      InterfaceCOLMAP reads $WORK/dense with the correct --image-folder.
    • dense_openmvs.sh passed --cuda-device unconditionally; the CPU OpenMVS
      build rejects it. The flag is now probed and passed only on CUDA builds.
  • Source-built, pinned Dockerfile. COLMAP (4.0.4) and OpenMVS (v2.4.0)
    are now compiled from upstream source with CUDA, replacing the distro packages;
    Eigen/CGAL/Boost/OpenCV come from Ubuntu 22.04. Versions are declared as build
    ARGs and a build-time which gate fails the build loudly if colmap,
    DensifyPointCloud, ReconstructMesh, RefineMesh, TextureMesh,
    InterfaceCOLMAP or pdal is missing.
  • Georeferenced point cloud output. New helpers/pointcloud_to_laz.py applies
    the georef similarity to scene_dense.ply and writes
    odm_georeferenced_model.laz via PDAL (full projected coordinates, LAS
    scale/offset for precision), and optionally builds an EPT tileset
    (entwine_pointcloud/) for the Potree viewer when entwine/untwine is
    present. map_outputs.py maps the LAZ + EPT into the WebODM paths, with the raw
    PLY kept as a documented fallback.
  • CPU test image (Dockerfile.cpu) — same pinned engine built without CUDA,
    for local integration testing on machines without an NVIDIA GPU (e.g. Apple
    Silicon). Plus docs/DEPLOYMENT.md (local CPU + GPU host recipes, WebODM node
    wiring) and a .dockerignore.
  • Unit tests for the point-cloud transform matrix (tests/test_pointcloud.py) and
    the NodeODM options translation (tests/test_options.py); the local runner and
    CI now execute every tests/test_*.py.

Fixed

  • Options were incompatible with NodeODM. options.json was a flat list, but
    NodeODM (libs/odmInfo.js) expects an argparse-style descriptor object keyed by
    --flag; it was serving every option as name="0".."12" with the wrong types.
    helpers/optionsToJson.py now translates our list into NodeODM's schema (enum
    choices, <class 'int'>/float, bool via default, valid metavar domains),
    so WebODM builds the correct task UI. (Found by actually running the node.)
  • Options shim could not find options.json. It resolved the path with
    abspath(__file__), which does not follow the NodeODM symlink — it looked in
    /opt/NodeODM. Now prefers ODM_PATH and falls back to realpath.
  • mesh-decimate domain changed to float: 0 <= x <= 1 so it passes NodeODM's
    checkDomain validation on task submission.

Notes

  • NodeODM hard-skips a --gcp UI option (WebODM handles GCP upload natively), so
    the gcp option is intentionally not shown; run.sh still auto-detects
    gcp_list.txt. The node is verified to build, run, serve all options and be
    reachable on the WebODM network; a full processing run on a dataset is the
    remaining 0.2.0 validation.

Planned

See ROADMAP.md. Still open for 0.2.0: a full end-to-end processing
run on a real dataset, a verified VCGlib commit pin, and confirming the
InterfaceCOLMAP/InterfaceOpenSfM binary names across OpenMVS builds. Beyond
that: multi-view GCP triangulation (0.3.0).

v0.1.0 — first public release

10 Jun 15:17

Choose a tag to compare

First public release of Effigies — a NodeODM-compatible alternative engine for WebODM.

It runs the full OpenMVS chain (Densify → ReconstructMesh → RefineMesh → TextureMesh) on a COLMAP sparse reconstruction, recovering the mesh-refinement steps that stock ODM skips, and bridges the result into the WebODM asset contract.

This is an alpha / working scaffold — see the README status section and ROADMAP.md for what still needs hardening (source-built Docker image with binary verification, .laz/EPT point clouds, multi-view GCP triangulation).

Full notes: see CHANGELOG.md.