diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md
deleted file mode 100644
index bb507ee59..000000000
--- a/.github/ISSUE_TEMPLATE/question.md
+++ /dev/null
@@ -1,44 +0,0 @@
----
-name: Question
-about: Ask a general question
-title: ''
-labels:
-assignees: victorreijgwart
-
----
-
-**Question**
-A clear and concise description of your question.
-
-**Existing resources**
-Before submitting this question, please check if it has been answered in a previous [GitHub Issue](https://github.com/ethz-asl/wavemap/issues?q=is%3Aissue).
-
-If the question is about the theory behind wavemap, it might already be addressed in our [RSS paper](https://www.roboticsproceedings.org/rss19/p065.pdf).
-
-If you would like to reproduce the results from our [RSS paper](https://www.roboticsproceedings.org/rss19/p065.pdf), please refer to the [Demo](https://ethz-asl.github.io/wavemap/pages/demos.html) documentation page.
-
-If you need help configuring wavemap on a custom dataset, the [Configuration](https://ethz-asl.github.io/wavemap/pages/configuration.html) documentation page might be helpful.
-
-**Images**
-If it helps to explain your question, please include screenshots, plots or sketches.
-
-**Runtime information:**
-Please fill out these questions in case a specific dataset or sensor setup is relevant to your question.
-
-- Launch file: [e.g. Link to the launch file you used]
-- Config file: [e.g. Link to the config file you used]
-- Dataset name [e.g. Newer College Cloister sequence] # For public datasets
-- Custom setup: # For online use or personal datasets
- - depth sensor: [e.g. Livox MID360 LiDAR]
- - pose source: [e.g. Odometry from FastLIO2]
-
-**System information:**
-Please fill out these questions in case your hardware is relevant to your question. For example, if you would like help to tune wavemap's performance on your robot.
-
-- CPU: [e.g. Intel i9-9900K]
-- GPU: [e.g. Nvidia RTX 2080Ti] # Only for visualization-related issues
-- RAM: [e.g. 32GB]
-- OS: [e.g. Ubuntu 20.04]
-- Wavemap
- - install: [e.g., Native (ROS with catkin); or Docker]
- - version: [e.g., v1.4.0]
diff --git a/.github/actions/log-ccache-stats/action.yml b/.github/actions/log-ccache-stats/action.yml
deleted file mode 100644
index f400c62a3..000000000
--- a/.github/actions/log-ccache-stats/action.yml
+++ /dev/null
@@ -1,16 +0,0 @@
-name: 'Log ccache stats'
-description: 'Log statistics for ccache if it was enabled'
-
-runs:
- using: "composite"
- steps:
- - name: Ccache statistics
- shell: bash
- run: |
- if [ "$(which gcc)" == "/usr/lib/ccache/gcc" ]; then
- echo "Using ccache: true"
- echo "Ccache stats"
- ccache --show-stats
- else
- echo "Using ccache: FALSE"
- fi
diff --git a/.github/actions/setup-ccache/action.yml b/.github/actions/setup-ccache/action.yml
deleted file mode 100644
index 1234c1222..000000000
--- a/.github/actions/setup-ccache/action.yml
+++ /dev/null
@@ -1,40 +0,0 @@
-name: 'Setup ccache'
-description: 'Install ccache and configure it to use GitHub cache sharing'
-inputs:
- cache-group:
- description: 'Key used to separate ccache caches on GitHub from different configurations'
- required: true
- cache-version:
- description: 'Key used to manually flush the cache (e.g. by setting a GitHub secret to a new random hash)'
- required: true
-
-runs:
- using: "composite"
- steps:
- - name: Get the current date (used for cache matching)
- id: get-date
- shell: bash
- run: echo "date=$(date -u "+%Y-%m-%d_%H-%M-%S")" >> $GITHUB_OUTPUT
-
- - name: Setup ccache cache sharing
- uses: actions/cache@v4
- with:
- path: ${{ env.CCACHE_DIR }}
- key: ccache-${{ inputs.cache-version }}-${{ inputs.cache-group }}-${{ github.sha }}-${{ steps.get-date.outputs.date }}
- restore-keys: |
- ccache-${{ inputs.cache-version }}-${{ inputs.cache-group }}-${{ github.sha }}-
- ccache-${{ inputs.cache-version }}-${{ inputs.cache-group }}-
- # NOTE: The action internally also gives priority to caches that were
- # created for the same git branch, i.e. it first tries to match
- # the restore keys against the current branch and then main.
-
- - name: Configure ccache
- shell: bash
- run: |
- echo "PATH="/usr/lib/ccache:$PATH"" >> $GITHUB_ENV
- ccache --max-size=1G
- ccache --set-config=compiler_check=content
-
- - name: Reset ccache stats to get per-run statistics
- shell: bash
- run: ccache --zero-stats
diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
deleted file mode 100644
index 226b1fe37..000000000
--- a/.github/workflows/cd.yml
+++ /dev/null
@@ -1,233 +0,0 @@
-name: Continuous Deployment
-
-on:
- push:
- tags:
- - "v*.*.*"
- branches: [ main ]
-
-# NOTE: We do not store the work files under $HOME ("/github/home/") since that
-# dir persists between jobs when using self-hosted GitHub Actions runners
-# (/github/home is a docker volume mapped to the container's host).
-env:
- REPOSITORY_NAME: wavemap
- DOCKER_CI_REGISTRY: hub.wavemap.vwire.ch
- DOCKER_RELEASE_REGISTRY: ghcr.io
- DOCKER_RELEASE_TARGET: workspace
- USER_HOME: /home/ci
- CATKIN_WS_PATH: /home/ci/catkin_ws
- CCACHE_DIR: /home/ci/ccache
-
-jobs:
- common-variables:
- name: Define common variables
- runs-on: [ self-hosted, vwire ]
- container:
- image: docker:20.10.9-dind
- outputs:
- docker_cache_image_name: type=registry,ref=${{ env.DOCKER_CI_REGISTRY }}/${{ env.REPOSITORY_NAME }}:buildcache
- local_ci_image_name: ${{ env.DOCKER_CI_REGISTRY }}/${{ env.REPOSITORY_NAME }}:${{ env.DOCKER_RELEASE_TARGET }}-${{ github.sha }}
- steps:
- - name: Empty
- run: echo
-
- draft-release:
- name: Draft Release
- if: startsWith(github.event.ref, 'refs/tags/v')
- runs-on: ubuntu-latest
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
- - name: Create Release
- id: create_release
- uses: softprops/action-gh-release@v1
-
- build-image:
- name: Build Docker image
- needs: [ common-variables ]
- runs-on: [ self-hosted, vwire ]
- container:
- image: docker:20.10.9-dind
- permissions:
- contents: read
- packages: write
- outputs:
- image: ${{ needs.common-variables.outputs.local_ci_image_name }}
- env:
- CACHE_IMAGE_NAME: ${{ needs.common-variables.outputs.docker_cache_image_name }}
- LOCAL_IMAGE_NAME: ${{ needs.common-variables.outputs.local_ci_image_name }}
- steps:
- - name: Fetch the package's repository
- uses: actions/checkout@v4
- with:
- path: ${{ env.REPOSITORY_NAME }}
-
- - name: Log in to the Container registry
- uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
- with:
- registry: ${{ env.DOCKER_RELEASE_REGISTRY }}
- username: ${{ github.actor }}
- password: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v3
-
- - name: Build the ${{ env.DOCKER_RELEASE_TARGET }} image
- uses: docker/build-push-action@v6
- with:
- context: ${{ env.REPOSITORY_NAME }}
- file: ${{ env.REPOSITORY_NAME }}/tooling/docker/ros1/full.Dockerfile
- target: ${{ env.DOCKER_RELEASE_TARGET }}
- build-args: |
- REPOSITORY_NAME=${{ env.REPOSITORY_NAME }}
- USER_HOME=${{ env.USER_HOME }}
- CATKIN_WS_PATH=${{ env.CATKIN_WS_PATH }}
- CCACHE_DIR=${{ env.CCACHE_DIR }}
- load: true
- cache-from: ${{ env.CACHE_IMAGE_NAME }}
- cache-to: ${{ env.CACHE_IMAGE_NAME }},mode=max
- tags: ${{ env.LOCAL_IMAGE_NAME }}
-
- - name: Test the ${{ env.DOCKER_RELEASE_TARGET }} image
- run: docker run --rm ${{ env.LOCAL_IMAGE_NAME }}
-
- - name: Push the ${{ env.DOCKER_RELEASE_TARGET }} image locally
- uses: docker/build-push-action@v6
- with:
- context: ${{ env.REPOSITORY_NAME }}
- file: ${{ env.REPOSITORY_NAME }}/tooling/docker/ros1/full.Dockerfile
- target: ${{ env.DOCKER_RELEASE_TARGET }}
- build-args: |
- REPOSITORY_NAME=${{ env.REPOSITORY_NAME }}
- USER_HOME=${{ env.USER_HOME }}
- CATKIN_WS_PATH=${{ env.CATKIN_WS_PATH }}
- CCACHE_DIR=${{ env.CCACHE_DIR }}
- push: true
- cache-from: ${{ env.CACHE_IMAGE_NAME }}
- tags: ${{ env.LOCAL_IMAGE_NAME }}
-
- - name: Extract metadata to annotate the image
- id: meta
- uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
- with:
- images: ${{ env.DOCKER_RELEASE_REGISTRY }}/${{ github.repository }}_ros1
-
- - name: Publish the ${{ env.DOCKER_RELEASE_TARGET }} image
- if: startsWith(github.event.ref, 'refs/tags/v')
- uses: docker/build-push-action@v6
- with:
- context: ${{ env.REPOSITORY_NAME }}
- file: ${{ env.REPOSITORY_NAME }}/tooling/docker/ros1/full.Dockerfile
- target: ${{ env.DOCKER_RELEASE_TARGET }}
- build-args: |
- REPOSITORY_NAME=${{ env.REPOSITORY_NAME }}
- USER_HOME=${{ env.USER_HOME }}
- CATKIN_WS_PATH=${{ env.CATKIN_WS_PATH }}
- CCACHE_DIR=${{ env.CCACHE_DIR }}
- push: true
- cache-from: ${{ env.CACHE_IMAGE_NAME }}
- tags: ${{ steps.meta.outputs.tags }}
- labels: ${{ steps.meta.outputs.labels }}
-
- build-docs:
- name: Build docs
- needs: [ build-image ]
- runs-on: [ self-hosted, vwire ]
- container:
- image: ${{ needs.build-image.outputs.image }}
- steps:
- - name: Fetch the package's repository
- uses: actions/checkout@v4
-
- - name: Install dependencies (doxygen+sphinx+breathe+exhale toolchain)
- run: |
- apt-get update
- apt-get install -q -y --no-install-recommends python3-pip doxygen
- apt-get install -q -y --no-install-recommends latexmk texlive-latex-extra tex-gyre texlive-fonts-recommended texlive-latex-recommended
- pip3 install exhale sphinx-sitemap sphinx-design sphinx-notfound-page
- pip3 install sphinxawesome-theme --pre
- pip3 install "sphinx<7,>6"
-
- - name: Parse C++ library with Doxygen
- working-directory: ${{ env.CATKIN_WS_PATH }}/src/${{ env.REPOSITORY_NAME }}/docs
- shell: bash
- run: doxygen Doxyfile_cpp
-
- - name: Parse ROS1 interface with Doxygen
- working-directory: ${{ env.CATKIN_WS_PATH }}/src/${{ env.REPOSITORY_NAME }}/docs
- shell: bash
- run: doxygen Doxyfile_ros1
-
- - name: Build documentation site
- working-directory: ${{ env.CATKIN_WS_PATH }}/src/${{ env.REPOSITORY_NAME }}/docs
- shell: bash
- run: sphinx-build -b html . _build/html
-
- - name: Bundle site sources into tarball
- shell: bash
- run: |
- tar \
- --dereference --hard-dereference \
- --directory ${{ env.CATKIN_WS_PATH }}/src/${{ env.REPOSITORY_NAME }}/docs/_build/html/ \
- -cvf ${{ env.CATKIN_WS_PATH }}/src/${{ env.REPOSITORY_NAME }}/docs/artifact.tar \
- --exclude=.git \
- --exclude=.github \
- .
-
- - name: Upload tarball as GH Pages artifact
- uses: actions/upload-artifact@v3
- with:
- name: github-pages
- path: ${{ env.CATKIN_WS_PATH }}/src/${{ env.REPOSITORY_NAME }}/docs/artifact.tar
- retention-days: 1
-
- - name: Build documentation PDF
- working-directory: ${{ env.CATKIN_WS_PATH }}/src/${{ env.REPOSITORY_NAME }}/docs
- shell: bash
- run: sphinx-build -M latexpdf . _build/latex
-
- - name: Attach PDF to GitHub release
- if: startsWith(github.event.ref, 'refs/tags/v')
- uses: actions/github-script@v6
- with:
- script: |
- const fs = require('fs');
- const tag = context.ref.replace("refs/tags/", "");
- // Get release for this tag
- const release = await github.rest.repos.getReleaseByTag({
- owner: context.repo.owner,
- repo: context.repo.repo,
- tag
- });
- // Upload the release asset
- await github.rest.repos.uploadReleaseAsset({
- owner: context.repo.owner,
- repo: context.repo.repo,
- release_id: release.data.id,
- name: "docs.pdf",
- data: await fs.readFileSync("${{ env.CATKIN_WS_PATH }}/src/${{ env.REPOSITORY_NAME }}/docs/_build/latex/latex/wavemap.pdf")
- });
-
- publish-docs:
- name: Publish docs
- needs: [ build-docs ]
- runs-on: [ self-hosted, vwire ]
- container:
- image: docker:20.10.9-dind
- permissions:
- contents: read
- pages: write
- id-token: write
- concurrency:
- group: "pages"
- cancel-in-progress: true
- environment:
- name: github-pages
- url: ${{ steps.deployment.outputs.page_url }}
- steps:
- - name: Setup Pages
- uses: actions/configure-pages@v4
-
- - name: Deploy uploaded docs to GitHub Pages
- id: deployment
- uses: actions/deploy-pages@v3
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
deleted file mode 100644
index 66d30d185..000000000
--- a/.github/workflows/ci.yml
+++ /dev/null
@@ -1,571 +0,0 @@
-name: Continuous Integration
-
-on:
- pull_request:
- branches: [ main ]
-
-# NOTE: We do not store the work files under $HOME ("/github/home/") since that
-# dir persists between jobs when using self-hosted GitHub Actions runners
-# (/github/home is a docker volume mapped to the container's host).
-env:
- REPOSITORY_NAME: wavemap
- DOCKER_CI_REGISTRY: hub.wavemap.vwire.ch
- DOCKER_CI_TARGET: workspace
- USER_HOME: /home/ci
- CATKIN_WS_PATH: /home/ci/catkin_ws
- CCACHE_DIR: /home/ci/ccache
- PRE_COMMIT_DIR: /home/ci/pre-commit
-
-jobs:
- common-variables:
- name: Define common variables
- # NOTE: This job is used to pass complex common variables around between jobs,
- # as a work-around for ENV variables in GitHub Actions not being composable.
- runs-on: [ self-hosted, vwire ]
- container:
- image: docker:20.10.9-dind
- outputs:
- docker_cache_image_name: type=registry,ref=${{ env.DOCKER_CI_REGISTRY }}/${{ env.REPOSITORY_NAME }}:buildcache
- local_ci_image_name: ${{ env.DOCKER_CI_REGISTRY }}/${{ env.REPOSITORY_NAME }}:${{ env.DOCKER_CI_TARGET }}-${{ github.sha }}
- steps:
- - name: Empty
- run: echo
-
- workspace-container:
- name: Build CI workspace container
- needs: [ common-variables ]
- runs-on: [ self-hosted, vwire ]
- container:
- image: docker:20.10.9-dind
- outputs:
- image: ${{ needs.common-variables.outputs.local_ci_image_name }}
- env:
- CACHE_IMAGE_NAME: ${{ needs.common-variables.outputs.docker_cache_image_name }}
- LOCAL_IMAGE_NAME: ${{ needs.common-variables.outputs.local_ci_image_name }}
- steps:
- - name: Fetch the package's repository
- uses: actions/checkout@v4
- with:
- path: ${{ env.REPOSITORY_NAME }}
-
- - name: Install dependencies
- # NOTE: Installing tar is required for actions/cache@v4 to work properly
- # on docker:20.10.9-dind.
- run: apk add --no-cache tar git
-
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v3
-
- - name: Build the ${{ env.DOCKER_CI_TARGET }} image
- uses: docker/build-push-action@v6
- with:
- context: ${{ env.REPOSITORY_NAME }}
- file: ${{ env.REPOSITORY_NAME }}/tooling/docker/ros1/full.Dockerfile
- target: ${{ env.DOCKER_CI_TARGET }}
- build-args: |
- REPOSITORY_NAME=${{ env.REPOSITORY_NAME }}
- USER_HOME=${{ env.USER_HOME }}
- CATKIN_WS_PATH=${{ env.CATKIN_WS_PATH }}
- CCACHE_DIR=${{ env.CCACHE_DIR }}
- load: true
- cache-from: ${{ env.CACHE_IMAGE_NAME }}
- cache-to: ${{ env.CACHE_IMAGE_NAME }},mode=max
- tags: ${{ env.LOCAL_IMAGE_NAME }}
-
- - name: Test the ${{ env.DOCKER_CI_TARGET }} image
- run: docker run --rm ${{ env.LOCAL_IMAGE_NAME }}
-
- - name: Push the ${{ env.DOCKER_CI_TARGET }} image
- uses: docker/build-push-action@v6
- with:
- context: ${{ env.REPOSITORY_NAME }}
- file: ${{ env.REPOSITORY_NAME }}/tooling/docker/ros1/full.Dockerfile
- target: ${{ env.DOCKER_CI_TARGET }}
- build-args: |
- REPOSITORY_NAME=${{ env.REPOSITORY_NAME }}
- USER_HOME=${{ env.USER_HOME }}
- CATKIN_WS_PATH=${{ env.CATKIN_WS_PATH }}
- CCACHE_DIR=${{ env.CCACHE_DIR }}
- push: true
- cache-from: ${{ env.CACHE_IMAGE_NAME }}
- tags: ${{ env.LOCAL_IMAGE_NAME }}
-
- lint:
- name: Lint
- needs: [ common-variables ]
- runs-on: [ self-hosted, vwire ]
- container:
- # NOTE: Pylint checks if all modules that are marked for import are
- # available. At the time of writing, the python scripts in this repo
- # only depend on modules that are present on noetic-ros-base-focal
- # out of the box. If scripts are added later that depend on custom
- # package (e.g. installed through rosdep or pulled in through
- # vcstool), it'd make sense to run pre-commit in a full workspace
- # container (such as ${{ needs.workspace-container.outputs.image }})
- # at the cost of a longer loading time on the CI actions runner.
- image: ros:noetic-ros-base-focal
- steps:
- - name: Install pre-commit's dependencies
- run: |
- apt-get update
- apt-get install -q -y --no-install-recommends git python3-pip clang-format-11 cppcheck libxml2-utils wget
- pip3 install pre-commit cpplint
- wget -O /bin/hadolint https://github.com/hadolint/hadolint/releases/download/v2.8.0/hadolint-Linux-x86_64
- chmod +x /bin/hadolint
-
- - name: Fetch the package's repository
- uses: actions/checkout@v4
- # NOTE: This has to be done after installing pre-commit, s.t. the
- # pre-commit hooks are automatically initialized.
-
- - name: Get python version for pre-commit cache
- run: echo "PRE_COMMIT_PYTHON_VERSION=$(python -VV | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV
-
- - name: Setup pre-commit cache sharing
- uses: actions/cache@v4
- with:
- path: ${{ env.PRE_COMMIT_DIR }}
- key: pre-commit|${{ env.PRE_COMMIT_PYTHON_VERSION }}|${{ hashFiles('.pre-commit-config.yaml') }}
-
- - name: Run the pre-commit hooks
- shell: bash
- run: |
- echo "::add-matcher::./.github/problem-matchers/pre-commit.json"
- source /opt/ros/noetic/setup.bash
- PRE_COMMIT_HOME=${{ env.PRE_COMMIT_DIR }} SKIP=no-commit-to-branch pre-commit run --all-files
- echo "::remove-matcher owner=problem-matcher-pre-commit::"
-
- build-docs:
- name: Build docs
- needs: [ workspace-container, lint ]
- runs-on: [ self-hosted, vwire ]
- container:
- image: ${{ needs.workspace-container.outputs.image }}
- steps:
- - name: Fetch the package's repository
- uses: actions/checkout@v4
-
- - name: Install dependencies (doxygen+sphinx+breathe+exhale toolchain)
- run: |
- apt-get update
- apt-get install -q -y --no-install-recommends python3-pip doxygen
- apt-get install -q -y --no-install-recommends latexmk texlive-latex-extra tex-gyre texlive-fonts-recommended texlive-latex-recommended
- pip3 install exhale sphinx-sitemap sphinx-design sphinx-notfound-page
- pip3 install sphinxawesome-theme --pre
- pip3 install "sphinx<7,>6"
-
- - name: Parse C++ library with Doxygen
- working-directory: ${{ env.CATKIN_WS_PATH }}/src/${{ env.REPOSITORY_NAME }}/docs
- shell: bash
- run: doxygen Doxyfile_cpp
-
- - name: Parse ROS1 interface with Doxygen
- working-directory: ${{ env.CATKIN_WS_PATH }}/src/${{ env.REPOSITORY_NAME }}/docs
- shell: bash
- run: doxygen Doxyfile_ros1
-
- - name: Build documentation site
- working-directory: ${{ env.CATKIN_WS_PATH }}/src/${{ env.REPOSITORY_NAME }}/docs
- shell: bash
- run: sphinx-build -b html . _build/html
-
- - name: Bundle site sources into tarball
- shell: bash
- run: |
- tar \
- --dereference --hard-dereference \
- --directory ${{ env.CATKIN_WS_PATH }}/src/${{ env.REPOSITORY_NAME }}/docs/_build/html/ \
- -cvf ${{ env.CATKIN_WS_PATH }}/src/${{ env.REPOSITORY_NAME }}/docs/artifact.tar \
- --exclude=.git \
- --exclude=.github \
- .
-
- - name: Upload tarball as GH Pages artifact
- uses: actions/upload-artifact@main
- with:
- name: github-pages
- path: ${{ env.CATKIN_WS_PATH }}/src/${{ env.REPOSITORY_NAME }}/docs/artifact.tar
- retention-days: 1
-
- - name: Build documentation PDF
- working-directory: ${{ env.CATKIN_WS_PATH }}/src/${{ env.REPOSITORY_NAME }}/docs
- shell: bash
- run: sphinx-build -M latexpdf . _build/latex
-
- - name: Upload PDF
- uses: actions/upload-artifact@main
- with:
- name: documentation-pdf
- path: ${{ env.CATKIN_WS_PATH }}/src/${{ env.REPOSITORY_NAME }}/docs/_build/latex/latex/wavemap.pdf
- retention-days: 3
-
- build:
- name: Build
- needs: workspace-container
- runs-on: [ self-hosted, vwire ]
- container:
- image: ${{ needs.workspace-container.outputs.image }}
- steps:
- - name: Fetch the package's repository
- uses: actions/checkout@v4
- # NOTE: Even though the repo is already present in the container, we
- # also need to check it out at GitHub Actions' preferred location
- # for private actions and problem matchers to work.
-
- - name: Setup ccache
- uses: ./.github/actions/setup-ccache
- with:
- cache-group: noetic-gcc-release
- cache-version: ${{ secrets.CCACHE_CACHE_VERSION }}
-
- - name: Build all wavemap packages
- working-directory: ${{ env.CATKIN_WS_PATH }}
- shell: bash
- run: |
- echo "::add-matcher::./.github/problem-matchers/gcc.json"
- catkin build wavemap_all --no-status --force-color
- echo "::remove-matcher owner=problem-matcher-gcc::"
-
- - name: Show statistics for ccache
- uses: ./.github/actions/log-ccache-stats
-
- install:
- name: Catkin install
- needs: [ workspace-container, build ]
- runs-on: [ self-hosted, vwire ]
- container:
- image: ${{ needs.workspace-container.outputs.image }}
- steps:
- - name: Fetch the package's repository
- uses: actions/checkout@v4
- # NOTE: Even though the repo is already present in the container, we
- # also need to check it out at GitHub Actions' preferred location
- # for private actions and problem matchers to work.
-
- - name: Setup ccache
- uses: ./.github/actions/setup-ccache
- with:
- cache-group: noetic-gcc-release
- cache-version: ${{ secrets.CCACHE_CACHE_VERSION }}
-
- - name: Enable catkin install
- working-directory: ${{ env.CATKIN_WS_PATH }}
- shell: bash
- run: |
- catkin config --install
- catkin clean -b -y
-
- - name: Build all wavemap packages
- working-directory: ${{ env.CATKIN_WS_PATH }}
- shell: bash
- run: |
- . /opt/ros/noetic/setup.sh
- echo "::add-matcher::./.github/problem-matchers/gcc.json"
- catkin build wavemap_all --no-status --force-color
- echo "::remove-matcher owner=problem-matcher-gcc::"
-
- - name: Show statistics for ccache
- uses: ./.github/actions/log-ccache-stats
-
- clang-tidy:
- name: Clang tidy
- needs: [ workspace-container, build ]
- runs-on: [ self-hosted, vwire ]
- container:
- image: ${{ needs.workspace-container.outputs.image }}
- steps:
- - name: Fetch the package's repository
- uses: actions/checkout@v4
- # NOTE: Even though the repo is already present in the container, we
- # also need to check it out at GitHub Actions' preferred location
- # for private actions and problem matchers to work.
-
- - name: Setup ccache
- uses: ./.github/actions/setup-ccache
- with:
- cache-group: noetic-gcc-release
- cache-version: ${{ secrets.CCACHE_CACHE_VERSION }}
-
- - name: Install clang-tidy
- run: |
- apt-get update
- apt-get install -q -y --no-install-recommends clang-tidy
-
- - name: Build catkin package and dependencies
- working-directory: ${{ env.CATKIN_WS_PATH }}
- shell: bash
- run: catkin build wavemap_all --no-status --force-color --cmake-args -DUSE_CLANG_TIDY=ON
-
- - name: Run clang-tidy for wavemap
- working-directory: ${{ env.CATKIN_WS_PATH }}/build/wavemap
- run: |
- echo "::add-matcher::./.github/problem-matchers/clang-tidy.json"
- run-clang-tidy -header-filter="*include/wavemap/*" -quiet
- echo "::remove-matcher owner=problem-matcher-clang-tidy::"
-
- - name: Run clang-tidy for wavemap_ros
- working-directory: ${{ env.CATKIN_WS_PATH }}/build/wavemap_ros
- run: |
- echo "::add-matcher::./.github/problem-matchers/clang-tidy.json"
- run-clang-tidy -header-filter="*include/wavemap_ros/*" -quiet
- echo "::remove-matcher owner=problem-matcher-clang-tidy::"
-
- test:
- name: Test
- needs: [ workspace-container, build ]
- runs-on: [ self-hosted, vwire ]
- container:
- image: ${{ needs.workspace-container.outputs.image }}
- steps:
- - name: Fetch the package's repository
- uses: actions/checkout@v4
- # NOTE: Even though the repo is already present in the container, we
- # also need to check it out at GitHub Actions' preferred location
- # for private actions and problem matchers to work.
-
- - name: Setup ccache
- uses: ./.github/actions/setup-ccache
- with:
- cache-group: noetic-gcc-release
- cache-version: ${{ secrets.CCACHE_CACHE_VERSION }}
-
- - name: Build regular code
- working-directory: ${{ env.CATKIN_WS_PATH }}
- shell: bash
- run: catkin build wavemap_all --no-status --force-color --cmake-args -DDCHECK_ALWAYS_ON=ON
-
- - name: Build unit tests
- working-directory: ${{ env.CATKIN_WS_PATH }}
- shell: bash
- run: |
- echo "::add-matcher::./.github/problem-matchers/gcc.json"
- catkin build wavemap_all --no-status --force-color --no-deps --cmake-args -DDCHECK_ALWAYS_ON=ON --catkin-make-args tests
- echo "::remove-matcher owner=problem-matcher-gcc::"
-
- - name: Run unit tests
- working-directory: ${{ env.CATKIN_WS_PATH }}
- shell: bash
- run: |
- all_tests_passed=1
- source devel/setup.bash
- for f in devel/lib/wavemap*/test_*
- do $f --gtest_color=yes || all_tests_passed=0
- done
- if [ $all_tests_passed -ne 1 ]; then
- echo "Not all tests passed!"
- exit 1
- fi
-
- - name: Show statistics for ccache
- uses: ./.github/actions/log-ccache-stats
-
- coverage:
- name: Coverage
- # TODO(victorr): Enable this again once it has been updated to work with the new package structure
- if: ${{ false }}
- needs: [ workspace-container, test ]
- runs-on: [ self-hosted, vwire ]
- container:
- image: ${{ needs.workspace-container.outputs.image }}
- steps:
- - name: Fetch the package's repository
- uses: actions/checkout@v4
- # NOTE: Even though the repo is already present in the container, we
- # also need to check it out at GitHub Actions' preferred location
- # for private actions and problem matchers to work.
-
- - name: Setup ccache
- uses: ./.github/actions/setup-ccache
- with:
- cache-group: noetic-gcc-debug
- cache-version: ${{ secrets.CCACHE_CACHE_VERSION }}
-
- - name: Install lcov for coverage report generation
- run: |
- apt-get update
- apt-get install -q -y --no-install-recommends lcov
-
- - name: Switch catkin workspace to debug mode
- working-directory: ${{ env.CATKIN_WS_PATH }}
- shell: bash
- run: |
- catkin clean -y
- catkin config --cmake-args -DCMAKE_BUILD_TYPE=Debug
-
- - name: Rebuild dependencies and build regular code (in debug mode)
- working-directory: ${{ env.CATKIN_WS_PATH }}
- shell: bash
- run: |
- source /opt/ros/noetic/setup.bash
- catkin build wavemap_all --no-status --force-color
-
- - name: Build unit tests
- working-directory: ${{ env.CATKIN_WS_PATH }}
- shell: bash
- run: catkin build wavemap_all --no-status --force-color --no-deps --cmake-args -DENABLE_COVERAGE_TESTING=ON --catkin-make-args tests
-
- - name: Set coverage counters to zero and create report base
- working-directory: ${{ env.CATKIN_WS_PATH }}/build/wavemap
- shell: bash
- run: |
- lcov --zerocounters --directory .
- lcov --capture --initial --directory . --output-file wavemap_coverage_base.info
-
- - name: Run all tests while measuring coverage
- working-directory: ${{ env.CATKIN_WS_PATH }}
- shell: bash
- run: |
- all_tests_passed=1
- for f in devel/lib/wavemap*/test_*
- do
- $f --gtest_color=yes || all_tests_passed=0
- done
- if [ $all_tests_passed -ne 1 ]; then
- echo "Not all tests passed! Note that the code is currently compiled"\
- "in Debug mode, so some additional errors may be caught compared"\
- "to previous test runs in Release mode (e.g. failing DCHECKs)."
- exit 1
- fi
-
- - name: Create the coverage report
- working-directory: ${{ env.CATKIN_WS_PATH }}/build/wavemap
- shell: bash
- run: |
- lcov --capture --directory . --output-file wavemap_coverage_unit_tests.info
- lcov --add-tracefile wavemap_coverage_base.info --add-tracefile wavemap_coverage_unit_tests.info --output-file wavemap_coverage_total.info
- lcov --extract wavemap_coverage_total.info '*/wavemap/wavemap*' --output-file wavemap_coverage_filtered_intermediate.info
- lcov --remove wavemap_coverage_filtered_intermediate.info '*/wavemap/test/*' '*/wavemap/app/*' '*/wavemap/benchmark/*' --output-file wavemap_coverage.info
- rm wavemap_coverage_base.info wavemap_coverage_unit_tests.info wavemap_coverage_total.info wavemap_coverage_filtered_intermediate.info
- lcov --list wavemap_coverage.info # Include report in logs for debugging
-
- - name: Upload coverage stats to Codecov
- uses: codecov/codecov-action@v2
- with:
- token: ${{ secrets.CODECOV_TOKEN }}
- directory: ${{ env.CATKIN_WS_PATH }}/build/wavemap
- flags: unittests
- fail_ci_if_error: true
- verbose: true
-
- - name: Show statistics for ccache
- uses: ./.github/actions/log-ccache-stats
-
- sanitize:
- name: Sanitize ${{ matrix.sanitizer.detects }}
- needs: [ workspace-container, test ]
- runs-on: [ self-hosted, vwire ]
- container:
- image: ${{ needs.workspace-container.outputs.image }}
- strategy:
- matrix:
- sanitizer:
- - { name: UBSAN, detects: 'undefined behavior' }
- - { name: ASAN, detects: 'addressability and leaks' }
- # - { name: TSAN, detects: 'data races and deadlocks' }
- # NOTE: TSAN is disabled until the following bug is resolved:
- # https://bugs.launchpad.net/ubuntu/+source/gcc-10/+bug/2029910.
- # NOTE: MSAN is not used for now since it also requires all deps to be
- # instrumented (recompiled with clang and the MSan flags, LLVM's
- # stdlib instead of GCCs,...). We therefore use Valgrind to
- # check for uninitialized memory usage errors instead.
- fail-fast: false
- steps:
- - name: Fetch the package's repository
- uses: actions/checkout@v4
- # NOTE: Even though the repo is already present in the container, we
- # also need to check it out at GitHub Actions' preferred location
- # for private actions and problem matchers to work.
-
- - name: Setup ccache
- uses: ./.github/actions/setup-ccache
- with:
- cache-group: noetic-gcc-release
- cache-version: ${{ secrets.CCACHE_CACHE_VERSION }}
-
- - name: Build regular code
- working-directory: ${{ env.CATKIN_WS_PATH }}
- shell: bash
- run: catkin build wavemap_all --no-status --force-color
-
- - name: Build unit tests
- working-directory: ${{ env.CATKIN_WS_PATH }}
- shell: bash
- run: catkin build wavemap_all --no-status --force-color --no-deps --cmake-args -DUSE_${{ matrix.sanitizer.name }}=ON --catkin-make-args tests
-
- - name: Check unit tests with ${{ matrix.sanitizer.name }}
- working-directory: ${{ env.CATKIN_WS_PATH }}
- env:
- UBSAN_OPTIONS: halt_on_error=1:print_stacktrace=1
- ASAN_OPTIONS: halt_on_error=1:detect_leaks=1:detect_stack_use_after_return=1
- TSAN_OPTIONS: halt_on_error=1:second_deadlock_stack=1
- shell: bash
- run: |
- echo "::add-matcher::./.github/problem-matchers/gcc-sanitizers.json"
- all_tests_passed=1
- for f in devel/lib/wavemap*/test_*
- do $f --gtest_color=yes || all_tests_passed=0
- done
- if [ $all_tests_passed -ne 1 ]; then
- echo "Not all tests passed!"
- exit 1
- fi
- echo "::remove-matcher owner=problem-matcher-gcc-ubsan::"
- echo "::remove-matcher owner=problem-matcher-gcc-asan::"
- echo "::remove-matcher owner=problem-matcher-gcc-tsan::"
-
- - name: Show statistics for ccache
- uses: ./.github/actions/log-ccache-stats
-
- valgrind:
- name: Valgrind memcheck
- needs: [ workspace-container, test ]
- runs-on: [ self-hosted, vwire ]
- container:
- image: ${{ needs.workspace-container.outputs.image }}
- steps:
- - name: Fetch the package's repository
- uses: actions/checkout@v4
- # NOTE: Even though the repo is already present in the container, we
- # also need to check it out at GitHub Actions' preferred location
- # for private actions and problem matchers to work.
-
- - name: Setup ccache
- uses: ./.github/actions/setup-ccache
- with:
- cache-group: noetic-gcc-release
- cache-version: ${{ secrets.CCACHE_CACHE_VERSION }}
-
- - name: Install Valgrind
- run: |
- apt-get update
- apt-get install -q -y --no-install-recommends valgrind
-
- - name: Build regular code
- working-directory: ${{ env.CATKIN_WS_PATH }}
- shell: bash
- run: catkin build wavemap_all --no-status --force-color
-
- - name: Build unit tests
- working-directory: ${{ env.CATKIN_WS_PATH }}
- shell: bash
- run: catkin build wavemap_all --no-status --force-color --no-deps --catkin-make-args tests
-
- - name: Check unit tests with Valgrind memcheck
- working-directory: ${{ env.CATKIN_WS_PATH }}
- shell: bash
- run: |
- echo "::add-matcher::./.github/problem-matchers/valgrind.json"
- all_tests_passed=1
- source devel/setup.bash
- for f in devel/lib/wavemap*/test_*
- do valgrind --tool=memcheck --leak-check=full --leak-resolution=high --num-callers=20 --track-origins=yes --show-possibly-lost=no --errors-for-leak-kinds=definite,indirect --error-exitcode=1 --xml=yes --xml-file=valgrind-log.xml $f --gtest_color=yes || all_tests_passed=0
- grep -Poz '(?<=)(.*\n)*.*(?=)' valgrind-log.xml || true
- done
- if [ $all_tests_passed -ne 1 ]; then
- echo "Not all tests passed!"
- exit 1
- fi
- echo "::remove-matcher owner=problem-matcher-valgrind::"
-
- - name: Show statistics for ccache
- uses: ./.github/actions/log-ccache-stats
diff --git a/.github/workflows/cpp.yml b/.github/workflows/cpp.yml
new file mode 100644
index 000000000..fa8896558
--- /dev/null
+++ b/.github/workflows/cpp.yml
@@ -0,0 +1,239 @@
+name: C++ API
+
+on:
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ build:
+ name: Build
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ ubuntu-20.04, ubuntu-22.04, ubuntu-24.04 ]
+ fail-fast: false
+ steps:
+ - name: Fetch the package's repository
+ uses: actions/checkout@v4
+
+ - name: Setup CMake
+ uses: jwlawson/actions-setup-cmake@v2
+ with:
+ cmake-version: '3.18'
+
+ - name: Setup ccache
+ uses: hendrikmuhs/ccache-action@v1.2
+ with:
+ key: ${{ secrets.CCACHE_CACHE_VERSION }}|${{ matrix.os }}-gcc-release
+ create-symlink: true
+
+ - name: Configure CMake
+ working-directory: ${{github.workspace}}
+ run: cmake -B build -DCMAKE_BUILD_TYPE=Release library/cpp
+
+ - name: Build
+ working-directory: ${{github.workspace}}
+ run: |
+ echo "::add-matcher::./.github/problem-matchers/gcc.json"
+ cmake --build build --parallel --config Release
+ echo "::remove-matcher owner=problem-matcher-gcc::"
+
+ test:
+ name: Test
+ needs: build
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ ubuntu-20.04, ubuntu-22.04, ubuntu-24.04 ]
+ fail-fast: false
+ steps:
+ - name: Fetch the package's repository
+ uses: actions/checkout@v4
+
+ - name: Setup CMake
+ uses: jwlawson/actions-setup-cmake@v2
+ with:
+ cmake-version: '3.18'
+
+ - name: Setup ccache
+ uses: hendrikmuhs/ccache-action@v1.2
+ with:
+ key: ${{ secrets.CCACHE_CACHE_VERSION }}|${{ matrix.os }}-gcc-release
+ create-symlink: true
+
+ - name: Setup GTest
+ run: |
+ sudo apt-get update
+ sudo apt-get install -yq --no-install-recommends libgtest-dev
+
+ - name: Configure CMake
+ working-directory: ${{github.workspace}}
+ run: cmake -B build -DCMAKE_BUILD_TYPE=Release -DENABLE_TESTING=ON library/cpp
+
+ - name: Build tests
+ working-directory: ${{github.workspace}}
+ run: |
+ echo "::add-matcher::./.github/problem-matchers/gcc.json"
+ cmake --build build --parallel --config Release
+ echo "::remove-matcher owner=problem-matcher-gcc::"
+
+ - name: Run tests
+ working-directory: ${{github.workspace}}
+ run: |
+ all_tests_passed=1
+ for f in `find build/test/src/*/test_* -executable`; do
+ $f --gtest_color=yes || all_tests_passed=0
+ done
+ if [ $all_tests_passed -ne 1 ]; then
+ echo "Not all tests passed!"
+ exit 1
+ fi
+
+ clang-tidy:
+ name: Clang tidy
+ needs: build
+ runs-on: ubuntu-20.04
+ steps:
+ - name: Fetch the package's repository
+ uses: actions/checkout@v4
+
+ - name: Setup CMake
+ uses: jwlawson/actions-setup-cmake@v2
+ with:
+ cmake-version: '3.18'
+
+ - name: Setup clang-tidy and system deps
+ run: |
+ sudo apt-get update
+ sudo apt-get install -q -y --no-install-recommends clang-tidy
+ # NOTE: The following deps are installed s.t. clang-tidy correctly treats them as system deps
+ sudo apt-get install -q -y --no-install-recommends libeigen3-dev libgoogle-glog-dev libboost-dev
+
+ - name: Configure CMake
+ working-directory: ${{github.workspace}}
+ run: cmake -B build -DCMAKE_BUILD_TYPE=Release library/cpp
+
+ - name: Run clang-tidy
+ working-directory: ${{github.workspace}}/build
+ run: |
+ echo "::add-matcher::./.github/problem-matchers/clang-tidy.json"
+ run-clang-tidy -quiet -header-filter="*include/wavemap/*"
+ echo "::remove-matcher owner=problem-matcher-clang-tidy::"
+
+ valgrind:
+ name: Valgrind memcheck
+ needs: test
+ runs-on: ubuntu-20.04
+ steps:
+ - name: Fetch the package's repository
+ uses: actions/checkout@v4
+
+ - name: Setup CMake
+ uses: jwlawson/actions-setup-cmake@v2
+ with:
+ cmake-version: '3.18'
+
+ - name: Setup ccache
+ uses: hendrikmuhs/ccache-action@v1.2
+ with:
+ key: ${{ secrets.CCACHE_CACHE_VERSION }}|ubuntu-20.04-gcc-release
+ create-symlink: true
+
+ - name: Setup GTest and Valgrind
+ run: |
+ sudo apt-get update
+ sudo apt-get install -yq --no-install-recommends libgtest-dev valgrind
+
+ - name: Configure CMake
+ working-directory: ${{github.workspace}}
+ run: cmake -B build -DCMAKE_BUILD_TYPE=Release -DENABLE_TESTING=ON library/cpp
+
+ - name: Build tests
+ working-directory: ${{github.workspace}}
+ run: |
+ echo "::add-matcher::./.github/problem-matchers/gcc.json"
+ cmake --build build --parallel --config Release
+ echo "::remove-matcher owner=problem-matcher-gcc::"
+
+ - name: Check unit tests with Valgrind memcheck
+ working-directory: ${{github.workspace}}
+ run: |
+ all_tests_passed=1
+ echo "::add-matcher::./.github/problem-matchers/valgrind.json"
+ for f in `find build/test/src/*/test_* -executable`; do
+ valgrind --tool=memcheck --leak-check=full --leak-resolution=high --num-callers=20 --track-origins=yes --show-possibly-lost=no --errors-for-leak-kinds=definite,indirect --error-exitcode=1 --xml=yes --xml-file=valgrind-log.xml $f --gtest_color=yes || all_tests_passed=0
+ grep -Poz '(?<=)(.*\n)*.*(?=)' valgrind-log.xml || true
+ done
+ echo "::remove-matcher owner=problem-matcher-valgrind::"
+ if [ $all_tests_passed -ne 1 ]; then
+ echo "Not all tests passed!"
+ exit 1
+ fi
+
+ sanitize:
+ name: Sanitize ${{ matrix.sanitizer.detects }}
+ needs: test
+ runs-on: ${{ matrix.sanitizer.os }}
+ strategy:
+ matrix:
+ sanitizer:
+ - { name: UBSAN, detects: 'undefined behavior', os: ubuntu-20.04 }
+ - { name: ASAN, detects: 'addressability and leaks', os: ubuntu-20.04 }
+ - { name: TSAN, detects: 'data races and deadlocks', os: ubuntu-22.04 }
+ # NOTE: We run TSAN on Ubuntu 22.04 since it's broken on 20.04, see:
+ # https://bugs.launchpad.net/ubuntu/+source/gcc-10/+bug/2029910.
+ # NOTE: MSAN is not used for now since it also requires all deps to be
+ # instrumented (recompiled with clang and the MSan flags, LLVM's
+ # stdlib instead of GCCs,...). We therefore use Valgrind to
+ # check for uninitialized memory usage errors instead.
+ fail-fast: false
+ steps:
+ - name: Fetch the package's repository
+ uses: actions/checkout@v4
+
+ - name: Setup CMake
+ uses: jwlawson/actions-setup-cmake@v2
+ with:
+ cmake-version: '3.18'
+
+ - name: Setup ccache
+ uses: hendrikmuhs/ccache-action@v1.2
+ with:
+ key: ${{ secrets.CCACHE_CACHE_VERSION }}|${{ matrix.sanitizer.os }}-gcc-${{ matrix.sanitizer.name }}
+ create-symlink: true
+
+ - name: Setup GTest
+ run: |
+ sudo apt-get update
+ sudo apt-get install -yq --no-install-recommends libgtest-dev
+
+ - name: Configure CMake
+ working-directory: ${{github.workspace}}
+ run: cmake -B build -DCMAKE_BUILD_TYPE=Release -DENABLE_TESTING=ON -DUSE_${{ matrix.sanitizer.name }}=ON library/cpp
+
+ - name: Build tests
+ working-directory: ${{github.workspace}}
+ run: |
+ echo "::add-matcher::./.github/problem-matchers/gcc.json"
+ cmake --build build --parallel --config Release
+ echo "::remove-matcher owner=problem-matcher-gcc::"
+
+ - name: Check unit tests with ${{ matrix.sanitizer.name }}
+ working-directory: ${{github.workspace}}
+ env:
+ UBSAN_OPTIONS: halt_on_error=1:print_stacktrace=1
+ ASAN_OPTIONS: halt_on_error=1:detect_leaks=1:detect_stack_use_after_return=1
+ TSAN_OPTIONS: halt_on_error=1:second_deadlock_stack=1
+ run: |
+ all_tests_passed=1
+ echo "::add-matcher::./.github/problem-matchers/gcc-sanitizers.json"
+ for f in `find build/test/src/*/test_* -executable`; do
+ $f --gtest_color=yes || all_tests_passed=0
+ done
+ if [ $all_tests_passed -ne 1 ]; then
+ echo "Not all tests passed!"
+ exit 1
+ fi
+ echo "::remove-matcher owner=problem-matcher-gcc-ubsan::"
+ echo "::remove-matcher owner=problem-matcher-gcc-asan::"
+ echo "::remove-matcher owner=problem-matcher-gcc-tsan::"
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
new file mode 100644
index 000000000..469045bf7
--- /dev/null
+++ b/.github/workflows/docs.yml
@@ -0,0 +1,113 @@
+name: Documentation
+
+on:
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ build-docs:
+ name: Build
+ runs-on: ubuntu-20.04
+ steps:
+ - name: Fetch the package's repository
+ uses: actions/checkout@v4
+
+ - name: Install dependencies (doxygen+sphinx+breathe+exhale toolchain)
+ run: |
+ sudo apt-get update
+ sudo apt-get install -q -y --no-install-recommends python3-pip doxygen
+ sudo apt-get install -q -y --no-install-recommends latexmk texlive-latex-extra tex-gyre texlive-fonts-recommended texlive-latex-recommended
+ python3 -m pip install --upgrade pip
+ pip3 install exhale sphinx-sitemap sphinx-design sphinx-notfound-page
+ pip3 install sphinxawesome-theme --pre
+ pip3 install "sphinx<7,>6"
+
+ - name: Parse C++ API with Doxygen
+ working-directory: ${{github.workspace}}/docs
+ shell: bash
+ run: doxygen Doxyfile_cpp
+
+ - name: Parse ROS1 Interface with Doxygen
+ working-directory: ${{github.workspace}}/docs
+ shell: bash
+ run: doxygen Doxyfile_ros1
+
+ - name: Build Python API (parsed by Sphinx)
+ run: python -m pip install -v ./library/python/
+
+ - name: Build documentation site
+ working-directory: ${{github.workspace}}/docs
+ shell: bash
+ run: sphinx-build -b html . _build/html
+
+ - name: Bundle site sources into tarball
+ shell: bash
+ run: |
+ tar \
+ --dereference --hard-dereference \
+ --directory ${{github.workspace}}/docs/_build/html/ \
+ -cvf ${{github.workspace}}/docs/artifact.tar \
+ --exclude=.git \
+ --exclude=.github \
+ .
+
+ - name: Upload tarball as GH Pages artifact
+ uses: actions/upload-artifact@v4.4.0
+ with:
+ name: github-pages
+ path: ${{github.workspace}}/docs/artifact.tar
+ retention-days: 1
+
+ - name: Build documentation PDF
+ working-directory: ${{github.workspace}}/docs
+ shell: bash
+ run: sphinx-build -M latexpdf . _build/latex
+
+ - name: Upload PDF
+ uses: actions/upload-artifact@v4.4.0
+ with:
+ name: documentation-pdf
+ path: ${{github.workspace}}/docs/_build/latex/latex/wavemap.pdf
+ retention-days: 3
+
+ draft-release:
+ name: Draft Release
+ if: startsWith(github.event.ref, 'refs/tags/v')
+ needs: build-docs
+ runs-on: ubuntu-20.04
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - uses: actions/download-artifact@v4.1.8
+ with:
+ name: documentation-pdf
+
+ - name: Create Release
+ id: create_release
+ uses: softprops/action-gh-release@v2.0.8
+ with:
+ files: "wavemap.pdf"
+
+ publish-docs:
+ name: Publish to GH Pages
+ if: github.ref == 'refs/heads/main'
+ needs: build-docs
+ runs-on: ubuntu-20.04
+ permissions:
+ contents: read
+ pages: write
+ id-token: write
+ concurrency:
+ group: "pages"
+ cancel-in-progress: true
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ steps:
+ - name: Setup Pages
+ uses: actions/configure-pages@v4
+
+ - name: Deploy uploaded docs to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v3
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 000000000..f195159a6
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -0,0 +1,39 @@
+name: Lint
+
+on:
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ pre-commit:
+ name: Pre-commit
+ runs-on: ubuntu-20.04
+ steps:
+ - name: Install pre-commit's dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -q -y --no-install-recommends git python3-pip clang-format-11 cppcheck libxml2-utils wget
+ pip3 install pre-commit cpplint
+ sudo wget -O /bin/hadolint https://github.com/hadolint/hadolint/releases/download/v2.8.0/hadolint-Linux-x86_64
+ sudo chmod +x /bin/hadolint
+
+ - name: Fetch the package's repository
+ uses: actions/checkout@v4
+ # NOTE: This has to be done after installing pre-commit, s.t. the
+ # pre-commit hooks are automatically initialized.
+
+ - name: Get python version for pre-commit cache
+ run: echo "PRE_COMMIT_PYTHON_VERSION=$(python -VV | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV
+
+ - name: Setup pre-commit cache sharing
+ uses: actions/cache@v4
+ with:
+ path: ~/.cache/pre-commit
+ key: pre-commit|${{ env.PRE_COMMIT_PYTHON_VERSION }}|${{ hashFiles('.pre-commit-config.yaml') }}
+
+ - name: Run the pre-commit hooks
+ shell: bash
+ run: |
+ echo "::add-matcher::./.github/problem-matchers/pre-commit.json"
+ PRE_COMMIT_HOME=~/.cache/pre-commit SKIP=no-commit-to-branch pre-commit run --all-files
+ echo "::remove-matcher owner=problem-matcher-pre-commit::"
diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml
new file mode 100644
index 000000000..49d192a97
--- /dev/null
+++ b/.github/workflows/python.yml
@@ -0,0 +1,67 @@
+name: Python API
+
+on:
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ build:
+ name: Build
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ubuntu-20.04, ubuntu-22.04, ubuntu-24.04]
+ fail-fast: false
+ steps:
+ - name: Fetch the package's repository
+ uses: actions/checkout@v4
+
+ - name: Setup Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '>=3.8'
+
+ - name: Activate virtual environment
+ run: |
+ python -m venv my-venv
+ source my-venv/bin/activate
+ echo PATH=$PATH >> $GITHUB_ENV
+
+ - name: Upgrade pip
+ if: matrix.os == 'ubuntu-20.04'
+ run: python -m pip install --upgrade pip
+
+ - name: Build
+ run: python -m pip install -v ./library/python/
+
+ test:
+ name: Test
+ needs: build
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ubuntu-20.04, ubuntu-22.04, ubuntu-24.04]
+ fail-fast: false
+ steps:
+ - name: Fetch the package's repository
+ uses: actions/checkout@v4
+
+ - name: Setup Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '>=3.8'
+
+ - name: Activate virtual environment
+ run: |
+ python -m venv my-venv
+ source my-venv/bin/activate
+ echo PATH=$PATH >> $GITHUB_ENV
+
+ - name: Upgrade pip
+ if: matrix.os == 'ubuntu-20.04'
+ run: python -m pip install --upgrade pip
+
+ - name: Test
+ run: |
+ python -m pip install -v './library/python[test]'
+ pytest -rAv ./library/python/
diff --git a/.github/workflows/ros1.yml b/.github/workflows/ros1.yml
new file mode 100644
index 000000000..30bcf2706
--- /dev/null
+++ b/.github/workflows/ros1.yml
@@ -0,0 +1,217 @@
+name: ROS1 Interface
+
+on:
+ pull_request:
+ branches: [ main ]
+
+env:
+ DOCKER_REGISTRY: ghcr.io
+ DOCKER_CI_IMAGE_NAME: ci_wavemap_ros1
+ DOCKER_RELEASE_IMAGE_NAME: wavemap_ros1
+ USER_HOME: /home/ci
+ CATKIN_WS_PATH: /home/ci/catkin_ws
+ CCACHE_DIR: /home/ci/ccache
+ PRE_COMMIT_DIR: /home/ci/pre-commit
+
+jobs:
+ workspace-container:
+ name: Build ROS1 container
+ runs-on: ubuntu-20.04
+ outputs:
+ image: ${{ steps.ref-names.outputs.ci_image }}
+ steps:
+ - name: Common variables
+ id: ref-names
+ run: |
+ echo "cache=${{ env.DOCKER_REGISTRY }}/ethz-asl/${{ env.DOCKER_CI_IMAGE_NAME }}:buildcache" >> $GITHUB_OUTPUT
+ echo "ci_image=${{ env.DOCKER_REGISTRY }}/ethz-asl/${{ env.DOCKER_CI_IMAGE_NAME }}:${{ github.sha }}" >> $GITHUB_OUTPUT
+
+ - name: Fetch the package's repository
+ uses: actions/checkout@v4
+ with:
+ path: ${{ github.repository }}
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Log in to ${{ env.DOCKER_REGISTRY }} registry
+ uses: docker/login-action@v3.3.0
+ with:
+ registry: ${{ env.DOCKER_REGISTRY }}
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Build the image
+ uses: docker/build-push-action@v6
+ env:
+ DOCKER_BUILD_SUMMARY: false
+ DOCKER_BUILD_RECORD_UPLOAD: false
+ with:
+ context: ${{ github.repository }}
+ file: ${{ github.repository }}/tooling/docker/ros1/full.Dockerfile
+ target: workspace
+ build-args: |
+ REPOSITORY_NAME=${{ github.repository }}
+ USER_HOME=${{ env.USER_HOME }}
+ CATKIN_WS_PATH=${{ env.CATKIN_WS_PATH }}
+ CCACHE_DIR=${{ env.CCACHE_DIR }}
+ load: true
+ cache-from: type=registry,ref=${{ steps.ref-names.outputs.cache }}
+ cache-to: type=registry,mode=max,ref=${{ steps.ref-names.outputs.cache }}
+ tags: ${{ steps.ref-names.outputs.ci_image }}
+
+ - name: Test the image
+ run: docker run --rm ${{ steps.ref-names.outputs.ci_image }}
+
+ - name: Push the CI image
+ uses: docker/build-push-action@v6
+ env:
+ DOCKER_BUILD_SUMMARY: false
+ DOCKER_BUILD_RECORD_UPLOAD: false
+ with:
+ context: ${{ github.repository }}
+ file: ${{ github.repository }}/tooling/docker/ros1/full.Dockerfile
+ target: workspace
+ build-args: |
+ REPOSITORY_NAME=${{ github.repository }}
+ USER_HOME=${{ env.USER_HOME }}
+ CATKIN_WS_PATH=${{ env.CATKIN_WS_PATH }}
+ CCACHE_DIR=${{ env.CCACHE_DIR }}
+ push: true
+ cache-from: type=registry,ref=${{ steps.ref-names.outputs.cache }}
+ tags: ${{ steps.ref-names.outputs.ci_image }}
+
+ - name: Generate release image metadata
+ if: startsWith(github.event.ref, 'refs/tags/v')
+ id: meta
+ uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
+ with:
+ images: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_RELEASE_IMAGE_NAME }}
+
+ - name: Publish the release image
+ if: startsWith(github.event.ref, 'refs/tags/v')
+ uses: docker/build-push-action@v6
+ env:
+ DOCKER_BUILD_SUMMARY: false
+ DOCKER_BUILD_RECORD_UPLOAD: false
+ with:
+ context: ${{ github.repository }}
+ file: ${{ github.repository }}/tooling/docker/ros1/full.Dockerfile
+ target: workspace
+ build-args: |
+ REPOSITORY_NAME=${{ github.repository }}
+ USER_HOME=${{ env.USER_HOME }}
+ CATKIN_WS_PATH=${{ env.CATKIN_WS_PATH }}
+ CCACHE_DIR=${{ env.CCACHE_DIR }}
+ push: true
+ cache-from: type=registry,ref=${{ steps.ref-names.outputs.cache }}
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
+
+ build:
+ name: Build
+ needs: workspace-container
+ runs-on: ubuntu-20.04
+ container:
+ image: ${{ needs.workspace-container.outputs.image }}
+ steps:
+ - name: Fetch the package's repository
+ uses: actions/checkout@v4
+ # NOTE: Even though the repo is already present in the container, we
+ # also need to check it out at GitHub Actions' preferred location
+ # for private actions and problem matchers to work.
+
+ - name: Setup ccache
+ uses: hendrikmuhs/ccache-action@v1.2
+ with:
+ key: ${{ secrets.CCACHE_CACHE_VERSION }}|ubuntu-20.04-gcc-ros1
+ create-symlink: true
+
+ - name: Build all wavemap packages
+ working-directory: ${{ env.CATKIN_WS_PATH }}
+ shell: bash
+ run: |
+ echo "::add-matcher::./.github/problem-matchers/gcc.json"
+ catkin build wavemap_all --no-status --force-color
+ echo "::remove-matcher owner=problem-matcher-gcc::"
+
+ install:
+ name: Install
+ needs: [ workspace-container, build ]
+ runs-on: ubuntu-20.04
+ container:
+ image: ${{ needs.workspace-container.outputs.image }}
+ steps:
+ - name: Fetch the package's repository
+ uses: actions/checkout@v4
+ # NOTE: Even though the repo is already present in the container, we
+ # also need to check it out at GitHub Actions' preferred location
+ # for private actions and problem matchers to work.
+
+ - name: Setup ccache
+ uses: hendrikmuhs/ccache-action@v1.2
+ with:
+ key: ${{ secrets.CCACHE_CACHE_VERSION }}|ubuntu-20.04-gcc-ros1
+ create-symlink: true
+
+ - name: Enable catkin install mode
+ working-directory: ${{ env.CATKIN_WS_PATH }}
+ shell: bash
+ run: |
+ catkin config --install
+ catkin clean -bdi -y
+
+ - name: Build all wavemap packages
+ working-directory: ${{ env.CATKIN_WS_PATH }}
+ shell: bash
+ run: |
+ . /opt/ros/noetic/setup.sh
+ echo "::add-matcher::./.github/problem-matchers/gcc.json"
+ catkin build wavemap_all --no-status --force-color
+ echo "::remove-matcher owner=problem-matcher-gcc::"
+
+ test:
+ name: Test
+ needs: [ workspace-container, build ]
+ runs-on: ubuntu-20.04
+ container:
+ image: ${{ needs.workspace-container.outputs.image }}
+ steps:
+ - name: Fetch the package's repository
+ uses: actions/checkout@v4
+ # NOTE: Even though the repo is already present in the container, we
+ # also need to check it out at GitHub Actions' preferred location
+ # for private actions and problem matchers to work.
+
+ - name: Setup ccache
+ uses: hendrikmuhs/ccache-action@v1.2
+ with:
+ key: ${{ secrets.CCACHE_CACHE_VERSION }}|ubuntu-20.04-gcc-ros1
+ create-symlink: true
+
+ - name: Build regular code
+ working-directory: ${{ env.CATKIN_WS_PATH }}
+ shell: bash
+ run: catkin build wavemap_all --no-status --force-color --cmake-args -DDCHECK_ALWAYS_ON=ON
+
+ - name: Build unit tests
+ working-directory: ${{ env.CATKIN_WS_PATH }}
+ shell: bash
+ run: |
+ echo "::add-matcher::./.github/problem-matchers/gcc.json"
+ catkin build wavemap_all --no-status --force-color --no-deps --cmake-args -DDCHECK_ALWAYS_ON=ON --catkin-make-args tests
+ echo "::remove-matcher owner=problem-matcher-gcc::"
+
+ - name: Run unit tests
+ working-directory: ${{ env.CATKIN_WS_PATH }}
+ shell: bash
+ run: |
+ all_tests_passed=1
+ source devel/setup.bash
+ for f in devel/lib/wavemap*/test_*
+ do $f --gtest_color=yes || all_tests_passed=0
+ done
+ if [ $all_tests_passed -ne 1 ]; then
+ echo "Not all tests passed!"
+ exit 1
+ fi
diff --git a/.gitignore b/.gitignore
index 136aa91d0..7874f4da8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,10 +34,12 @@ CMakeLists.txt.user
srv/_*.py
*.pcd
-*.pyc
qtcreator-*
*.user
+# Python
+*.pyc
+
/planning/cfg
/planning/docs
/planning/src
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index a87c21e1e..cf90d7beb 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -67,7 +67,8 @@ repos:
--library=googletest, --library=tooling/cppcheck/gazebo,
"--enable=warning,performance,portability",
"--suppress=constStatement",
- "--suppress=syntaxError:*test/*/test_*.cc" ]
+ "--suppress=syntaxError:*test/*/test_*.cc",
+ "--suppress=assignBoolToPointer:library/python/*"]
- repo: https://github.com/cheshirekow/cmake-format-precommit
rev: v0.6.13
diff --git a/CITATION.cff b/CITATION.cff
new file mode 100644
index 000000000..f3f5de185
--- /dev/null
+++ b/CITATION.cff
@@ -0,0 +1,18 @@
+cff-version: 1.2.0
+preferred-citation:
+ title: "Efficient volumetric mapping of multi-scale environments using wavelet-based compression"
+ authors:
+ - family-names: Reijgwart
+ given-names: Victor
+ - family-names: Cadena
+ given-names: Cesar
+ - family-names: Siegwart
+ given-names: Roland
+ - family-names: Ott
+ given-names: Lionel
+ journal: "Robotics: Science and Systems"
+ year: "2023"
+ type: conference-paper
+ doi: "10.15607/RSS.2023.XIX.065"
+ url: https://www.roboticsproceedings.org/rss19/p065.pdf
+ codeurl: https://github.com/ethz-asl/wavemap
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0a131f7a4..46ad0be2a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -18,13 +18,16 @@ if ("$ENV{ROS_VERSION}" STREQUAL "1")
include_directories(${CATKIN_WS_DEVEL_PATH}/include)
# ROS interfaces and tooling
- # NOTE: Wavemap's C++ library gets included through interfaces/ros1/wavemap.
add_subdirectory(interfaces/ros1/wavemap)
add_subdirectory(interfaces/ros1/wavemap_msgs)
add_subdirectory(interfaces/ros1/wavemap_ros_conversions)
add_subdirectory(interfaces/ros1/wavemap_ros)
add_subdirectory(interfaces/ros1/wavemap_rviz_plugin)
+ # Libraries
+ # NOTE: Wavemap's C++ lib is already included through interfaces/ros1/wavemap.
+ add_subdirectory(library/python)
+
# Usage examples
add_subdirectory(examples/cpp)
add_subdirectory(examples/ros1)
@@ -36,8 +39,13 @@ elseif ("$ENV{ROS_VERSION}" STREQUAL "2")
else ()
# Load in pure CMake mode
- # In this mode, introspection is available only for the C++ library
+ # In this mode, introspection is available only for the C++ and python libs
+
+ # Libraries
add_subdirectory(library/cpp)
+ add_subdirectory(library/python)
+
+ # Usage examples
add_subdirectory(examples/cpp)
endif ()
diff --git a/README.md b/README.md
index a88248625..737a112e6 100644
--- a/README.md
+++ b/README.md
@@ -1,31 +1,24 @@
# Wavemap
-
-
-
+
+
+
+
+
+
[![3D reconstruction of Newer College's Cloister](https://github.com/ethz-asl/wavemap/assets/6238939/e432d4ea-440d-4e9d-adf9-af3ae3b09a10)](https://www.youtube.com/live/ftQhK75Ri1E?si=9txTYyJ78wQuhyN-&t=733)
## Hierarchical, multi-resolution volumetric mapping
-
Wavemap achieves state-of-the-art memory and computational efficiency by combining Haar wavelet compression and a coarse-to-fine measurement integration scheme. Advanced measurement models allow it to attain exceptionally high recall rates on challenging obstacles like thin objects.
-The framework is very flexible and supports several data structures, measurement integration methods, and sensor models out of the box. The ROS interface can, for example, easily be configured to fuse multiple sensor inputs, such as a LiDAR configured with a range of 20m and several depth cameras up to a resolution of 1cm, into a single map.
+The framework is very flexible and supports several data structures, measurement integration methods, and sensor models out of the box. The ROS interface can, for example, easily be configured to fuse multiple sensor inputs, such as a LiDAR configured with a range of 20m and several depth cameras up to a resolution of 1cm, into a single multi-resolution occupancy grid map.
+
+Wavemap provides [C++](https://ethz-asl.github.io/wavemap/pages/tutorials/cpp) and [Python](https://ethz-asl.github.io/wavemap/pages/tutorials/python) APIs and an interface to [ROS1](https://ethz-asl.github.io/wavemap/pages/tutorials/ros1). The code is extensively tested on Intel, AMD and ARM CPUs on Ubuntu 20.04, 22.04 and 24.04. Example Docker files [are available](https://github.com/ethz-asl/wavemap/tree/main/tooling/docker) and documented in the [installation instructions](https://ethz-asl.github.io/wavemap/pages/installation/index). We [welcome contributions](https://ethz-asl.github.io/wavemap/pages/contributing).
⭐ If you find wavemap useful, star it on GitHub to get notified of new releases!
+
## Documentation
-The framework's documentation is hosted on [GitHub Pages](https://ethz-asl.github.io/wavemap/).
+The framework's documentation is available on [GitHub Pages](https://ethz-asl.github.io/wavemap/) for easy online access. A PDF version of each release’s documentation can also be found in the respective [release notes](https://github.com/ethz-asl/wavemap/releases).
### Table of contents
* [Installation](https://ethz-asl.github.io/wavemap/pages/installation)
@@ -33,7 +26,8 @@ The framework's documentation is hosted on [GitHub Pages](https://ethz-asl.githu
* [Tutorials](https://ethz-asl.github.io/wavemap/pages/tutorials)
* [Parameters](https://ethz-asl.github.io/wavemap/pages/parameters)
* [Contributing](https://ethz-asl.github.io/wavemap/pages/contributing)
-* [Library API](https://ethz-asl.github.io/wavemap/cpp_api/unabridged_api)
+* [C++ API](https://ethz-asl.github.io/wavemap/cpp_api/unabridged_api)
+* [Python API](https://ethz-asl.github.io/wavemap/python_api)
* [FAQ](https://ethz-asl.github.io/wavemap/pages/faq)
## Paper
diff --git a/docs/_static/custom.css b/docs/_static/custom.css
new file mode 100644
index 000000000..9630190d8
--- /dev/null
+++ b/docs/_static/custom.css
@@ -0,0 +1,9 @@
+#content h5 {
+ font-weight: 600;
+ line-height: 1.75rem;
+ margin-top: 1.5rem
+}
+
+#content span {
+ scroll-margin: 5rem;
+}
diff --git a/docs/conf.py b/docs/conf.py
index 2f061f716..2f9e12e28 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -19,8 +19,8 @@
# General configuration
extensions = [
'sphinx.ext.mathjax', "sphinx.ext.extlinks", 'sphinx.ext.githubpages',
- 'sphinx_design', 'sphinx_sitemap', 'notfound.extension', 'breathe',
- 'exhale'
+ 'sphinx.ext.autodoc', 'sphinx_design', 'sphinx_sitemap',
+ 'notfound.extension', 'breathe', 'exhale'
]
templates_path = ['_templates']
source_suffix = ['.rst', '.md']
@@ -53,7 +53,7 @@
"mode": "production",
}
html_static_path = ["_static"]
-html_css_files = []
+html_css_files = ["custom.css"]
html_js_files = []
# Theme specific options
@@ -115,6 +115,7 @@
"doxygenStripFromPath": "..",
# Heavily encouraged optional argument (see docs)
# "rootFileTitle": "API",
+ "fullApiSubSectionTitle": "C++ API",
# Suggested optional arguments
"createTreeView": False,
# TIP: if using the sphinx-bootstrap-theme, you need
diff --git a/docs/latex_index.rst b/docs/latex_index.rst
index 407c8892a..becf44484 100644
--- a/docs/latex_index.rst
+++ b/docs/latex_index.rst
@@ -12,6 +12,3 @@ Wavemap documentation
pages/parameters/index
pages/contributing
pages/faq
-
-..
- _TODO: Include the Library API again once more code is documented in Doxygen
diff --git a/docs/pages/contributing.rst b/docs/pages/contributing.rst
index 1a1891aba..def20104e 100644
--- a/docs/pages/contributing.rst
+++ b/docs/pages/contributing.rst
@@ -3,11 +3,20 @@ Contributing
.. highlight:: bash
.. rstcheck: ignore-roles=gh_file
-Thank you for investing time in contributing to wavemap!
+Thank you for your interest in contributing to wavemap!
+
+Questions
+*********
+If you have any questions, feel free to ask them in the `Q&A section `_ of our GitHub Discussions.
+
+Before posting, please check if your question has already been addressed in our :doc:`installation ` or :doc:`usage ` tutorials. We're happy to answer any remaining theoretical or code-related questions, and help you optimize wavemap for your specific sensor setup.
Bug reports & Feature requests
******************************
-We welcome bug reports, feature requests, and general questions. Please submit them through `GitHub Issues `_ and use the corresponding `bug report `_, `feature request `_, and `question `_ templates.
+We encourage you to submit bug reports and feature requests. You can do so using the relevant GitHub Issue templates:
+
+* `Bug report `_
+* `Feature request `_
In addition to requests for new functionality, do not hesitate to open feature requests for:
diff --git a/docs/pages/faq.rst b/docs/pages/faq.rst
index be4070e4f..9b8da7106 100644
--- a/docs/pages/faq.rst
+++ b/docs/pages/faq.rst
@@ -1,12 +1,9 @@
FAQ
###
+For a comprehensive list of frequently asked questions, please visit the `FAQ section `_ of our GitHub Discussions.
-If you have a question that is not yet answered below, feel free to open a `GitHub Issue `_ or contact us over email.
+Many practical questions may also be covered in our :doc:`Installation ` and :doc:`Usage ` tutorials, which include a variety of code examples.
-How do I query if a point in the map is occupied?
-=================================================
-Please see the :doc:`usage examples ` on :ref:`interpolation ` and :ref:`classification `.
+For a theoretical introduction to the concepts behind wavemap, you can explore our open-access RSS paper, available for download `here `_, and summarized in `this 5-minute presentation `_.
-Does wavemap support (Euclidean) Signed Distance Fields?
-========================================================
-Not yet, but we will add this feature in the near future.
+If your question remains unanswered, don't hesitate to ask in the `Q&A section `_ of our GitHub Discussions. We’d be happy to assist with any remaining theoretical or code-related questions, and help you optimize wavemap for your sensor setup.
diff --git a/docs/pages/installation/cmake.rst b/docs/pages/installation/cpp.rst
similarity index 89%
rename from docs/pages/installation/cmake.rst
rename to docs/pages/installation/cpp.rst
index d7309fb52..0816a51c3 100644
--- a/docs/pages/installation/cmake.rst
+++ b/docs/pages/installation/cpp.rst
@@ -1,5 +1,5 @@
-C++ Library (CMake)
-###################
+C++ (CMake)
+###########
.. highlight:: bash
.. rstcheck: ignore-directives=tab-set-code
.. rstcheck: ignore-roles=gh_file
@@ -8,24 +8,30 @@ Wavemap's C++ library can be used as standard CMake package. In the following se
Note that if you intend to use wavemap with ROS1, you can skip this guide and proceed directly to the :doc:`ROS1 installation page `.
+Prerequisites
+*************
Before you start, make sure you have the necessary tools installed to build C++ projects with CMake. On Ubuntu, we recommend installing::
sudo apt install cmake build-essential git
+.. note::
+
+ If you are working in Docker, these dependencies are only required inside your container. Not on your host machine.
+
FetchContent
************
The fastest way to include wavemap in an existing CMake project is to use FetchContent, by adding the following lines to your project's `CMakeLists.txt`:
.. code-block:: cmake
- set(WAVEMAP_TAG develop/v2.0)
+ set(WAVEMAP_VERSION main) # Select a git branch, tag or commit
cmake_minimum_required(VERSION 3.18)
- message(STATUS "Fetching wavemap ${WAVEMAP_TAG} from GitHub")
+ message(STATUS "Loading wavemap from GitHub (ref ${WAVEMAP_VERSION})")
include(FetchContent)
FetchContent_Declare(wavemap
GIT_REPOSITORY https://github.com/ethz-asl/wavemap.git
- GIT_TAG ${WAVEMAP_TAG}
+ GIT_TAG ${WAVEMAP_VERSION}
GIT_SHALLOW 1
SOURCE_SUBDIR library/cpp)
FetchContent_MakeAvailable(wavemap)
@@ -81,7 +87,7 @@ To build wavemap's C++ Docker image, simply run:
docker build --tag=wavemap_cpp --pull - <<< $(curl -s https://raw.githubusercontent.com/ethz-asl/wavemap/main/tooling/docker/cpp/debian.Dockerfile)
-This will create a local image on your machine containing the latest version of wavemap's C++ library. You can give the local image a different name by modifying the ``--tag=wavemap_cpp`` argument. By default, the image will be built using the latest code on wavemap's ``main`` branch. To specify a specific release or branch, such as `develop/v2.0`, add the ``--build-arg="WAVEMAP_TAG=develop/v2.0"`` argument.
+This will create a local image on your machine containing the latest version of wavemap's C++ library. You can give the local image a different name by modifying the ``--tag=wavemap_cpp`` argument. By default, the image will be built using the latest code on wavemap's ``main`` branch. To specify a specific branch, commit or release, such as `v2.1.0`, add the ``--build-arg="WAVEMAP_VERSION=v2.1.0"`` argument.
Native install
**************
diff --git a/docs/pages/installation/index.rst b/docs/pages/installation/index.rst
index 323ecb042..720305dc0 100644
--- a/docs/pages/installation/index.rst
+++ b/docs/pages/installation/index.rst
@@ -13,6 +13,6 @@ For roboticists working with ROS, we provide packages that tightly integrate wav
:caption: Installation types
:maxdepth: 1
- cmake
+ cpp
python
ros1
diff --git a/docs/pages/installation/python.rst b/docs/pages/installation/python.rst
index e7cbd7362..8ae3ab3d4 100644
--- a/docs/pages/installation/python.rst
+++ b/docs/pages/installation/python.rst
@@ -1,3 +1,106 @@
-Python
-######
-Wavemap's Python API is under active development. We will add it to the documentation soon.
+Python (pip)
+############
+.. highlight:: bash
+.. rstcheck: ignore-directives=tab-set-code
+
+We're still working on making pywavemap available through PyPI. In the meantime, you can build and install it locally with pip, which takes less than two minutes and optimizes the build for your specific computer.
+
+If you plan to use pywavemap without changing its code, a regular installation is easiest. However, if you're modifying wavemap's C++ or Python libraries, we recommend using the editable installation method for fast, incremental rebuilds.
+
+Regular install
+***************
+.. _python-install-build-deps:
+
+First, make sure the necessary dependencies to build C++ and Python packages are available:
+
+.. tab-set-code::
+
+ .. code-block:: Debian/Ubuntu
+ :class: no-header
+
+ sudo apt update
+ sudo apt install git build-essential python3-dev python3-pip
+ sudo apt install python3-venv # If you use virtual environments
+
+ .. code-block:: Alpine
+ :class: no-header
+
+ apk update
+ apk add git build-base python3-dev py3-pip
+ apk add python3-venv # If you use virtual environments
+
+.. _python-install-setup-venv:
+
+*Optional:* We recommend using a virtual environment to isolate your Python dependencies. Create and activate it with the following commands:
+
+.. tab-set-code::
+
+ .. code-block:: Debian/Ubuntu
+ :class: no-header
+
+ sudo apt install python3-venv # If needed
+ python3 -m venv
+ source /bin/activate
+
+ .. code-block:: Alpine
+ :class: no-header
+
+ apk add python3-venv # If needed
+ python3 -m venv
+ source /bin/activate
+
+You can then build and install the latest version or a specific version of pywavemap by running:
+
+.. tab-set-code::
+
+ .. code-block:: Latest
+ :class: no-header
+
+ pip3 install git+https://github.com/ethz-asl/wavemap#subdirectory=library/python
+
+ .. code-block:: Specific
+ :class: no-header
+
+ # Select a specific git branch, tag or commit using @...
+ # For example, to install version v2.1.0, run
+ pip3 install git+https://github.com/ethz-asl/wavemap@v2.1.0#subdirectory=library/python
+
+Editable install
+****************
+If you're interested in modifying wavemap's code, you can save time by enabling incremental rebuilds.
+
+The general steps are similar to those for a regular installation. Ensure your machine is :ref:`ready to build C++ and Python packages ` and, optionally, :ref:`set up a virtual environment `.
+
+Next, clone wavemap's code to your machine:
+
+.. tab-set-code::
+
+ .. code-block:: SSH
+ :class: no-header
+
+ cd ~/
+ git clone git@github.com:ethz-asl/wavemap.git
+
+ .. code-block:: HTTPS
+ :class: no-header
+
+ cd ~/
+ git clone https://github.com/ethz-asl/wavemap.git
+
+Since editable installs are no longer built in an isolated environment, all build dependencies must be available on your system::
+
+ pip3 install nanobind scikit-build-core
+ pip3 install typing_extensions # Only needed for Python < 3.11
+
+You can then install pywavemap with incremental rebuilds using::
+
+ cd ~/wavemap/library/python
+ pip3 install --no-build-isolation -ve .
+
+When you change wavemap's code, the command above must manually be rerun to reinstall the updated package. For a more interactive experience, you can use::
+
+ cd ~/wavemap/library/python
+ rm -rf build # Only needed if you previously built pywavemap differently
+ pip3 install --no-build-isolation -Ceditable.rebuild=true -ve .
+
+In this mode, code changes are automatically rebuilt whenever pywavemap is imported into a Python session. Note that the rebuild message is quite verbose. You can suppress it by passing ``-Ceditable.verbose=false`` as an additional argument to ``pip3 install``.
diff --git a/docs/pages/intro.rst b/docs/pages/intro.rst
index 158ba2bf9..d44030aeb 100644
--- a/docs/pages/intro.rst
+++ b/docs/pages/intro.rst
@@ -5,7 +5,7 @@ Hierarchical, multi-resolution volumetric mapping
*************************************************
Wavemap achieves state-of-the-art memory and computational efficiency by combining Haar wavelet compression and a coarse-to-fine measurement integration scheme. Advanced measurement models allow it to attain exceptionally high recall rates on challenging obstacles like thin objects.
-The framework is very flexible and supports several data structures, measurement integration methods, and sensor models out of the box. The ROS interface can, for example, easily be configured to fuse multiple sensor inputs, such as a LiDAR configured with a range of 20m and several depth cameras up to a resolution of 1cm, into a single map.
+The framework is very flexible and supports several data structures, measurement integration methods, and sensor models out of the box. The ROS interface can, for example, easily be configured to fuse multiple sensor inputs, such as a LiDAR configured with a range of 20m and several depth cameras up to a resolution of 1cm, into a single multi-resolution occupancy grid map.
Paper
*****
@@ -47,7 +47,7 @@ For other citation styles, you can use the `Crosscite's citation formatter `__.
+ The code has significantly improved since the paper was written. Wavemap is now up to 10x faster, thanks to new multi-threaded measurement integrators, and uses up to 50% less RAM, by virtue of new memory efficient data structures inspired by `OpenVDB `__.
.. only:: html
@@ -64,12 +64,9 @@ For other citation styles, you can use the `Crosscite's citation formatter `.
+In this tutorial, we illustrate how you can use wavemap's C++ API in your own projects.
+
+.. tip::
+
+ An example package that combines the setup steps and code examples that follow can be found :gh_file:`here `.
CMake target setup
******************
-Once you included wavemap's C++ library in your CMake project, for example by following our :doc:`installation instructions <../installation/cmake>`, the last remaining step to start using it is to configure your CMake target (e.g. library or executable) to use it.
+Once you included wavemap's C++ library in your CMake project, for example by following our :doc:`installation instructions <../installation/cpp>`, the last remaining step to start using it is to configure your CMake target (e.g. library or executable) to use it.
Wavemap's C++ library contains three main components:
@@ -34,61 +38,97 @@ We **strongly recommend** to also call the ``set_wavemap_target_properties`` fun
Code examples
*************
-
In the following sections, you'll find sample code for common tasks. If you'd like to request examples for additional tasks or contribute new examples, please don't hesitate to `contact us `_.
-Serialization
-=============
+Serializing maps
+================
+In this section, we'll demonstrate how to serialize and deserialize maps using wavemap's lightweight and efficient binary format. This format is consistent across wavemap's C++, Python, and ROS interfaces. For instance, you can create maps on a robot with ROS and later load them into a rendering engine plugin that only depends on wavemap's C++ library.
-Files
------
-Saving maps to files:
+Binary files
+------------
+Maps can be saved to disk with
.. literalinclude:: ../../../examples/cpp/io/save_map_to_file.cc
:language: c++
-Loading maps from files:
+.. _cpp-code-examples-read-map:
+
+and read using
.. literalinclude:: ../../../examples/cpp/io/load_map_from_file.cc
:language: c++
+Byte streams
+------------
+We also provide an alternative, lower-level interface to convert maps to (byte) streams
+
+.. literalinclude:: ../../../examples/cpp/io/save_map_to_stream.cc
+ :language: c++
+
+and read them with
+
+.. literalinclude:: ../../../examples/cpp/io/load_map_from_stream.cc
+ :language: c++
+
+
Queries
=======
+In this section, we illustrate how you can query the map and classify whether a point or region of interest is occupied.
+
+Node indices
+------------
+The map models the environment by filling it with cubes of variable sizes, arranged as the nodes of an octree. Node indices are defined as integer [X, Y, Z, height] coordinates, whose XYZ values correspond to the node's position in the octree's grid at the given *height*, or level in the tree. Height 0 corresponds to the map's maximum resolution, and the grid resolution is halved for each subsequent height level.
Fixed resolution
-----------------
+^^^^^^^^^^^^^^^^
+Querying the value of a single node in the highest resolution grid (*height=0*) can be done as follows.
+
.. literalinclude:: ../../../examples/cpp/queries/fixed_resolution.cc
:language: c++
Multi-res averages
-------------------
+^^^^^^^^^^^^^^^^^^
+It is also possible to query lower resolution nodes, whose values correspond to the average estimated occupancy of the volume they cover.
+
.. literalinclude:: ../../../examples/cpp/queries/multi_resolution.cc
:language: c++
Accelerators
-------------
+^^^^^^^^^^^^
+In case you intend to look up multiple node values, we recommend using wavemap's query accelerator which traverses the octree significantly faster by caching parent nodes.
+
.. literalinclude:: ../../../examples/cpp/queries/accelerated_queries.cc
:language: c++
.. _cpp-code-examples-interpolation:
-Interpolation
--------------
+Real coordinates
+----------------
+Many applications require occupancy estimates at arbitrary 3D points, with real-valued coordinates. Such estimates are computed by interpolating the map.
+
+.. caution::
+
+ If your query points are expressed in a different coordinate frame than the map, do not forget to transform them into the map frame before you continue.
-Nearest neighbor interpolation:
+Nearest neighbor interpolation
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+The simplest form of interpolation simply looks up the value of the map node that is closest to the query point.
.. literalinclude:: ../../../examples/cpp/queries/nearest_neighbor_interpolation.cc
:language: c++
-Trilinear interpolation:
+Trilinear interpolation
+^^^^^^^^^^^^^^^^^^^^^^^
+Another option is to linearly interpolate the map along the x, y, and z axes. This method produces cleaner, more accurate results at the cost of being slightly slower, since it needs to query 8 neighboring map nodes.
.. literalinclude:: ../../../examples/cpp/queries/trilinear_interpolation.cc
:language: c++
.. _cpp-code-examples-classification:
-Classification
---------------
+Occupancy classification
+------------------------
+Once the estimated occupancy at a node or point has been retrieved, it can be classified as follows.
.. literalinclude:: ../../../examples/cpp/queries/classification.cc
:language: c++
diff --git a/docs/pages/tutorials/python.rst b/docs/pages/tutorials/python.rst
index 9d5a7aaa7..dde65c4e8 100644
--- a/docs/pages/tutorials/python.rst
+++ b/docs/pages/tutorials/python.rst
@@ -1,3 +1,142 @@
Python API
##########
-Wavemap's Python API is under active development. We will add it to the documentation soon.
+.. highlight:: python
+.. rstcheck: ignore-roles=gh_file
+
+In this tutorial, we illustrate how you can use wavemap's Python API in your own projects.
+
+Setup
+*****
+Before you start, make sure you :doc:`installed pywavemap <../installation/python>`. In case you used a virtual environment, activate it by running the following command from your terminal:
+
+.. code-block:: bash
+
+ source /bin/activate
+
+In your python files, you can then load the API by simply calling::
+
+ import pywavemap as wave
+
+Code examples
+*************
+
+In the following sections, we provide sample code for common tasks. If you'd like to request examples for additional tasks or contribute new examples, please don't hesitate to `contact us `_.
+
+.. tip::
+
+ All of the examples scripts that follow can be found :gh_file:`here `.
+
+Mapping
+=======
+The only requirements to build wavemap maps are that you have a set of
+
+1. depth measurements,
+2. sensor pose (estimates) for each measurement.
+
+We usually use depth measurements from depth cameras or 3D LiDARs, but any source would work as long as a corresponding :ref:`projection ` and :ref:`measurement ` model is available. To help you get started quickly, we provide example configs for various sensor setups :gh_file:`here `. An overview of all the available settings is provided on the :doc:`parameters page <../parameters/index>`.
+
+Example pipeline
+----------------
+
+.. literalinclude:: ../../../examples/python/mapping/full_pipeline.py
+ :language: python
+
+Serialization
+=============
+Next, we show how you can serialize and deserialize common wavemap objects, for example to save and load them from files.
+
+Maps
+----
+Wavemap uses a lightweight, efficient binary format to serialize its maps. The same format is used across wavemap's C++, Python and ROS interfaces. You could therefore, for example, create maps on a robot with ROS and subsequently analyze them in Python.
+
+Binary files
+^^^^^^^^^^^^
+Maps can be saved to disk using
+
+.. literalinclude:: ../../../examples/python/io/save_map_to_file.py
+ :language: python
+
+.. _python-code-examples-read-map:
+
+and read with
+
+.. literalinclude:: ../../../examples/python/io/load_map_from_file.py
+ :language: python
+
+Configs
+-------
+In the previous mapping pipeline example, the configuration parameters for the map and the measurement integration components were hard-coded. To make your setup more flexible, you can use configuration files. We will demonstrate how to work with YAML files, which is the format we use for wavemap's :gh_file:`example configs `. However, pywavemap is flexible and can support any parameter format that can be read into a Python `dict`.
+
+
+YAML files
+^^^^^^^^^^
+
+.. literalinclude:: ../../../examples/python/io/load_params_from_file.py
+ :language: python
+
+
+Queries
+=======
+In this section, we show how you can query wavemap maps and classify whether a point or region of interest is occupied.
+
+Node indices
+------------
+The map models the environment by filling it with cubes of variable sizes, arranged as the nodes of an octree. Node indices are defined as integer [X, Y, Z, height] coordinates, whose XYZ values correspond to the node's position in the octree's grid at the given *height*, or level in the tree. Height 0 corresponds to the map's maximum resolution, and the grid resolution is halved for each subsequent height level.
+
+Fixed resolution
+^^^^^^^^^^^^^^^^
+Querying the value of a single node in the highest resolution grid (*height=0*) can be done as follows.
+
+.. literalinclude:: ../../../examples/python/queries/fixed_resolution.py
+ :language: python
+
+Multi-res averages
+^^^^^^^^^^^^^^^^^^
+It is also possible to query lower resolution nodes, whose values correspond to the average estimated occupancy of the volume they cover.
+
+.. literalinclude:: ../../../examples/python/queries/multi_resolution.py
+ :language: python
+
+Accelerators
+^^^^^^^^^^^^
+If you need to look up multiple node values, we recommend using our batched query functions. These functions deliver significant speedups by utilizing wavemap's QueryAccelerator.
+
+.. literalinclude:: ../../../examples/python/queries/accelerated_queries.py
+ :language: python
+
+.. note::
+
+ So far batched queries are only implemented for HashedWaveletOctree maps. We will add support for HashedChunkedWaveletOctree maps in the near future.
+
+.. _python-code-examples-interpolation:
+
+Real coordinates
+----------------
+Many applications require occupancy estimates at arbitrary 3D points, with real-valued coordinates. Such estimates are computed by interpolating the map.
+
+.. caution::
+
+ If your query points are expressed in a different coordinate frame than the map, do not forget to transform them into the map frame before you continue.
+
+Nearest neighbor interpolation
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+The simplest form of interpolation simply looks up the value of the map node that is closest to the query point.
+
+.. literalinclude:: ../../../examples/python/queries/nearest_neighbor_interpolation.py
+ :language: python
+
+Trilinear interpolation
+^^^^^^^^^^^^^^^^^^^^^^^
+Another option is to linearly interpolate the map along the x, y, and z axes. This method produces cleaner, more accurate results at the cost of being slightly slower, since it needs to query 8 neighboring map nodes.
+
+.. literalinclude:: ../../../examples/python/queries/trilinear_interpolation.py
+ :language: python
+
+.. _python-code-examples-classification:
+
+Occupancy classification
+------------------------
+Once the estimated occupancy at a node or point has been retrieved, it can be classified as follows.
+
+.. literalinclude:: ../../../examples/python/queries/classification.py
+ :language: python
diff --git a/docs/pages/tutorials/ros1.rst b/docs/pages/tutorials/ros1.rst
index d6ae496fc..31ce6e936 100644
--- a/docs/pages/tutorials/ros1.rst
+++ b/docs/pages/tutorials/ros1.rst
@@ -26,11 +26,27 @@ We usually use depth measurements from depth cameras or 3D LiDARs, but any sourc
To help you get started quickly, we provide example :gh_file:`config ` and ROS :gh_file:`launch ` files for various sensor setups and use cases. An overview of all the available settings is provided on the :doc:`parameters page <../parameters/index>`.
+Publishing maps
+===============
+Wavemap's ROS server offers multiple ways to publish its maps to ROS topics, enabling visualization and usage by other ROS nodes. Please refer to the documentation on :ref:`ROS1 map operations ` for an overview of the available options.
+
+Saving maps
+===========
+The server's map can also be written to disk by calling its ``save_map`` service as follows:
+
+.. code-block:: bash
+
+ rosservice call /wavemap/save_map "file_path: '/path/to/your/map.wvmp'"
+
+Saved maps can subsequently be used :ref:`in C++ ` (with or without ROS) and :ref:`in Python `.
+
Your own code
*************
We now briefly discuss how to set up your own ROS1 package to use wavemap, before proceeding to code examples.
-Note that a working example package that combines this tutorial's setup steps and code examples can be found :gh_file:`here `.
+.. tip::
+
+ An example package that combines the setup steps and code examples that follow can be found :gh_file:`here `.
Build configuration
===================
@@ -86,12 +102,12 @@ Code examples
=============
Since wavemap's ROS1 interface extends its C++ API, all of the :ref:`C++ API's code examples ` can directly be used in ROS.
-The only code required to receive maps over a ROS topic in your own ROS node is:
+Additionally, the following code can be used to receive maps over a ROS topic
.. literalinclude:: ../../../examples/ros1/io/receive_map_over_ros.cc
:language: c++
-To send a map, the following code can be used:
+and maps can be sent over ROS with
.. literalinclude:: ../../../examples/ros1/io/send_map_over_ros.cc
:language: c++
diff --git a/docs/python_api/index.rst b/docs/python_api/index.rst
new file mode 100644
index 000000000..0066b42f1
--- /dev/null
+++ b/docs/python_api/index.rst
@@ -0,0 +1,54 @@
+Python API
+##########
+.. rstcheck: ignore-directives=automodule
+.. rstcheck: ignore-directives=autoclass
+.. rstcheck: ignore-directives=automethod
+
+.. automodule:: pywavemap
+
+.. autoclass:: pywavemap.Map
+ :members:
+.. autoclass:: pywavemap.HashedWaveletOctree
+ :show-inheritance:
+ :members:
+.. autoclass:: pywavemap.HashedChunkedWaveletOctree
+ :show-inheritance:
+ :members:
+.. autoclass:: pywavemap.InterpolationMode
+ :members:
+
+.. autoclass:: pywavemap.OctreeIndex
+ :members:
+
+.. autoclass:: pywavemap.Rotation
+ :members:
+.. autoclass:: pywavemap.Pose
+ :members:
+
+.. autoclass:: pywavemap.Pointcloud
+ :members:
+.. autoclass:: pywavemap.PosedPointcloud
+ :members:
+
+.. autoclass:: pywavemap.Image
+ :members:
+.. autoclass:: pywavemap.PosedImage
+ :members:
+
+.. autoclass:: pywavemap.Pipeline
+ :members:
+
+.. automodule:: pywavemap.convert
+ :members:
+.. automethod:: pywavemap.convert.cell_width_to_height
+.. automethod:: pywavemap.convert.height_to_cell_width
+.. automethod:: pywavemap.convert.point_to_nearest_index
+.. automethod:: pywavemap.convert.point_to_node_index
+
+.. automodule:: pywavemap.logging
+.. automethod:: pywavemap.logging.set_level
+.. automethod:: pywavemap.logging.enable_prefix
+
+.. automodule:: pywavemap.param
+.. autoclass:: pywavemap.param.Value
+ :members:
diff --git a/examples/cpp/CHANGELOG.rst b/examples/cpp/CHANGELOG.rst
index b7a546dee..24e671576 100644
--- a/examples/cpp/CHANGELOG.rst
+++ b/examples/cpp/CHANGELOG.rst
@@ -2,6 +2,13 @@
Changelog for package wavemap_examples_cpp
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+2.1.0 (2024-09-16)
+------------------
+* Extend and improve documentation and examples
+* Minor changes to the C++ API for better consistency with the Python API
+* Extend map interpolation utils
+* Contributors: Victor Reijgwart
+
2.0.1 (2024-08-30)
------------------
diff --git a/examples/cpp/CMakeLists.txt b/examples/cpp/CMakeLists.txt
index 5b19a821b..dbcad1701 100644
--- a/examples/cpp/CMakeLists.txt
+++ b/examples/cpp/CMakeLists.txt
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.10)
-project(wavemap_examples_cpp VERSION 2.0.1 LANGUAGES CXX)
+project(wavemap_examples_cpp VERSION 2.1.0 LANGUAGES CXX)
# Load the wavemap library
# First, try to load it from sources
@@ -15,14 +15,15 @@ else ()
if (wavemap_FOUND)
message(STATUS "Loading wavemap library installed on system")
else () # Otherwise, fetch wavemap's code from GitHub
- set(WAVEMAP_TAG develop/v2.0)
- message(STATUS "Loading wavemap library from GitHub (tag ${WAVEMAP_TAG})")
+ set(WAVEMAP_VERSION main) # Select a git branch, tag or commit
+ message(STATUS
+ "Loading wavemap library from GitHub (ref ${WAVEMAP_VERSION})")
cmake_minimum_required(VERSION 3.18)
include(FetchContent)
FetchContent_Declare(
ext_wavemap PREFIX wavemap
GIT_REPOSITORY https://github.com/ethz-asl/wavemap.git
- GIT_TAG ${WAVEMAP_TAG}
+ GIT_TAG ${WAVEMAP_VERSION}
GIT_SHALLOW 1
SOURCE_SUBDIR library/cpp)
FetchContent_MakeAvailable(ext_wavemap)
diff --git a/examples/cpp/io/CMakeLists.txt b/examples/cpp/io/CMakeLists.txt
index 2537509b7..21446f226 100644
--- a/examples/cpp/io/CMakeLists.txt
+++ b/examples/cpp/io/CMakeLists.txt
@@ -8,3 +8,13 @@ add_executable(load_map_from_file load_map_from_file.cc)
set_wavemap_target_properties(load_map_from_file)
target_link_libraries(load_map_from_file PUBLIC
wavemap::wavemap_core wavemap::wavemap_io)
+
+add_executable(save_map_to_stream save_map_to_stream.cc)
+set_wavemap_target_properties(save_map_to_stream)
+target_link_libraries(save_map_to_stream PUBLIC
+ wavemap::wavemap_core wavemap::wavemap_io)
+
+add_executable(load_map_from_stream load_map_from_stream.cc)
+set_wavemap_target_properties(load_map_from_stream)
+target_link_libraries(load_map_from_stream PUBLIC
+ wavemap::wavemap_core wavemap::wavemap_io)
diff --git a/examples/cpp/io/load_map_from_file.cc b/examples/cpp/io/load_map_from_file.cc
index b26e951cb..5862575ae 100644
--- a/examples/cpp/io/load_map_from_file.cc
+++ b/examples/cpp/io/load_map_from_file.cc
@@ -5,5 +5,6 @@ int main(int, char**) {
wavemap::MapBase::Ptr loaded_map;
// Load the map
- wavemap::io::fileToMap("/some/path/to/your/map.wvmp", loaded_map);
+ const bool success =
+ wavemap::io::fileToMap("/path/to/your/map.wvmp", loaded_map);
}
diff --git a/examples/cpp/io/load_map_from_stream.cc b/examples/cpp/io/load_map_from_stream.cc
new file mode 100644
index 000000000..1f855230b
--- /dev/null
+++ b/examples/cpp/io/load_map_from_stream.cc
@@ -0,0 +1,14 @@
+#include
+
+#include
+
+int main(int, char**) {
+ // Create a smart pointer that will own the loaded map
+ wavemap::MapBase::Ptr loaded_map;
+
+ // Create an input stream for illustration purposes
+ std::istrstream input_stream{""};
+
+ // Load the map
+ const bool success = wavemap::io::streamToMap(input_stream, loaded_map);
+}
diff --git a/examples/cpp/io/save_map_to_file.cc b/examples/cpp/io/save_map_to_file.cc
index a686cec90..fa7c862b5 100644
--- a/examples/cpp/io/save_map_to_file.cc
+++ b/examples/cpp/io/save_map_to_file.cc
@@ -6,5 +6,5 @@ int main(int, char**) {
wavemap::HashedWaveletOctree map(config);
// Save the map
- wavemap::io::mapToFile(map, "/some/path/to/your/map.wvmp");
+ const bool success = wavemap::io::mapToFile(map, "/path/to/your/map.wvmp");
}
diff --git a/examples/cpp/io/save_map_to_stream.cc b/examples/cpp/io/save_map_to_stream.cc
new file mode 100644
index 000000000..251beec78
--- /dev/null
+++ b/examples/cpp/io/save_map_to_stream.cc
@@ -0,0 +1,17 @@
+#include
+
+#include
+
+int main(int, char**) {
+ // Create an empty map for illustration purposes
+ wavemap::HashedWaveletOctreeConfig config;
+ wavemap::HashedWaveletOctree map(config);
+
+ // Create an output stream for illustration purposes
+ std::ostrstream output_stream;
+
+ // Save the map
+ bool success = wavemap::io::mapToStream(map, output_stream);
+ output_stream.flush();
+ success &= output_stream.good();
+}
diff --git a/examples/cpp/queries/CMakeLists.txt b/examples/cpp/queries/CMakeLists.txt
index 1aaea9444..4feb8a7d7 100644
--- a/examples/cpp/queries/CMakeLists.txt
+++ b/examples/cpp/queries/CMakeLists.txt
@@ -23,4 +23,3 @@ target_link_libraries(trilinear_interpolation PUBLIC wavemap::wavemap_core)
add_executable(classification classification.cc)
set_wavemap_target_properties(classification)
target_link_libraries(classification PUBLIC wavemap::wavemap_core)
-target_compile_options(classification PRIVATE -Wno-suggest-attribute=const)
diff --git a/examples/cpp/queries/classification.cc b/examples/cpp/queries/classification.cc
index 6bd37e504..24619ea0b 100644
--- a/examples/cpp/queries/classification.cc
+++ b/examples/cpp/queries/classification.cc
@@ -36,7 +36,7 @@ int main(int, char**) {
// Once a threshold has been chosen, you can either classify in log space
{
- const bool is_occupied = kOccupancyThresholdLogOdds < occupancy_log_odds;
+ const bool is_occupied = kOccupancyThresholdLogOdds <= occupancy_log_odds;
const bool is_free = occupancy_log_odds < kOccupancyThresholdLogOdds;
examples::doSomething(is_occupied);
examples::doSomething(is_free);
@@ -44,7 +44,7 @@ int main(int, char**) {
// Or in probability space
{
- const bool is_occupied = kOccupancyThresholdProb < occupancy_probability;
+ const bool is_occupied = kOccupancyThresholdProb <= occupancy_probability;
const bool is_free = occupancy_probability < kOccupancyThresholdProb;
examples::doSomething(is_occupied);
examples::doSomething(is_free);
diff --git a/examples/cpp/queries/nearest_neighbor_interpolation.cc b/examples/cpp/queries/nearest_neighbor_interpolation.cc
index b1df52609..181316d41 100644
--- a/examples/cpp/queries/nearest_neighbor_interpolation.cc
+++ b/examples/cpp/queries/nearest_neighbor_interpolation.cc
@@ -13,13 +13,8 @@ int main(int, char**) {
// Declare the point to query [in map frame]
const Point3D query_point = Point3D::Zero();
- // Compute the index that's nearest to the query point
- const FloatingPoint min_cell_width_inv = 1.f / map->getMinCellWidth();
- const Index3D nearest_neighbor_index =
- convert::pointToNearestIndex(query_point, min_cell_width_inv);
-
- // Query the map
+ // Query the value of the nearest cell in the map
const FloatingPoint occupancy_log_odds =
- map->getCellValue(nearest_neighbor_index);
+ interpolate::nearestNeighbor(*map, query_point);
examples::doSomething(occupancy_log_odds);
}
diff --git a/examples/python/CHANGELOG.rst b/examples/python/CHANGELOG.rst
new file mode 100644
index 000000000..62250f1a3
--- /dev/null
+++ b/examples/python/CHANGELOG.rst
@@ -0,0 +1,8 @@
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Changelog for package wavemap_examples_python
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+2.1.0 (2024-09-16)
+------------------
+* Initial examples and documentation on how to use wavemap's Python API
+* Contributors: Victor Reijgwart
diff --git a/examples/python/io/load_map_from_file.py b/examples/python/io/load_map_from_file.py
new file mode 100644
index 000000000..fdd903e96
--- /dev/null
+++ b/examples/python/io/load_map_from_file.py
@@ -0,0 +1,7 @@
+import os
+import pywavemap as wave
+
+# Load the map
+user_home = os.path.expanduser('~')
+map_path = os.path.join(user_home, "your_map.wvmp")
+your_map = wave.Map.load(map_path)
diff --git a/examples/python/io/load_params_from_file.py b/examples/python/io/load_params_from_file.py
new file mode 100644
index 000000000..0ae24fd65
--- /dev/null
+++ b/examples/python/io/load_params_from_file.py
@@ -0,0 +1,29 @@
+import os
+import yaml
+import pywavemap as wave
+
+
+def create_map_from_config(config_file_path):
+ """
+ Example function that creates a map based on parameters in a YAML file.
+ """
+ with open(config_file_path) as file:
+ try:
+ config = yaml.safe_load(file)
+ except yaml.YAMLError as exc:
+ print(exc)
+ return None
+
+ if isinstance(config, dict) and "map" in config.keys():
+ return wave.Map.create(config["map"])
+
+ return None
+
+
+# Provide the path to your config
+config_dir = os.path.abspath(
+ os.path.join(__file__, "../../../../interfaces/ros1/wavemap_ros/config"))
+config_file = os.path.join(config_dir, "wavemap_panoptic_mapping_rgbd.yaml")
+
+# Create the map
+your_map = create_map_from_config(config_file)
diff --git a/examples/python/io/save_map_to_file.py b/examples/python/io/save_map_to_file.py
new file mode 100644
index 000000000..30a85cbdf
--- /dev/null
+++ b/examples/python/io/save_map_to_file.py
@@ -0,0 +1,15 @@
+import os
+import pywavemap as wave
+
+# Create an empty map for illustration purposes
+your_map = wave.Map.create({
+ "type": "hashed_chunked_wavelet_octree",
+ "min_cell_width": {
+ "meters": 0.1
+ }
+})
+
+# Save the map
+user_home = os.path.expanduser('~')
+map_path = os.path.join(user_home, "your_map.wvmp")
+your_map.store(map_path)
diff --git a/examples/python/mapping/full_pipeline.py b/examples/python/mapping/full_pipeline.py
new file mode 100644
index 000000000..0df496cc8
--- /dev/null
+++ b/examples/python/mapping/full_pipeline.py
@@ -0,0 +1,122 @@
+import os
+import csv
+from PIL import Image as PilImage
+import numpy as np
+import pywavemap as wave
+
+# Parameters
+home_dir = os.path.expanduser('~')
+measurement_dir = os.path.join(home_dir,
+ "data/panoptic_mapping/flat_dataset/run2")
+output_map_path = os.path.join(home_dir, "your_map.wvmp")
+
+# Create a map
+your_map = wave.Map.create({
+ "type": "hashed_chunked_wavelet_octree",
+ "min_cell_width": {
+ "meters": 0.05
+ }
+})
+
+# Create a measurement integration pipeline
+pipeline = wave.Pipeline(your_map)
+# Add map operations
+pipeline.add_operation({
+ "type": "threshold_map",
+ "once_every": {
+ "seconds": 5.0
+ }
+})
+# Add a measurement integrator
+pipeline.add_integrator(
+ "my_integrator", {
+ "projection_model": {
+ "type": "pinhole_camera_projector",
+ "width": 640,
+ "height": 480,
+ "fx": 320.0,
+ "fy": 320.0,
+ "cx": 320.0,
+ "cy": 240.0
+ },
+ "measurement_model": {
+ "type": "continuous_ray",
+ "range_sigma": {
+ "meters": 0.01
+ },
+ "scaling_free": 0.2,
+ "scaling_occupied": 0.4
+ },
+ "integration_method": {
+ "type": "hashed_chunked_wavelet_integrator",
+ "min_range": {
+ "meters": 0.1
+ },
+ "max_range": {
+ "meters": 5.0
+ }
+ },
+ })
+
+# Index the input data
+ids = []
+times = []
+stamps_file = os.path.join(measurement_dir, 'timestamps.csv')
+if not os.path.isfile(stamps_file):
+ print(f"Could not find timestamp file '{stamps_file}'.")
+with open(stamps_file) as read_obj:
+ csv_reader = csv.reader(read_obj)
+ for row in csv_reader:
+ if row[0] == "ImageID":
+ continue
+ ids.append(str(row[0]))
+ times.append(float(row[1]) / 1e9)
+ids = [x for _, x in sorted(zip(times, ids))]
+
+# Integrate all the measurements
+current_index = 0
+while True:
+ # Check we're not done
+ if current_index >= len(ids):
+ break
+
+ # Load depth image
+ file_path_prefix = os.path.join(measurement_dir, ids[current_index])
+ depth_file = file_path_prefix + "_depth.tiff"
+ if not os.path.isfile(depth_file):
+ print(f"Could not find depth image file '{depth_file}'")
+ current_index += 1
+ raise SystemExit
+ cv_img = PilImage.open(depth_file)
+ image = wave.Image(np.array(cv_img).transpose())
+
+ # Load transform
+ pose_file = file_path_prefix + "_pose.txt"
+ if not os.path.isfile(pose_file):
+ print(f"Could not find pose file '{pose_file}'")
+ current_index += 1
+ raise SystemExit
+ if os.path.isfile(pose_file):
+ with open(pose_file) as f:
+ pose_data = [float(x) for x in f.read().split()]
+ transform = np.eye(4)
+ for row in range(4):
+ for col in range(4):
+ transform[row, col] = pose_data[row * 4 + col]
+ pose = wave.Pose(transform)
+
+ # Integrate the depth image
+ print(f"Integrating measurement {ids[current_index]}")
+ pipeline.run_pipeline(["my_integrator"], wave.PosedImage(pose, image))
+
+ current_index += 1
+
+# Remove map nodes that are no longer needed
+your_map.prune()
+
+# Save the map
+print(f"Saving map of size {your_map.memory_usage} bytes")
+your_map.store(output_map_path)
+
+# Avoids leak warnings on old Python versions with lazy garbage collectors
+del pipeline, your_map
diff --git a/examples/python/panoptic_mapping.py b/examples/python/panoptic_mapping.py
new file mode 100644
index 000000000..92148b591
--- /dev/null
+++ b/examples/python/panoptic_mapping.py
@@ -0,0 +1,123 @@
+# !/usr/bin/env python3
+
+import os
+import csv
+from PIL import Image as PilImage
+import numpy as np
+import yaml
+import pywavemap as wave
+from tqdm import tqdm
+
+
+class DataLoader():
+
+ def __init__(self, params, data_path):
+ self.data_path = data_path
+
+ self.map = wave.Map.create(params["map"])
+
+ self.pipeline = wave.Pipeline(self.map)
+
+ for operation in params["map_operations"]:
+ self.pipeline.add_operation(operation)
+
+ measurement_integrators = params["measurement_integrators"]
+ if len(measurement_integrators) != 1:
+ print("Expected 1 integrator to be specified. "
+ f"Got {len(measurement_integrators)}.")
+ raise SystemExit
+ self.integrator_name, integrator_params = \
+ measurement_integrators.popitem()
+
+ self.pipeline.add_integrator(self.integrator_name, integrator_params)
+
+ # Load list of measurements
+ stamps_file = os.path.join(self.data_path, 'timestamps.csv')
+ self.times = []
+ self.ids = []
+ self.current_index = 0 # Used to iterate through
+ if not os.path.isfile(stamps_file):
+ print(f"No timestamp file '{stamps_file}' found.")
+ with open(stamps_file) as read_obj:
+ csv_reader = csv.reader(read_obj)
+ for row in csv_reader:
+ if row[0] == "ImageID":
+ continue
+ self.ids.append(str(row[0]))
+ self.times.append(float(row[1]) / 1e9)
+
+ self.ids = [x for _, x in sorted(zip(self.times, self.ids))]
+ self.times = sorted(self.times)
+
+ def run(self):
+ for _ in tqdm(range(len(self.times)), desc="Integrating..."):
+ if not self.integrate_frame():
+ break
+
+ def integrate_frame(self):
+ # Check we're not done.
+ if self.current_index >= len(self.times):
+ return False
+
+ # Get all data and publish.
+ file_id = os.path.join(self.data_path, self.ids[self.current_index])
+
+ # Read the image and pose
+ depth_file = file_id + "_depth.tiff"
+ pose_file = file_id + "_pose.txt"
+ files = [depth_file, pose_file]
+ for f in files:
+ if not os.path.isfile(f):
+ print(f"Could not find file '{f}', skipping frame.")
+ self.current_index += 1
+ return False
+
+ # Load depth image
+ cv_img = PilImage.open(depth_file)
+ image = wave.Image(np.array(cv_img).transpose())
+
+ # Load transform
+ if os.path.isfile(pose_file):
+ with open(pose_file) as f:
+ pose_data = [float(x) for x in f.read().split()]
+ transform = np.eye(4)
+ for row in range(4):
+ for col in range(4):
+ transform[row, col] = pose_data[row * 4 + col]
+ pose = wave.Pose(transform)
+
+ self.pipeline.run_pipeline([self.integrator_name],
+ wave.PosedImage(pose, image))
+
+ self.current_index += 1
+
+ return True
+
+ def save_map(self, path):
+ print(f"Saving map of size {self.map.memory_usage}")
+ self.map.store(path)
+
+
+if __name__ == '__main__':
+ config_dir = os.path.abspath(
+ os.path.join(__file__, "../../../interfaces/ros1/wavemap_ros/config"))
+ config_file = os.path.join(config_dir,
+ "wavemap_panoptic_mapping_rgbd.yaml")
+ with open(config_file) as stream:
+ try:
+ config = yaml.safe_load(stream)
+ except yaml.YAMLError as exc:
+ print(exc)
+
+ user_home = os.path.expanduser('~')
+ panoptic_mapping_dir = os.path.join(user_home,
+ "data/panoptic_mapping/flat_dataset")
+ panoptic_mapping_seq = "run2"
+ output_map_path = os.path.join(
+ user_home, f"panoptic_mapping_{panoptic_mapping_seq}.wvmp")
+
+ data_loader = DataLoader(
+ config, os.path.join(panoptic_mapping_dir, panoptic_mapping_seq))
+ data_loader.run()
+ data_loader.save_map(output_map_path)
+ del data_loader # To avoid mem leak warnings on older Python versions
diff --git a/examples/python/queries/_dummy_objects.py b/examples/python/queries/_dummy_objects.py
new file mode 100644
index 000000000..7060f9b30
--- /dev/null
+++ b/examples/python/queries/_dummy_objects.py
@@ -0,0 +1,16 @@
+import pywavemap as wave
+
+
+def example_occupancy_log_odds():
+ """Function that returns a dummy occupancy value to be used in examples."""
+ return 0.0
+
+
+def example_map():
+ """Function that returns a dummy map to be used in examples."""
+ return wave.Map.create({
+ "type": "hashed_wavelet_octree",
+ "min_cell_width": {
+ "meters": 0.1
+ }
+ })
diff --git a/examples/python/queries/accelerated_queries.py b/examples/python/queries/accelerated_queries.py
new file mode 100644
index 000000000..84906cb63
--- /dev/null
+++ b/examples/python/queries/accelerated_queries.py
@@ -0,0 +1,17 @@
+import numpy as np
+
+import _dummy_objects
+
+# Load a map
+your_map = _dummy_objects.example_map()
+
+# Vectorized query for a list of indices at the highest resolution (height 0)
+indices = np.random.randint(-100, 100, size=(64 * 64 * 32, 3))
+values = your_map.get_cell_values(indices)
+print(values)
+
+# Vectorized query for a list of multi-resolution indices (at random heights)
+node_heights = np.random.randint(0, 6, size=(64 * 64 * 32, 1))
+node_indices = np.concatenate((node_heights, indices), axis=1)
+node_values = your_map.get_cell_values(node_indices)
+print(node_values)
diff --git a/examples/python/queries/classification.py b/examples/python/queries/classification.py
new file mode 100644
index 000000000..0b37fc859
--- /dev/null
+++ b/examples/python/queries/classification.py
@@ -0,0 +1,59 @@
+import numpy as np
+import _dummy_objects
+
+# Declare a floating point value representing the occupancy posterior in log
+# odds as queried from the map in one of the previous examples
+occupancy_log_odds = _dummy_objects.example_occupancy_log_odds()
+
+# A point is considered unobserved if its occupancy posterior is equal to the
+# prior. Wavemap assumes that an unobserved point is equally likely to be
+# free or occupied. In other words, the prior occupancy probability is 0.5,
+# which corresponds to a log odds value of 0.0. Accounting for numerical
+# noise, checking whether a point is unobserved can be done as follows:
+kUnobservedThreshold = 1e-3
+is_unobserved = np.abs(occupancy_log_odds) < kUnobservedThreshold
+print(is_unobserved)
+
+
+# In case you would like to convert log odds into probabilities, we provide
+# the following convenience function:
+def log_odds_to_probability(log_odds):
+ odds = np.exp(log_odds)
+ prob = odds / (1.0 + odds)
+ return prob
+
+
+occupancy_probability = log_odds_to_probability(occupancy_log_odds)
+print(occupancy_probability)
+
+
+# To do the opposite
+def probability_to_log_odds(probability):
+ odds = probability / (1.0 - probability)
+ return np.log(odds)
+
+
+occupancy_log_odds = probability_to_log_odds(occupancy_probability)
+print(occupancy_log_odds)
+
+# To classify whether a point is estimated to be occupied or free, you need
+# to choose a discrimination threshold. A reasonable default threshold is 0.5
+# (probability), which corresponds to 0.0 log odds.
+kOccupancyThresholdProb = 0.5
+kOccupancyThresholdLogOdds = 0.0
+
+# NOTE: To tailor the threshold, we recommend running wavemap on a dataset
+# that is representative of your application and analyzing the Receiver
+# Operating Characteristic curve.
+
+# Once a threshold has been chosen, you can either classify in log space
+is_occupied = kOccupancyThresholdLogOdds <= occupancy_log_odds
+is_free = occupancy_log_odds < kOccupancyThresholdLogOdds
+print(is_occupied)
+print(is_free)
+
+# Or in probability space
+is_occupied = kOccupancyThresholdProb <= occupancy_probability
+is_free = occupancy_probability < kOccupancyThresholdProb
+print(is_occupied)
+print(is_free)
diff --git a/examples/python/queries/fixed_resolution.py b/examples/python/queries/fixed_resolution.py
new file mode 100644
index 000000000..e72829680
--- /dev/null
+++ b/examples/python/queries/fixed_resolution.py
@@ -0,0 +1,12 @@
+import numpy as np
+import _dummy_objects
+
+# Load a map
+your_map = _dummy_objects.example_map()
+
+# Declare the index to query
+query_index = np.array([0, 0, 0])
+
+# Query the map's value at the given index
+occupancy_log_odds = your_map.get_cell_value(query_index)
+print(occupancy_log_odds)
diff --git a/examples/python/queries/multi_resolution.py b/examples/python/queries/multi_resolution.py
new file mode 100644
index 000000000..0f30215f8
--- /dev/null
+++ b/examples/python/queries/multi_resolution.py
@@ -0,0 +1,21 @@
+import numpy as np
+import pywavemap as wave
+import _dummy_objects
+
+# Load a map
+your_map = _dummy_objects.example_map()
+
+# Define the center point and the minimum width of your region of interest
+query_point = np.array([0.4, 0.5, 0.6])
+query_min_cell_width = 0.5 # in meters
+
+# Compute the index of the smallest node that covers it completely
+query_height = wave.convert.cell_width_to_height(query_min_cell_width,
+ your_map.min_cell_width)
+query_index = wave.convert.point_to_node_index(query_point,
+ your_map.min_cell_width,
+ query_height)
+
+# Query the node's average occupancy
+occupancy_log_odds = your_map.get_cell_value(query_index)
+print(occupancy_log_odds)
diff --git a/examples/python/queries/nearest_neighbor_interpolation.py b/examples/python/queries/nearest_neighbor_interpolation.py
new file mode 100644
index 000000000..791fb907f
--- /dev/null
+++ b/examples/python/queries/nearest_neighbor_interpolation.py
@@ -0,0 +1,19 @@
+import numpy as np
+from pywavemap import InterpolationMode
+import _dummy_objects
+
+# Load a map
+your_map = _dummy_objects.example_map()
+
+# Declare the point to query [in map frame]
+query_point = np.array([0.4, 0.5, 0.6])
+
+# Query a single point
+occupancy_log_odds = your_map.interpolate(query_point,
+ InterpolationMode.NEAREST)
+print(occupancy_log_odds)
+
+# Vectorized query for a list of points
+points = np.random.random(size=(64 * 64 * 32, 3))
+points_log_odds = your_map.interpolate(points, InterpolationMode.NEAREST)
+print(points_log_odds)
diff --git a/examples/python/queries/trilinear_interpolation.py b/examples/python/queries/trilinear_interpolation.py
new file mode 100644
index 000000000..412822b4a
--- /dev/null
+++ b/examples/python/queries/trilinear_interpolation.py
@@ -0,0 +1,19 @@
+import numpy as np
+from pywavemap import InterpolationMode
+import _dummy_objects
+
+# Load a map
+your_map = _dummy_objects.example_map()
+
+# Declare the point to query [in map frame]
+query_point = np.array([0.4, 0.5, 0.6])
+
+# Query a single point
+occupancy_log_odds = your_map.interpolate(query_point,
+ InterpolationMode.TRILINEAR)
+print(occupancy_log_odds)
+
+# Vectorized query for a list of points
+points = np.random.random(size=(64 * 64 * 32, 3))
+points_log_odds = your_map.interpolate(points, InterpolationMode.TRILINEAR)
+print(points_log_odds)
diff --git a/examples/ros1/CHANGELOG.rst b/examples/ros1/CHANGELOG.rst
index 49a86df74..8a2f9c800 100644
--- a/examples/ros1/CHANGELOG.rst
+++ b/examples/ros1/CHANGELOG.rst
@@ -2,6 +2,9 @@
Changelog for package wavemap_examples_ros1
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+2.1.0 (2024-09-16)
+------------------
+
2.0.1 (2024-08-30)
------------------
diff --git a/examples/ros1/package.xml b/examples/ros1/package.xml
index 8e76a6b48..0f43c187a 100644
--- a/examples/ros1/package.xml
+++ b/examples/ros1/package.xml
@@ -1,7 +1,7 @@
wavemap_examples_ros1
- 2.0.1
+ 2.1.0
Usages examples for wavemap's ROS1 interface.
Victor Reijgwart
diff --git a/interfaces/ros1/wavemap/CHANGELOG.rst b/interfaces/ros1/wavemap/CHANGELOG.rst
index b6bcf515f..3f375105f 100644
--- a/interfaces/ros1/wavemap/CHANGELOG.rst
+++ b/interfaces/ros1/wavemap/CHANGELOG.rst
@@ -2,6 +2,9 @@
Changelog for package wavemap
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+2.1.0 (2024-09-16)
+------------------
+
2.0.1 (2024-08-30)
------------------
diff --git a/interfaces/ros1/wavemap/package.xml b/interfaces/ros1/wavemap/package.xml
index a588047bb..aacf26d7f 100644
--- a/interfaces/ros1/wavemap/package.xml
+++ b/interfaces/ros1/wavemap/package.xml
@@ -1,7 +1,7 @@
wavemap
- 2.0.1
+ 2.1.0
Base library for wavemap.
Victor Reijgwart
diff --git a/interfaces/ros1/wavemap_all/CHANGELOG.rst b/interfaces/ros1/wavemap_all/CHANGELOG.rst
index efe351a9d..fdf230e6a 100644
--- a/interfaces/ros1/wavemap_all/CHANGELOG.rst
+++ b/interfaces/ros1/wavemap_all/CHANGELOG.rst
@@ -2,6 +2,9 @@
Changelog for package wavemap_all
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+2.1.0 (2024-09-16)
+------------------
+
2.0.1 (2024-08-30)
------------------
diff --git a/interfaces/ros1/wavemap_all/package.xml b/interfaces/ros1/wavemap_all/package.xml
index 470a1744c..233c9d405 100644
--- a/interfaces/ros1/wavemap_all/package.xml
+++ b/interfaces/ros1/wavemap_all/package.xml
@@ -1,7 +1,7 @@
wavemap_all
- 2.0.1
+ 2.1.0
Metapackage that builds all wavemap packages.
Victor Reijgwart
diff --git a/interfaces/ros1/wavemap_msgs/CHANGELOG.rst b/interfaces/ros1/wavemap_msgs/CHANGELOG.rst
index e5fa70504..6ed180bb8 100644
--- a/interfaces/ros1/wavemap_msgs/CHANGELOG.rst
+++ b/interfaces/ros1/wavemap_msgs/CHANGELOG.rst
@@ -2,6 +2,9 @@
Changelog for package wavemap_msgs
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+2.1.0 (2024-09-16)
+------------------
+
2.0.1 (2024-08-30)
------------------
diff --git a/interfaces/ros1/wavemap_msgs/package.xml b/interfaces/ros1/wavemap_msgs/package.xml
index 5afc4f088..3bb0b6205 100644
--- a/interfaces/ros1/wavemap_msgs/package.xml
+++ b/interfaces/ros1/wavemap_msgs/package.xml
@@ -1,7 +1,7 @@
wavemap_msgs
- 2.0.1
+ 2.1.0
Message definitions for wavemap's ROS interfaces.
Victor Reijgwart
diff --git a/interfaces/ros1/wavemap_ros/CHANGELOG.rst b/interfaces/ros1/wavemap_ros/CHANGELOG.rst
index 477dd5a1d..b64bd5c76 100644
--- a/interfaces/ros1/wavemap_ros/CHANGELOG.rst
+++ b/interfaces/ros1/wavemap_ros/CHANGELOG.rst
@@ -2,6 +2,9 @@
Changelog for package wavemap_ros
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+2.1.0 (2024-09-16)
+------------------
+
2.0.1 (2024-08-30)
------------------
* Fix outdated Livox callback code
diff --git a/interfaces/ros1/wavemap_ros/CMakeLists.txt b/interfaces/ros1/wavemap_ros/CMakeLists.txt
index 34d6a9568..e90bc7449 100644
--- a/interfaces/ros1/wavemap_ros/CMakeLists.txt
+++ b/interfaces/ros1/wavemap_ros/CMakeLists.txt
@@ -27,9 +27,6 @@ if (livox_ros_driver2_FOUND)
add_compile_definitions(LIVOX_AVAILABLE)
endif ()
-# Enable general wavemap tooling (e.g. to run clang-tidy CI)
-enable_wavemap_general_tooling()
-
# Libraries
add_library(${PROJECT_NAME}
src/inputs/depth_image_topic_input.cc
diff --git a/interfaces/ros1/wavemap_ros/package.xml b/interfaces/ros1/wavemap_ros/package.xml
index 924616f5d..5f2d4143e 100644
--- a/interfaces/ros1/wavemap_ros/package.xml
+++ b/interfaces/ros1/wavemap_ros/package.xml
@@ -1,7 +1,7 @@
wavemap_ros
- 2.0.1
+ 2.1.0
ROS interface for wavemap.
Victor Reijgwart
diff --git a/interfaces/ros1/wavemap_ros/scripts/panoptic_mapping_flat_data_player.py b/interfaces/ros1/wavemap_ros/scripts/panoptic_mapping_flat_data_player.py
index 09013a86c..bb26eb97c 100755
--- a/interfaces/ros1/wavemap_ros/scripts/panoptic_mapping_flat_data_player.py
+++ b/interfaces/ros1/wavemap_ros/scripts/panoptic_mapping_flat_data_player.py
@@ -21,7 +21,7 @@
class FlatDataPlayer():
- # pylint: disable=R0902
+ # pylint: disable=too-many-instance-attributes
def __init__(self):
""" Initialize ros node and read params """
# params
diff --git a/interfaces/ros1/wavemap_ros_conversions/CHANGELOG.rst b/interfaces/ros1/wavemap_ros_conversions/CHANGELOG.rst
index a20337f58..5f6d01762 100644
--- a/interfaces/ros1/wavemap_ros_conversions/CHANGELOG.rst
+++ b/interfaces/ros1/wavemap_ros_conversions/CHANGELOG.rst
@@ -2,6 +2,9 @@
Changelog for package wavemap_ros_conversions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+2.1.0 (2024-09-16)
+------------------
+
2.0.1 (2024-08-30)
------------------
diff --git a/interfaces/ros1/wavemap_ros_conversions/CMakeLists.txt b/interfaces/ros1/wavemap_ros_conversions/CMakeLists.txt
index 6a2e2f381..2c505017e 100644
--- a/interfaces/ros1/wavemap_ros_conversions/CMakeLists.txt
+++ b/interfaces/ros1/wavemap_ros_conversions/CMakeLists.txt
@@ -11,9 +11,6 @@ catkin_package(
LIBRARIES ${PROJECT_NAME}
CATKIN_DEPENDS roscpp eigen_conversions wavemap wavemap_msgs)
-# Enable general wavemap tooling (e.g. to run clang-tidy CI)
-enable_wavemap_general_tooling()
-
# Libraries
add_library(${PROJECT_NAME}
src/config_conversions.cc
diff --git a/interfaces/ros1/wavemap_ros_conversions/package.xml b/interfaces/ros1/wavemap_ros_conversions/package.xml
index 37f9d27c5..4aa81f36f 100644
--- a/interfaces/ros1/wavemap_ros_conversions/package.xml
+++ b/interfaces/ros1/wavemap_ros_conversions/package.xml
@@ -1,7 +1,7 @@
wavemap_ros_conversions
- 2.0.1
+ 2.1.0
Conversions between wavemap and ROS types.
Victor Reijgwart
diff --git a/interfaces/ros1/wavemap_rviz_plugin/CHANGELOG.rst b/interfaces/ros1/wavemap_rviz_plugin/CHANGELOG.rst
index 5f7b8887a..7c75939f9 100644
--- a/interfaces/ros1/wavemap_rviz_plugin/CHANGELOG.rst
+++ b/interfaces/ros1/wavemap_rviz_plugin/CHANGELOG.rst
@@ -2,6 +2,9 @@
Changelog for package wavemap_rviz_plugin
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+2.1.0 (2024-09-16)
+------------------
+
2.0.1 (2024-08-30)
------------------
diff --git a/interfaces/ros1/wavemap_rviz_plugin/package.xml b/interfaces/ros1/wavemap_rviz_plugin/package.xml
index 568f05ff1..81ac381ff 100644
--- a/interfaces/ros1/wavemap_rviz_plugin/package.xml
+++ b/interfaces/ros1/wavemap_rviz_plugin/package.xml
@@ -1,7 +1,7 @@
wavemap_rviz_plugin
- 2.0.1
+ 2.1.0
Plugin to interactively visualize maps published in wavemap's
native format.
diff --git a/library/cpp/CHANGELOG.rst b/library/cpp/CHANGELOG.rst
index 359198578..390759893 100644
--- a/library/cpp/CHANGELOG.rst
+++ b/library/cpp/CHANGELOG.rst
@@ -2,6 +2,38 @@
Changelog for package wavemap
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+2.1.0 (2024-09-16)
+------------------
+* Improvements
+
+ * CMake
+
+ * Add CMake options to support embedding the C++ library in a Python pip pkg
+ * Improve auto-fetching of glog, switch to version with better CMake support
+
+ * C++
+
+ * Extend map interpolation utils
+ * Improve consistency between chunked and regular octree map interfaces
+ * Improve consistency between Pointcloud and Image data structures
+ * Add method to parse TypeSelector types directly from std::strings
+
+ * Documentation
+
+ * Improve C++ library installation instructions
+ * Improve and extend C++ library usage tutorial
+ * Add doxygen annotations for more C++ API classes and methods
+
+* Bug fixes
+
+ * Warn user and ignore range images of wrong dimensions to avoid segfaults
+ * Avoid out of bounds access bug in Haar coefficients print method
+ * Remove usage of deprecated STL types (avoid warnings from new GCC versions)
+ * Explicitly forbid shallow copying of wavemap maps to avoid nanobind errors
+ * Set glog logging level directly, not with gflags lib (might be unavailable)
+
+* Contributors: Victor Reijgwart
+
2.0.1 (2024-08-30)
------------------
diff --git a/library/cpp/CMakeLists.txt b/library/cpp/CMakeLists.txt
index c854e93a1..e61b3f5d8 100644
--- a/library/cpp/CMakeLists.txt
+++ b/library/cpp/CMakeLists.txt
@@ -1,11 +1,13 @@
cmake_minimum_required(VERSION 3.10)
-project(wavemap VERSION 2.0.1 LANGUAGES CXX)
+project(wavemap VERSION 2.1.0 LANGUAGES CXX)
# General options
cmake_policy(SET CMP0077 NEW)
cmake_policy(SET CMP0079 NEW)
option(GENERATE_WAVEMAP_INSTALL_RULES
"Whether to generate install rules for the wavemap library" ON)
+option(BUILD_SHARED_LIBS
+ "Whether to build wavemap as a shared library" ON)
option(USE_SYSTEM_EIGEN "Use system pre-installed Eigen" ON)
option(USE_SYSTEM_GLOG "Use system pre-installed glog" ON)
option(USE_SYSTEM_BOOST "Use system pre-installed Boost" ON)
@@ -13,7 +15,9 @@ option(USE_SYSTEM_BOOST "Use system pre-installed Boost" ON)
# CMake helpers and general wavemap tooling (e.g. to run clang-tidy CI)
include(GNUInstallDirs)
include(cmake/wavemap-extras.cmake)
-enable_wavemap_general_tooling()
+
+# Export compilation database for compatibility with clang-tidy
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Dependencies
include(cmake/find-wavemap-deps.cmake)
diff --git a/library/cpp/cmake/find-wavemap-deps.cmake b/library/cpp/cmake/find-wavemap-deps.cmake
index 6bb85297b..695f0c98e 100644
--- a/library/cpp/cmake/find-wavemap-deps.cmake
+++ b/library/cpp/cmake/find-wavemap-deps.cmake
@@ -1,9 +1,13 @@
+if (CMAKE_VERSION VERSION_GREATER 3.24)
+ cmake_policy(SET CMP0135 OLD)
+endif ()
+
# Eigen
if (USE_SYSTEM_EIGEN)
find_package(Eigen3 QUIET NO_MODULE)
endif ()
if (USE_SYSTEM_EIGEN AND TARGET Eigen3::Eigen)
- message(STATUS "Using system Eigen")
+ message(STATUS "Using system Eigen (version ${Eigen3_VERSION})")
else ()
message(STATUS "Fetching external Eigen")
set(USE_SYSTEM_EIGEN OFF)
@@ -16,12 +20,12 @@ if (USE_SYSTEM_GLOG)
if (NOT glog_FOUND)
find_package(PkgConfig QUIET)
if (PkgConfig_FOUND)
- pkg_check_modules(glog REQUIRED libglog)
+ pkg_check_modules(glog QUIET libglog)
endif ()
endif ()
endif ()
if (USE_SYSTEM_GLOG AND glog_FOUND)
- message(STATUS "Using system Glog")
+ message(STATUS "Using system Glog (version ${glog_VERSION})")
else ()
message(STATUS "Fetching external Glog")
set(USE_SYSTEM_GLOG OFF)
@@ -42,7 +46,7 @@ if (USE_SYSTEM_BOOST)
endif ()
endif ()
if (USE_SYSTEM_BOOST AND TARGET Boost::preprocessor)
- message(STATUS "Using system Boost")
+ message(STATUS "Using system Boost (version ${Boost_VERSION})")
else ()
message(STATUS "Fetching external Boost")
set(USE_SYSTEM_BOOST OFF)
diff --git a/library/cpp/cmake/wavemap-extras.cmake b/library/cpp/cmake/wavemap-extras.cmake
index ecd761aa7..33aa2e3cb 100644
--- a/library/cpp/cmake/wavemap-extras.cmake
+++ b/library/cpp/cmake/wavemap-extras.cmake
@@ -8,14 +8,6 @@ option(ENABLE_COVERAGE_TESTING
"Compile with necessary flags for coverage testing" OFF)
option(USE_CLANG_TIDY "Generate necessary files to run clang-tidy" OFF)
-# Enable general wavemap tooling for the calling CMake project
-function(enable_wavemap_general_tooling)
- # Export compilation database for compatibility with clang-tidy
- if (USE_CLANG_TIDY)
- set(CMAKE_EXPORT_COMPILE_COMMANDS ON PARENT_SCOPE)
- endif ()
-endfunction()
-
# Adds the include paths of the wavemap library to the given target.
function(add_wavemap_include_directories target)
# Configure the include dirs
@@ -40,8 +32,8 @@ function(set_wavemap_target_properties target)
set_target_properties(${target} PROPERTIES POSITION_INDEPENDENT_CODE ON)
target_compile_options(${target} PUBLIC -march=native)
target_compile_options(${target} PRIVATE
- -Wall -Wextra -Wpedantic -Wsuggest-attribute=const
- -Wno-deprecated-copy -Wno-class-memaccess)
+ -Wall -Wextra -Wpedantic
+ -Wno-unused-result -Wno-deprecated-copy -Wno-class-memaccess)
# General C++ defines
target_compile_definitions(${target} PUBLIC EIGEN_INITIALIZE_MATRICES_BY_NAN)
diff --git a/library/cpp/include/wavemap/core/config/impl/type_selector_inl.h b/library/cpp/include/wavemap/core/config/impl/type_selector_inl.h
index e2871dce7..ffd2302f4 100644
--- a/library/cpp/include/wavemap/core/config/impl/type_selector_inl.h
+++ b/library/cpp/include/wavemap/core/config/impl/type_selector_inl.h
@@ -88,20 +88,36 @@ TypeSelector::toStr(TypeId type_id) {
}
}
-template
-typename TypeSelector::TypeId
-TypeSelector::toTypeId(const std::string& name) {
- for (size_t type_idx = 0; type_idx < DerivedNamedTypeSetT::names.size();
+template
+typename TypeSelector::TypeId
+TypeSelector::toTypeId(const std::string& name) {
+ for (size_t type_idx = 0; type_idx < DerivedTypeSelectorT::names.size();
++type_idx) {
- if (name == DerivedNamedTypeSetT::names[type_idx]) {
+ if (name == DerivedTypeSelectorT::names[type_idx]) {
return static_cast(type_idx);
}
}
return kInvalidTypeId;
}
-template
-std::optional TypeSelector::from(
+template
+std::optional TypeSelector::from(
+ const std::string& type_name) {
+ DerivedTypeSelectorT type_id(type_name);
+ if (!type_id.isValid()) {
+ LOG(WARNING)
+ << "Value of type name param \"" << param::kTypeSelectorKey << "\": \""
+ << type_name
+ << "\" does not match a known type name. Supported type names are ["
+ << print::sequence(DerivedTypeSelectorT::names) << "].";
+ return std::nullopt;
+ }
+
+ return type_id;
+}
+
+template
+std::optional TypeSelector::from(
const param::Value& params) {
// Read the type name from params
const auto type_name = param::getTypeStr(params);
@@ -112,21 +128,11 @@ std::optional TypeSelector::from(
}
// Match the type name to a type id
- DerivedNamedTypeSetT type_id(type_name.value());
- if (!type_id.isValid()) {
- LOG(WARNING)
- << "Value of type name param \"" << param::kTypeSelectorKey << "\": \""
- << type_name.value()
- << "\" does not match a known type name. Supported type names are ["
- << print::sequence(DerivedNamedTypeSetT::names) << "].";
- return std::nullopt;
- }
-
- return type_id;
+ return from(type_name.value());
}
-template
-std::optional TypeSelector::from(
+template
+std::optional TypeSelector::from(
const param::Value& params, const std::string& subconfig_name) {
if (const auto subconfig_params = params.getChild(subconfig_name);
subconfig_params) {
diff --git a/library/cpp/include/wavemap/core/config/param.h b/library/cpp/include/wavemap/core/config/param.h
index c46ea5620..8458b8bf8 100644
--- a/library/cpp/include/wavemap/core/config/param.h
+++ b/library/cpp/include/wavemap/core/config/param.h
@@ -4,6 +4,7 @@
#include