Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 98 additions & 16 deletions .github/workflows/build-push-docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,53 @@ on:
- main

jobs:
build:
runs-on: ubuntu-latest
prepare:
runs-on: ubuntu-24.04
permissions:
contents: read
outputs:
tags: ${{ steps.push_tags.outputs.tags }}

steps:
- uses: actions/checkout@v5

- name: Set docker image tag to 'develop'
run: echo "DOCKER_TAG=develop" >> $GITHUB_ENV
run: echo "DOCKER_TAG=develop" >> "$GITHUB_ENV"

- name: Set docker image tag to git tag
if: startsWith(github.ref, 'refs/tags/v')
run: echo "DOCKER_TAG=${{ github.ref_name }}" >> $GITHUB_ENV
run: echo "DOCKER_TAG=${{ github.ref_name }}" >> "$GITHUB_ENV"

- name: Set immutable develop tag with commit hash
if: github.ref == 'refs/heads/develop' && github.event_name != 'pull_request'
run: echo "EXTRA_TAG=develop-${GITHUB_SHA::7}" >> $GITHUB_ENV
run: echo "EXTRA_TAG=develop-${GITHUB_SHA::7}" >> "$GITHUB_ENV"

- name: Assemble push tags
- name: Assemble image tags
id: push_tags
if: github.event_name != 'pull_request'
run: |
tags="3dgi/roofer:${DOCKER_TAG}"
if [ -n "${EXTRA_TAG:-}" ]; then
tags="${tags},3dgi/roofer:${EXTRA_TAG}"
fi
echo "tags=${tags}" >> "$GITHUB_OUTPUT"

build:
needs: prepare
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-24.04
platform: linux/amd64
arch: amd64
- runner: ubuntu-24.04-arm
platform: linux/arm64
arch: arm64
runs-on: ${{ matrix.runner }}
permissions:
contents: read

steps:
- uses: actions/checkout@v5

- name: Login to Docker Hub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
Expand All @@ -64,11 +82,13 @@ jobs:
build-args: |
JOBS=2
GIT_HASH=${{ github.sha }}
platforms: ${{ matrix.platform }}
push: false
tags: 3dgi/roofer:${{ env.DOCKER_TAG }}
cache-from: type=registry,ref=3dgi/roofer:buildcache
tags: ${{ needs.prepare.outputs.tags }}
cache-from: type=registry,ref=3dgi/roofer:buildcache-${{ matrix.arch }}

- name: Build and push
- name: Build and push by digest
id: build
if: github.event_name != 'pull_request'
uses: docker/build-push-action@v6
with:
Expand All @@ -78,7 +98,69 @@ jobs:
build-args: |
JOBS=2
GIT_HASH=${{ github.sha }}
push: true
tags: ${{ steps.push_tags.outputs.tags }}
cache-from: type=registry,ref=3dgi/roofer:buildcache
cache-to: type=registry,ref=3dgi/roofer:buildcache,mode=max
platforms: ${{ matrix.platform }}
outputs: type=image,name=3dgi/roofer,push-by-digest=true,name-canonical=true,push=true
cache-from: type=registry,ref=3dgi/roofer:buildcache-${{ matrix.arch }}
cache-to: type=registry,ref=3dgi/roofer:buildcache-${{ matrix.arch }},mode=max

- name: Export image digest
if: github.event_name != 'pull_request'
env:
IMAGE_DIGEST: ${{ steps.build.outputs.digest }}
run: |
mkdir -p /tmp/digests
touch "/tmp/digests/${IMAGE_DIGEST#sha256:}"

- name: Upload image digest
if: github.event_name != 'pull_request'
uses: actions/upload-artifact@v7
with:
name: digests-${{ matrix.arch }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1

merge:
if: github.event_name != 'pull_request'
needs:
- prepare
- build
runs-on: ubuntu-24.04
permissions:
contents: read

steps:
- name: Download image digests
uses: actions/download-artifact@v8
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true

- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Create multi-platform manifest
env:
IMAGE_NAME: 3dgi/roofer
IMAGE_TAGS: ${{ needs.prepare.outputs.tags }}
run: |
IFS=',' read -r -a tags <<< "$IMAGE_TAGS"
tag_args=()
for tag in "${tags[@]}"; do
tag_args+=(--tag "$tag")
done

image_args=()
for digest in /tmp/digests/*; do
image_args+=("${IMAGE_NAME}@sha256:$(basename "$digest")")
done

docker buildx imagetools create "${tag_args[@]}" "${image_args[@]}"
docker buildx imagetools inspect "${tags[0]}"
27 changes: 15 additions & 12 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

This releases contains several bugfixes, stability improvements, and new functionalities. Some highlights include:

1. significantly improved geometric validity of the generated building models; in the order of 10x less errors with val3dity. This was achieved by detecting and fixing locally 2D arrangement configurations that led to e.g. non-manifold edges after extrusion and self intersecting faces. Likewise it is now detected if a slanted roofplane would be extruded to under the floor elevation, the below the floor part is now cut off from the footprint.
2. User access to all reconstruction parameters.
3. Reorganised config file. Parameters are now divided into Input, Crop, Reconstruction, and Output sections. Old parameters locations will give a deprecation warning, and be removed in v2.0.
4. A new `unit-scale` parameter that scales parameter values that are defined in metres. For example if you data is in feet, this can be used to get good results with the default parameter values. One user also reported that a scale of 0.55 can help with getting better results on low density point clouds.


### Added
- Repair of non-manifold and self-intersecting roof junctions during arrangement snapping. This prevents certain geometric errors like non-watertight meshes, and highly non-planar faces that could previously happen in complex roof arrangements. See #168.
- Optional per-tile triangulated terrain output as CityJSON `TINRelief` features, see the `--terrain` option.
- Experimental optional per-tile triangulated terrain output as CityJSON `TINRelief` features, see the `--terrain` option. Its output and configuration may change, and the feature may be removed in a future release.
- A new `max-nodata-fraction` configuration option to control the maximum fraction of nodata pixels in a building polygon that is allowed before a building pointcloud is considered insufficient.
- A descriptor-backed `roofer::reconstruction::ReconstructionConfig` that contains the configuration for every reconstruction stage. Field declarations are now the single source for defaults, descriptions, validation, nested TOML parsing, generated documentation, and Python bindings.
- A nested C++ `ReconstructOptions` API and corresponding `reconstruct` overload. The nested reconstruction configuration is also available from Python, including each component configuration that exposes public parameters.
- Arm64 docker image

### Fixed
- More robust handling of OGC WKT payloads in LAS/LAZ files.
- Prevent roof-ground bow-ties by clipping roof faces against the terrain before extrusion.
- Correct squared-distance tolerance comparisons during extrusion.
- Reject TOML integer values outside the supported `int` range instead of silently narrowing them.
- Reject malformed or non-finite numeric CLI and TOML values instead of accepting numeric prefixes or propagating `NaN`/infinity. CLI `--no-` options are now limited to boolean parameters, general CLI validators are applied consistently, and TOML syntax errors retain their source location and parser explanation.

### Changed
- Application configuration is organised into `[input]`, `[crop]`, `[reconstruction]`, and `[output]` tables, with output attribute names under `[output.attributes]` and pointclouds under `[[input.pointclouds]]`. Deprecated root keys remain supported during 1.x, while canonical section values take precedence. Generated example configuration now uses the canonical layout.
- The pointcloud-insufficient test is now an absolute per-building density floor (`min-building-density`, points/m²) instead of a tile-relative `mean - 2·std` outlier test. The old test depended the entire tile that was being processed, so the same building could be flagged differently between runs with different tiling — potentially skipping point-rich buildings and leaving them without geometry. The decision is now deterministic and depends only on the building's own data.
- Region growing no longer uses a wall-clock time limit, which could make a building reconstruct with success on one run and fall back to LoD 1.1 on the next given identical input due to resource starvation with multithreading. The deterministic plane-count limit (`lod11-fallback-planes`) is now the sole reconstruction-complexity cutoff. The `lod11-fallback-time` behavior is removed; its CLI flag and root configuration key are accepted but ignored with a deprecation warning during 1.x compatibility.
- Reconstruction configuration now uses nested TOML tables such as `[reconstruction.plane-detector]` and `[reconstruction.arrangement-optimiser]`. Core reconstruction stages receive their component configuration aggregates; pipeline-derived settings such as requested LoD and terrain availability are overlaid on local copies.
- Reconstruction parameter descriptions now state physical and angular units where applicable; counts remain unit-free.
- All canonical reconstruction angle parameters now use degrees.
Plane-detection normal, horizontal, and wall thresholds are expressed
directly as angles instead of dot products, and the line-regularisation
angle is no longer in radians. The legacy `plane-detect-normal-angle` alias
and compatibility API properties retain their 1.x dot-product semantics.
- Reconstruction snapping and duplicate-vertex tolerances now use distances
in metres instead of base-10 exponents. Existing defaults retain their
previous effective distances.
- Added global `unit-scale` (metres per input coordinate unit) to convert
metre-based reconstruction, crop, and output parameters for input data that
uses another unit.
- All canonical reconstruction angle parameters now use degrees. Plane-detection normal, horizontal, and wall thresholds are expressed directly as angles instead of dot products, and the line-regularisation angle is no longer in radians. The legacy `plane-detect-normal-angle` alias and compatibility API properties retain their 1.x dot-product semantics.
- Reconstruction snapping and duplicate-vertex tolerances now use distances in metres instead of base-10 exponents. Existing defaults retain their previous effective distances.
- Added global `unit-scale` (metres per input coordinate unit) to convert metre-based reconstruction, crop, and output parameters for input data that uses another unit.
- Set the default cityjson scale in the transform object to 0.0001. This helps with improving geometric validity. The previous 0.001 setting regurlarly caused validity issues due to the relatively large quatisation error.
- Descriptor-generated TOML documentation and Python bindings omit reconstruction components that have no public parameters.
- `complexity-factor` now directly controls the optimiser energy balance: the data term is weighted by `complexity-factor` and the smoothness term by `1 - complexity-factor`. The independent `data_multiplier` and `smoothness_multiplier` component options were removed.
Expand Down
Loading
Loading