diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..369b6de --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,28 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-dockerfile +{ + "name": "Existing Dockerfile", + "build": { + // Sets the run context to one level up instead of the .devcontainer folder. + "context": "..", + // Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename. + "dockerfile": "../Dockerfile" + }, + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Uncomment the next line to run commands after the container is created. + // "postCreateCommand": "cat /etc/os-release", + + "postStartCommand": ".devcontainer/post_start_command.sh" + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "devcontainer" +} diff --git a/.devcontainer/post-start-command.sh b/.devcontainer/post-start-command.sh new file mode 100755 index 0000000..4ce93a2 --- /dev/null +++ b/.devcontainer/post-start-command.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +export DIR=$(dirname $(realpath ${BASH_SOURCE[0]})) + +python3 -m venv venv \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..f33a02c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for more information: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +# https://containers.dev/guide/dependabot + +version: 2 +updates: + - package-ecosystem: "devcontainers" + directory: "/" + schedule: + interval: weekly diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml new file mode 100644 index 0000000..71acc2a --- /dev/null +++ b/.github/workflows/wheels.yml @@ -0,0 +1,91 @@ +name: Wheels + +on: + workflow_dispatch: + pull_request: + push: + release: + types: + - published + +jobs: + build_sdist: + name: Build SDist + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./ + + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - name: Build SDist + run: pipx run build --sdist + + - name: Check metadata + run: pipx run twine check dist/* + + - uses: actions/upload-artifact@v3 + with: + path: dist/*.tar.gz + + + build_wheels: + name: Wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-22.04] + defaults: + run: + working-directory: ./ + + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - name: Dependencies + run: | + sudo apt update + sudo apt install libeigen3-dev libgtest-dev \ + libabsl-dev libceres-dev \ + libopencv-dev libyaml-cpp-dev libgmock-dev + ./scripts/install_ceres.sh + + - uses: pypa/cibuildwheel@v2.16 + env: + CIBW_ARCHS_MACOS: auto universal2 + + - name: Verify clean directory + run: git diff --exit-code + shell: bash + + - name: Upload wheels + uses: actions/upload-artifact@v3 + with: + path: wheelhouse/*.whl + + + upload_all: + name: Upload if release + needs: [build_wheels, build_sdist] + runs-on: ubuntu-latest + if: github.event_name == 'release' && github.event.action == 'published' + + steps: + - uses: actions/setup-python@v5 + with: + python-version: "3.x" + + - uses: actions/download-artifact@v3 + with: + name: artifact + path: dist + + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.pypi_password }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index e45ca05..78d96b7 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,13 @@ __pycache__ Testing .ipynb_checkpoints doc -calico_python.egg-info \ No newline at end of file +*.egg-info +CMakeFiles +*.a +*.whl +Makefile +venv +cmake_install.cmake +CMakeCache.txt +CTestTestfile.cmake +DartConfiguration.tcl diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..8bbf9fa --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "cmake.sourceDirectory": "/wsl.localhost/Ubuntu/home/jpollak/src/jbcpollak/Calico" +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 1632c21..b9faec0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,10 @@ cmake_minimum_required(VERSION 3.5) project (Calico) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/build/lib) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/build/lib) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/build/bin) + # Turn off testing by default. include(CTest) @@ -13,21 +17,19 @@ find_package(Eigen3 3.3 REQUIRED NO_MODULE) find_package(GTest REQUIRED) find_package(absl REQUIRED) find_package(Ceres REQUIRED) -find_package(pybind11 REQUIRED) -find_package(Python REQUIRED) find_package(OpenCV REQUIRED) find_package(yaml-cpp REQUIRED) # Add top level include directory. include_directories( - ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_LIST_DIR} ${OpenCV_INCLUDE_DIRS} ) # Build trajectory library and tests. add_library( trajectory - ${CMAKE_CURRENT_SOURCE_DIR}/calico/trajectory.cpp + ${CMAKE_CURRENT_LIST_DIR}/calico/trajectory.cpp ) target_link_libraries( trajectory @@ -41,7 +43,7 @@ target_link_libraries( # World model library. add_library( world_model - ${CMAKE_CURRENT_SOURCE_DIR}/calico/world_model.cpp + ${CMAKE_CURRENT_LIST_DIR}/calico/world_model.cpp ) target_link_libraries( world_model @@ -55,7 +57,7 @@ target_link_libraries( # Gyroscope library. add_library( gyroscope_models - ${CMAKE_CURRENT_SOURCE_DIR}/calico/sensors/gyroscope_models.cpp + ${CMAKE_CURRENT_LIST_DIR}/calico/sensors/gyroscope_models.cpp ) target_link_libraries( gyroscope_models @@ -65,7 +67,7 @@ target_link_libraries( add_library( gyroscope_cost_functor - ${CMAKE_CURRENT_SOURCE_DIR}/calico/sensors/gyroscope_cost_functor.cpp + ${CMAKE_CURRENT_LIST_DIR}/calico/sensors/gyroscope_cost_functor.cpp ) target_link_libraries( gyroscope_cost_functor @@ -75,7 +77,7 @@ target_link_libraries( add_library( gyroscope - ${CMAKE_CURRENT_SOURCE_DIR}/calico/sensors/gyroscope.cpp + ${CMAKE_CURRENT_LIST_DIR}/calico/sensors/gyroscope.cpp ) target_link_libraries( gyroscope @@ -84,11 +86,11 @@ target_link_libraries( trajectory world_model ) - + # Accelerometer library and tests. add_library( accelerometer_models - ${CMAKE_CURRENT_SOURCE_DIR}/calico/sensors/accelerometer_models.cpp + ${CMAKE_CURRENT_LIST_DIR}/calico/sensors/accelerometer_models.cpp ) target_link_libraries( accelerometer_models @@ -98,7 +100,7 @@ target_link_libraries( add_library( accelerometer_cost_functor - ${CMAKE_CURRENT_SOURCE_DIR}/calico/sensors/accelerometer_cost_functor.cpp + ${CMAKE_CURRENT_LIST_DIR}/calico/sensors/accelerometer_cost_functor.cpp ) target_link_libraries( accelerometer_cost_functor @@ -108,7 +110,7 @@ target_link_libraries( add_library( accelerometer - ${CMAKE_CURRENT_SOURCE_DIR}/calico/sensors/accelerometer.cpp + ${CMAKE_CURRENT_LIST_DIR}/calico/sensors/accelerometer.cpp ) target_link_libraries( accelerometer @@ -121,7 +123,7 @@ target_link_libraries( # Camera library. add_library( camera_models - ${CMAKE_CURRENT_SOURCE_DIR}/calico/sensors/camera_models.cpp + ${CMAKE_CURRENT_LIST_DIR}/calico/sensors/camera_models.cpp ) target_link_libraries( camera_models @@ -131,7 +133,7 @@ target_link_libraries( add_library( camera_cost_functor - ${CMAKE_CURRENT_SOURCE_DIR}/calico/sensors/camera_cost_functor.cpp + ${CMAKE_CURRENT_LIST_DIR}/calico/sensors/camera_cost_functor.cpp ) target_link_libraries( camera_cost_functor @@ -141,7 +143,7 @@ target_link_libraries( add_library( camera - ${CMAKE_CURRENT_SOURCE_DIR}/calico/sensors/camera.cpp + ${CMAKE_CURRENT_LIST_DIR}/calico/sensors/camera.cpp ) target_link_libraries( camera @@ -154,7 +156,7 @@ target_link_libraries( # Batch optimizer. add_library( batch_optimizer - ${CMAKE_CURRENT_SOURCE_DIR}/calico/batch_optimizer.cpp + ${CMAKE_CURRENT_LIST_DIR}/calico/batch_optimizer.cpp ) target_link_libraries( batch_optimizer @@ -166,25 +168,25 @@ target_link_libraries( # AprilTags c++ library. include_directories( - ${CMAKE_CURRENT_SOURCE_DIR}/calico/third_party/apriltags/ - ${CMAKE_CURRENT_SOURCE_DIR}/calico/third_party/apriltags/apriltags/ + ${CMAKE_CURRENT_LIST_DIR}/calico/third_party/apriltags/ + ${CMAKE_CURRENT_LIST_DIR}/calico/third_party/apriltags/apriltags/ ) add_library( apriltags - ${CMAKE_CURRENT_SOURCE_DIR}/calico/third_party/apriltags/src/Edge.cc - ${CMAKE_CURRENT_SOURCE_DIR}/calico/third_party/apriltags/src/FloatImage.cc - ${CMAKE_CURRENT_SOURCE_DIR}/calico/third_party/apriltags/src/Gaussian.cc - ${CMAKE_CURRENT_SOURCE_DIR}/calico/third_party/apriltags/src/GLine2D.cc - ${CMAKE_CURRENT_SOURCE_DIR}/calico/third_party/apriltags/src/GLineSegment2D.cc - ${CMAKE_CURRENT_SOURCE_DIR}/calico/third_party/apriltags/src/GrayModel.cc - ${CMAKE_CURRENT_SOURCE_DIR}/calico/third_party/apriltags/src/Homography33.cc - ${CMAKE_CURRENT_SOURCE_DIR}/calico/third_party/apriltags/src/MathUtil.cc - ${CMAKE_CURRENT_SOURCE_DIR}/calico/third_party/apriltags/src/Quad.cc - ${CMAKE_CURRENT_SOURCE_DIR}/calico/third_party/apriltags/src/Segment.cc - ${CMAKE_CURRENT_SOURCE_DIR}/calico/third_party/apriltags/src/TagFamily.cc - ${CMAKE_CURRENT_SOURCE_DIR}/calico/third_party/apriltags/src/TagDetection.cc - ${CMAKE_CURRENT_SOURCE_DIR}/calico/third_party/apriltags/src/TagDetector.cc - ${CMAKE_CURRENT_SOURCE_DIR}/calico/third_party/apriltags/src/UnionFindSimple.cc + ${CMAKE_CURRENT_LIST_DIR}/calico/third_party/apriltags/src/Edge.cc + ${CMAKE_CURRENT_LIST_DIR}/calico/third_party/apriltags/src/FloatImage.cc + ${CMAKE_CURRENT_LIST_DIR}/calico/third_party/apriltags/src/Gaussian.cc + ${CMAKE_CURRENT_LIST_DIR}/calico/third_party/apriltags/src/GLine2D.cc + ${CMAKE_CURRENT_LIST_DIR}/calico/third_party/apriltags/src/GLineSegment2D.cc + ${CMAKE_CURRENT_LIST_DIR}/calico/third_party/apriltags/src/GrayModel.cc + ${CMAKE_CURRENT_LIST_DIR}/calico/third_party/apriltags/src/Homography33.cc + ${CMAKE_CURRENT_LIST_DIR}/calico/third_party/apriltags/src/MathUtil.cc + ${CMAKE_CURRENT_LIST_DIR}/calico/third_party/apriltags/src/Quad.cc + ${CMAKE_CURRENT_LIST_DIR}/calico/third_party/apriltags/src/Segment.cc + ${CMAKE_CURRENT_LIST_DIR}/calico/third_party/apriltags/src/TagFamily.cc + ${CMAKE_CURRENT_LIST_DIR}/calico/third_party/apriltags/src/TagDetection.cc + ${CMAKE_CURRENT_LIST_DIR}/calico/third_party/apriltags/src/TagDetector.cc + ${CMAKE_CURRENT_LIST_DIR}/calico/third_party/apriltags/src/UnionFindSimple.cc ) target_link_libraries( apriltags @@ -194,7 +196,7 @@ target_link_libraries( # AprilGrid Detector add_library( aprilgrid_detector - ${CMAKE_CURRENT_SOURCE_DIR}/calico/chart_detectors/aprilgrid_detector.cpp + ${CMAKE_CURRENT_LIST_DIR}/calico/chart_detectors/aprilgrid_detector.cpp ) target_link_libraries( aprilgrid_detector @@ -204,33 +206,6 @@ target_link_libraries( ${YAML_CPP_LIBRARIES} ) -# Python bindings. -pybind11_add_module( - _calico - ${CMAKE_CURRENT_SOURCE_DIR}/calico/calico.cpp -) -target_link_libraries( - _calico PUBLIC - accelerometer - aprilgrid_detector - batch_optimizer - camera - gyroscope - trajectory - world_model - Eigen3::Eigen -) -set_target_properties( - _calico PROPERTIES - LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/calico -) -file( - COPY - ${CMAKE_CURRENT_SOURCE_DIR}/calico/__init__.py - ${CMAKE_CURRENT_SOURCE_DIR}/calico/utils.py - DESTINATION ${PROJECT_BINARY_DIR}/calico -) - # Install cpp library. install( TARGETS @@ -258,56 +233,45 @@ install( DESTINATION lib/cmake/calico ) set(top_level_headers - ${CMAKE_CURRENT_SOURCE_DIR}/calico/profiler.h - ${CMAKE_CURRENT_SOURCE_DIR}/calico/trajectory.h - ${CMAKE_CURRENT_SOURCE_DIR}/calico/bspline.h - ${CMAKE_CURRENT_SOURCE_DIR}/calico/test_utils.h - ${CMAKE_CURRENT_SOURCE_DIR}/calico/optimization_utils.h - ${CMAKE_CURRENT_SOURCE_DIR}/calico/batch_optimizer.h - ${CMAKE_CURRENT_SOURCE_DIR}/calico/typedefs.h - ${CMAKE_CURRENT_SOURCE_DIR}/calico/matchers.h - ${CMAKE_CURRENT_SOURCE_DIR}/calico/status_builder.h - ${CMAKE_CURRENT_SOURCE_DIR}/calico/geometry.h - ${CMAKE_CURRENT_SOURCE_DIR}/calico/world_model.h - ${CMAKE_CURRENT_SOURCE_DIR}/calico/statusor_macros.h + ${CMAKE_CURRENT_LIST_DIR}/calico/profiler.h + ${CMAKE_CURRENT_LIST_DIR}/calico/trajectory.h + ${CMAKE_CURRENT_LIST_DIR}/calico/bspline.h + ${CMAKE_CURRENT_LIST_DIR}/calico/test_utils.h + ${CMAKE_CURRENT_LIST_DIR}/calico/optimization_utils.h + ${CMAKE_CURRENT_LIST_DIR}/calico/batch_optimizer.h + ${CMAKE_CURRENT_LIST_DIR}/calico/typedefs.h + ${CMAKE_CURRENT_LIST_DIR}/calico/matchers.h + ${CMAKE_CURRENT_LIST_DIR}/calico/status_builder.h + ${CMAKE_CURRENT_LIST_DIR}/calico/geometry.h + ${CMAKE_CURRENT_LIST_DIR}/calico/world_model.h + ${CMAKE_CURRENT_LIST_DIR}/calico/statusor_macros.h ) install( FILES ${top_level_headers} DESTINATION include/calico ) set(sensor_headers - ${CMAKE_CURRENT_SOURCE_DIR}/calico/sensors/gyroscope_models.h - ${CMAKE_CURRENT_SOURCE_DIR}/calico/sensors/camera.h - ${CMAKE_CURRENT_SOURCE_DIR}/calico/sensors/accelerometer_cost_functor.h - ${CMAKE_CURRENT_SOURCE_DIR}/calico/sensors/sensor_base.h - ${CMAKE_CURRENT_SOURCE_DIR}/calico/sensors/gyroscope_cost_functor.h - ${CMAKE_CURRENT_SOURCE_DIR}/calico/sensors/camera_cost_functor.h - ${CMAKE_CURRENT_SOURCE_DIR}/calico/sensors/camera_models.h - ${CMAKE_CURRENT_SOURCE_DIR}/calico/sensors/accelerometer_models.h - ${CMAKE_CURRENT_SOURCE_DIR}/calico/sensors/gyroscope.h - ${CMAKE_CURRENT_SOURCE_DIR}/calico/sensors/accelerometer.h + ${CMAKE_CURRENT_LIST_DIR}/calico/sensors/gyroscope_models.h + ${CMAKE_CURRENT_LIST_DIR}/calico/sensors/camera.h + ${CMAKE_CURRENT_LIST_DIR}/calico/sensors/accelerometer_cost_functor.h + ${CMAKE_CURRENT_LIST_DIR}/calico/sensors/sensor_base.h + ${CMAKE_CURRENT_LIST_DIR}/calico/sensors/gyroscope_cost_functor.h + ${CMAKE_CURRENT_LIST_DIR}/calico/sensors/camera_cost_functor.h + ${CMAKE_CURRENT_LIST_DIR}/calico/sensors/camera_models.h + ${CMAKE_CURRENT_LIST_DIR}/calico/sensors/accelerometer_models.h + ${CMAKE_CURRENT_LIST_DIR}/calico/sensors/gyroscope.h + ${CMAKE_CURRENT_LIST_DIR}/calico/sensors/accelerometer.h ) install( FILES ${sensor_headers} DESTINATION include/calico/sensors/ ) -# Install python library. -install( - TARGETS _calico - DESTINATION ${Python_SITELIB}/calico -) -install( - FILES - ${CMAKE_CURRENT_SOURCE_DIR}/calico/utils.py - ${CMAKE_CURRENT_SOURCE_DIR}/calico/__init__.py - DESTINATION ${Python_SITELIB}/calico -) if (BUILD_TESTING) # Typedefs test. add_executable( typedefs_test - ${CMAKE_CURRENT_SOURCE_DIR}/calico/test/typedefs_test.cpp + ${CMAKE_CURRENT_LIST_DIR}/calico/test/typedefs_test.cpp ) target_link_libraries( typedefs_test @@ -319,7 +283,7 @@ if (BUILD_TESTING) # Geometry test. add_executable( geometry_test - ${CMAKE_CURRENT_SOURCE_DIR}/calico/test/geometry_test.cpp + ${CMAKE_CURRENT_LIST_DIR}/calico/test/geometry_test.cpp ) target_link_libraries( geometry_test @@ -332,7 +296,7 @@ if (BUILD_TESTING) # Spline test. add_executable( bspline_test - ${CMAKE_CURRENT_SOURCE_DIR}/calico/test/bspline_test.cpp + ${CMAKE_CURRENT_LIST_DIR}/calico/test/bspline_test.cpp ) target_link_libraries( bspline_test @@ -346,7 +310,7 @@ if (BUILD_TESTING) # Trajectory test. add_executable( trajectory_test - ${CMAKE_CURRENT_SOURCE_DIR}/calico/test/trajectory_test.cpp + ${CMAKE_CURRENT_LIST_DIR}/calico/test/trajectory_test.cpp ) target_link_libraries( trajectory_test @@ -358,7 +322,7 @@ if (BUILD_TESTING) # World model test. add_executable( world_model_test - ${CMAKE_CURRENT_SOURCE_DIR}/calico/test/world_model_test.cpp + ${CMAKE_CURRENT_LIST_DIR}/calico/test/world_model_test.cpp ) target_link_libraries( world_model_test @@ -370,7 +334,7 @@ if (BUILD_TESTING) # Gyroscope models test. add_executable( gyroscope_models_test - ${CMAKE_CURRENT_SOURCE_DIR}/calico/test/gyroscope_models_test.cpp + ${CMAKE_CURRENT_LIST_DIR}/calico/test/gyroscope_models_test.cpp ) target_link_libraries( gyroscope_models_test @@ -382,7 +346,7 @@ if (BUILD_TESTING) # Gyroscope cost functor test. add_executable( gyroscope_cost_functor_test - ${CMAKE_CURRENT_SOURCE_DIR}/calico/test/gyroscope_cost_functor_test.cpp + ${CMAKE_CURRENT_LIST_DIR}/calico/test/gyroscope_cost_functor_test.cpp ) target_link_libraries( gyroscope_cost_functor_test @@ -394,7 +358,7 @@ if (BUILD_TESTING) # Gyroscope test. add_executable( gyroscope_test - ${CMAKE_CURRENT_SOURCE_DIR}/calico/test/gyroscope_test.cpp + ${CMAKE_CURRENT_LIST_DIR}/calico/test/gyroscope_test.cpp ) target_link_libraries( gyroscope_test @@ -406,7 +370,7 @@ if (BUILD_TESTING) # Accelerometer models test. add_executable( accelerometer_models_test - ${CMAKE_CURRENT_SOURCE_DIR}/calico/test/accelerometer_models_test.cpp + ${CMAKE_CURRENT_LIST_DIR}/calico/test/accelerometer_models_test.cpp ) target_link_libraries( accelerometer_models_test @@ -418,7 +382,7 @@ if (BUILD_TESTING) # Accelerometer cost functor test. add_executable( accelerometer_cost_functor_test - ${CMAKE_CURRENT_SOURCE_DIR}/calico/test/accelerometer_cost_functor_test.cpp + ${CMAKE_CURRENT_LIST_DIR}/calico/test/accelerometer_cost_functor_test.cpp ) target_link_libraries( accelerometer_cost_functor_test @@ -430,7 +394,7 @@ if (BUILD_TESTING) # Accelerometer test. add_executable( accelerometer_test - ${CMAKE_CURRENT_SOURCE_DIR}/calico/test/accelerometer_test.cpp + ${CMAKE_CURRENT_LIST_DIR}/calico/test/accelerometer_test.cpp ) target_link_libraries( accelerometer_test @@ -442,7 +406,7 @@ if (BUILD_TESTING) # Camera models test. add_executable( camera_models_test - ${CMAKE_CURRENT_SOURCE_DIR}/calico/test/camera_models_test.cpp + ${CMAKE_CURRENT_LIST_DIR}/calico/test/camera_models_test.cpp ) target_link_libraries( camera_models_test @@ -454,7 +418,7 @@ if (BUILD_TESTING) # Camera cost functor test. add_executable( camera_cost_functor_test - ${CMAKE_CURRENT_SOURCE_DIR}/calico/test/camera_cost_functor_test.cpp + ${CMAKE_CURRENT_LIST_DIR}/calico/test/camera_cost_functor_test.cpp ) target_link_libraries( camera_cost_functor_test @@ -466,7 +430,7 @@ if (BUILD_TESTING) # Camera test. add_executable( camera_test - ${CMAKE_CURRENT_SOURCE_DIR}/calico/test/camera_test.cpp + ${CMAKE_CURRENT_LIST_DIR}/calico/test/camera_test.cpp ) target_link_libraries( camera_test @@ -478,7 +442,7 @@ if (BUILD_TESTING) # Batch optimizer test. add_executable( batch_optimizer_test - ${CMAKE_CURRENT_SOURCE_DIR}/calico/test/batch_optimizer_test.cpp + ${CMAKE_CURRENT_LIST_DIR}/calico/test/batch_optimizer_test.cpp ) target_link_libraries( batch_optimizer_test @@ -492,14 +456,14 @@ if (BUILD_TESTING) # Python bindings test. add_test( NAME python_bindings_test - COMMAND python3 -m pytest ${CMAKE_CURRENT_SOURCE_DIR}/calico/test/python_bindings_test.py - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/calico/test + COMMAND python3 -m pytest ${CMAKE_CURRENT_LIST_DIR}/calico/test/python_bindings_test.py + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/calico/test ) # Python utilities test. add_test( NAME python_utils_test - COMMAND python3 -m pytest ${CMAKE_CURRENT_SOURCE_DIR}/calico/test/python_utils_test.py - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/calico/test + COMMAND python3 -m pytest ${CMAKE_CURRENT_LIST_DIR}/calico/test/python_utils_test.py + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/calico/test ) set_tests_properties( python_bindings_test python_utils_test diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8107410 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM ubuntu:22.04 + +ARG DEBIAN_FRONTEND=noninteractive +ARG TZ=US/Eastern + +RUN apt update && \ + apt install -y \ + tzdata sudo git \ + python3.10 python3.10-venv python3.10-dev python3-pip \ + libeigen3-dev libgtest-dev libabsl-dev \ + libopencv-dev libyaml-cpp-dev libgmock-dev + +COPY scripts/install-ceres.sh /tmp/install-ceres.sh +RUN /tmp/install-ceres.sh + +WORKDIR /calico +RUN chmod 777 /calico \ No newline at end of file diff --git a/README.md b/README.md index d97c69d..0b99963 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,50 @@ Calico features: Check out our [wiki pages](https://github.com/yangjames/Calico/wiki) for more info. +# Dependencies + +* The minimum supported version of Ubuntu is 22.04. +* * This is because Ubuntu 20.04 and older do not ship with libabsl-dev. +* The version of Ceres that ships with 22.04 is too old for Calico, run `./scripts/install-ceres.sh` to install the latest version. +* * Optional: Install the latest version of the CUDA sdk before you install Ceres + +You will also need the following dependencies: + +``` +sudo apt install -y libeigen3-dev libgtest-dev libabsl-dev \ + libopencv-dev libyaml-cpp-dev libgmock-dev +``` + +# Building + +## Native + +```bash +mkdir build +cd build +cmake .. +make -j3 +make install +``` + +## Python + +Note: For the Python wheel you do _not_ need to compile the native code first. The wheel is self-contained. + +From main directory: + +```bash +pip wheel --no-deps -w wheels . +``` + +### Docker + +If you have docker and just want the python wheel, you don't need to install any dependencies and can simply run: + +./scripts/build-python.sh + +and sit back and relax. Eventually you'll find the wheel in the `wheels` folder. + # License Poor sensor calibration is a problem endemic to robotics, yet it's typically not given enough attention because it tends to detract from higher level project goals. The purpose of this library is to help roboticists quickly resolve their calibration issues so that they can move on to more interesting things. This library is hereby granted the MIT license, to be used free of charge by anyone within academia or industry. diff --git a/calico/__init__.py b/calico/__init__.py deleted file mode 100644 index 783a420..0000000 --- a/calico/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from ._calico import * -from .utils import * diff --git a/calico/test/__pycache__/python_bindings_test.cpython-310-pytest-6.2.5.pyc b/calico/test/__pycache__/python_bindings_test.cpython-310-pytest-6.2.5.pyc deleted file mode 100644 index 1243d6d..0000000 Binary files a/calico/test/__pycache__/python_bindings_test.cpython-310-pytest-6.2.5.pyc and /dev/null differ diff --git a/pyproject.toml b/pyproject.toml index 8b96441..410282d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,14 +1,19 @@ [build-system] -requires = ["setuptools", "wheel", "pybind11", "opencv-python", "scipy", "pyyaml"] +requires = [ + "setuptools", + "wheel", + "pybind11", + "pybind11[global]", + "cmake>=3.12" +] build-backend = "setuptools.build_meta" -[tool.setuptools.packages.find] -where = ["."] -include = ["."] -exclude = ["demos"] +[tool.setuptools] +packages= ["calico"] +package-dir = {"" = "python"} [project] -name = "calico-python" +name = "calico" version = "0.1.0" description="A visual-inertial calibration library designed for rapid problem construction and debugging." readme = "README.md" @@ -24,6 +29,18 @@ classifiers = [ "Operating System :: Unix", "Programming Language :: Python", ] +dependencies = [ + 'numpy ~= 1.26.0', + "opencv-python", + "pyyaml", +] + +[project.optional-dependencies] # Optional +test = [ + 'pytest>=6.0', + "scipy" +] + [project.urls] -"Homepage" = "https://github.com/yangjames/Calico.git" \ No newline at end of file +"Homepage" = "https://github.com/yangjames/Calico.git" diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt new file mode 100644 index 0000000..4c8deb6 --- /dev/null +++ b/python/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.4...3.18) +project(calico) + +include(${CMAKE_CURRENT_LIST_DIR}/../CMakeLists.txt) + +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PYBIND_LIBRARY_OUTPUT_DIRECTORY}) + +# # Load dependencies. +find_package(pybind11 REQUIRED) + +# Add top level include directory. +include_directories( + ${CMAKE_CURRENT_LIST_DIR} +) + +pybind11_add_module(native calico.cpp) +target_link_libraries( + native PUBLIC + Eigen3::Eigen + accelerometer + aprilgrid_detector + batch_optimizer + camera + gyroscope + trajectory + world_model +) + + +# CALICO_VERSION_INFO is defined by setup.py and passed into the C++ code as a +# define (VERSION_INFO) here. +target_compile_definitions(native + PRIVATE VERSION_INFO=${CALICO_VERSION_INFO}) diff --git a/calico/calico.cpp b/python/calico.cpp similarity index 99% rename from calico/calico.cpp rename to python/calico.cpp index f9eea07..a4c1ddd 100644 --- a/calico/calico.cpp +++ b/python/calico.cpp @@ -15,8 +15,8 @@ #include "pybind11/stl.h" -PYBIND11_MODULE(_calico, m) { - m.doc() = "Calico"; +PYBIND11_MODULE(native, m) { + m.doc() = "Native code for calico"; namespace py = pybind11; using namespace calico; using namespace calico::chart_detectors; diff --git a/python/calico/__init__.py b/python/calico/__init__.py new file mode 100644 index 0000000..71c3e32 --- /dev/null +++ b/python/calico/__init__.py @@ -0,0 +1,2 @@ +from .native import * +from .utils import * \ No newline at end of file diff --git a/calico/test/python_bindings_test.py b/python/calico/test/python_bindings_test.py similarity index 100% rename from calico/test/python_bindings_test.py rename to python/calico/test/python_bindings_test.py diff --git a/calico/test/python_utils_test.py b/python/calico/test/python_utils_test.py similarity index 100% rename from calico/test/python_utils_test.py rename to python/calico/test/python_utils_test.py diff --git a/calico/utils.py b/python/calico/utils.py similarity index 99% rename from calico/utils.py rename to python/calico/utils.py index 610e5ad..d964394 100644 --- a/calico/utils.py +++ b/python/calico/utils.py @@ -1,7 +1,7 @@ """@package Python utils Utility functions for calico python bindings. """ -import calico +import calico.native as calico import cv2 import numpy as np @@ -123,7 +123,7 @@ def InitializePinholeAndPoses( [ 0 fy cy] [ 0 0 1] R_chart_camera: - List of 3x3 + List of 3x3 """ V = np.zeros((2*len(all_detections), 6)) H_camera_chart = [] diff --git a/scripts/build-docker.sh b/scripts/build-docker.sh new file mode 100755 index 0000000..0cabbce --- /dev/null +++ b/scripts/build-docker.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +DOCKER_BASE=$(dirname "$(realpath "${BASH_SOURCE[0]}")")/.. + +if [[ $USER == "root" ]]; then + echo "Cannot be root!" + exit 1 +fi + +# build local dockerfile to run bg apps in containers +export DOCKER_BUILDKIT=1 +export UBUNTU_VERSION=22.04 + +( + cd "${DOCKER_BASE}" || exit 1 + + docker build --progress plain --pull -f Dockerfile \ + -t ghcr.io/yangjames/calico:latest \ + --build-arg UBUNTU_VERSION . +) diff --git a/scripts/build-python.sh b/scripts/build-python.sh new file mode 100755 index 0000000..2ac2859 --- /dev/null +++ b/scripts/build-python.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +DOCKER_BASE=$(dirname "$(realpath "${BASH_SOURCE[0]}")")/.. + +./scripts/build-docker.sh + +# If we don't do this, we'll end up with files owned by root +# all over the place. +export USER_ID=$(id -u) +export GID=$(id -g) + +docker run --rm \ + --user $USER_ID:$GID \ + -v ./:/calico:rw \ + ghcr.io/yangjames/calico:latest \ + /bin/bash -c "python3 -m venv venv && . ./venv/bin/activate && pip wheel --no-deps -w wheels ." \ No newline at end of file diff --git a/scripts/install-ceres.sh b/scripts/install-ceres.sh new file mode 100755 index 0000000..f4eb292 --- /dev/null +++ b/scripts/install-ceres.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# CMake +# google-glog + gflags +# Use ATLAS for BLAS & LAPACK +# Eigen3 +# SuiteSparse (optional) +sudo apt-get install -y cmake libgoogle-glog-dev \ + libgflags-dev libatlas-base-dev libeigen3-dev \ + libsuitesparse-dev + +cd /tmp || exit +git clone https://ceres-solver.googlesource.com/ceres-solver + +mkdir ceres-bin +cd ceres-bin || exit +USE_CUDA=off cmake ../ceres-solver + +make -j3 +#make test +# Optionally install Ceres, it can also be exported using CMake which +# allows Ceres to be used without requiring installation, see the documentation +# for the EXPORT_BUILD_DIR option for more information. +sudo make install \ No newline at end of file diff --git a/setup.py b/setup.py index 0bb843d..5a96583 100644 --- a/setup.py +++ b/setup.py @@ -1,86 +1,131 @@ import os -import sys -import sysconfig -import platform +import re import subprocess +import sys from pathlib import Path -from distutils.version import LooseVersion -from setuptools import setup, Extension, find_packages +from setuptools import Extension, setup from setuptools.command.build_ext import build_ext -from setuptools.command.test import test as TestCommand +# Convert distutils Windows platform specifiers to CMake -A arguments +PLAT_TO_CMAKE = { + "win32": "Win32", + "win-amd64": "x64", + "win-arm32": "ARM", + "win-arm64": "ARM64", +} + +# A CMakeExtension needs a sourcedir instead of a file list. +# The name must be the _single_ output extension from the CMake build. +# If you need multiple extensions, see scikit-build. class CMakeExtension(Extension): - def __init__(self, name, sources=[]): - Extension.__init__(self, name, sources=sources) + def __init__(self, name: str, sourcedir: str = "") -> None: + super().__init__(name, sources=[]) + self.sourcedir = os.fspath(Path(sourcedir).resolve()) class CMakeBuild(build_ext): - def run(self): - try: - out = subprocess.check_output(['cmake', '--version']) - except OSError: - raise RuntimeError( - "CMake must be installed to build the following extensions: " + - ", ".join(e.name for e in self.extensions)) - - build_directory = os.path.abspath(self.build_temp) - - cmake_args = [ - '-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + build_directory, - '-DPYTHON_EXECUTABLE=' + sys.executable - ] - - cfg = 'Debug' if self.debug else 'Release' - build_args = ['--config', cfg] - - cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg, '-DBUILD_TESTING=OFF'] - - # Assuming Makefiles - build_args += ['--'] - - self.build_args = build_args - - env = os.environ.copy() - env['CXXFLAGS'] = '{} -DVERSION_INFO=\\"{}\\"'.format( - env.get('CXXFLAGS', ''), - self.distribution.get_version()) - if not os.path.exists(self.build_temp): - os.makedirs(self.build_temp) - - # CMakeLists.txt is in the same directory as this setup.py file - cmake_list_dir = os.path.abspath(os.path.dirname(__file__)) - print('-'*10, 'Running CMake prepare', '-'*40) - subprocess.check_call(['cmake', cmake_list_dir] + cmake_args, - cwd=self.build_temp, env=env) - - print('-'*10, 'Building extensions', '-'*40) - cmake_cmd = ['cmake', '--build', '.'] + self.build_args - subprocess.check_call(cmake_cmd, - cwd=self.build_temp) - - # Move from build temp to final position - for ext in self.extensions: - self.move_output(ext) - - def move_output(self, ext): - build_temp = Path(self.build_temp).resolve() - dest_path = Path(self.get_ext_fullpath(ext.name)).resolve() - source_path = build_temp / self.get_ext_filename(ext.name) - dest_directory = dest_path.parents[0] - dest_directory.mkdir(parents=True, exist_ok=True) - self.copy_file(source_path, dest_path) - -ext_modules = [ - CMakeExtension('calico._calico'), -] - + def build_extension(self, ext: CMakeExtension) -> None: + # Must be in this form due to bug in .resolve() only fixed in Python 3.10+ + ext_fullpath = Path.cwd() / self.get_ext_fullpath(ext.name) + extdir = ext_fullpath.parent.resolve() + + # Using this requires trailing slash for auto-detection & inclusion of + # auxiliary "native" libs + + debug = int(os.environ.get("DEBUG", 0)) if self.debug is None else self.debug + cfg = "Debug" if debug else "Release" + + # CMake lets you override the generator - we need to check this. + # Can be set with Conda-Build, for example. + cmake_generator = os.environ.get("CMAKE_GENERATOR", "") + + # Set Python_EXECUTABLE instead if you use PYBIND11_FINDPYTHON + # EXAMPLE_VERSION_INFO shows you how to pass a value into the C++ code + # from Python. + cmake_args = [ + f"-DPYBIND_LIBRARY_OUTPUT_DIRECTORY={extdir}{os.sep}", + f"-DPYTHON_EXECUTABLE={sys.executable}", + f"-DCMAKE_BUILD_TYPE={cfg}", # not used on MSVC, but no harm + ] + build_args = [] + # Adding CMake arguments set as environment variable + # (needed e.g. to build for ARM OSx on conda-forge) + if "CMAKE_ARGS" in os.environ: + cmake_args += [item for item in os.environ["CMAKE_ARGS"].split(" ") if item] + + # In this example, we pass in the version to C++. You might not need to. + cmake_args += [f"-DCALICO_VERSION_INFO={self.distribution.get_version()}"] + + if self.compiler.compiler_type != "msvc": + # Using Ninja-build since it a) is available as a wheel and b) + # multithreads automatically. MSVC would require all variables be + # exported for Ninja to pick it up, which is a little tricky to do. + # Users can override the generator with CMAKE_GENERATOR in CMake + # 3.15+. + if not cmake_generator or cmake_generator == "Ninja": + try: + import ninja + + ninja_executable_path = Path(ninja.BIN_DIR) / "ninja" + cmake_args += [ + "-GNinja", + f"-DCMAKE_MAKE_PROGRAM:FILEPATH={ninja_executable_path}", + ] + except ImportError: + pass + + else: + # Single config generators are handled "normally" + single_config = any(x in cmake_generator for x in {"NMake", "Ninja"}) + + # CMake allows an arch-in-generator style for backward compatibility + contains_arch = any(x in cmake_generator for x in {"ARM", "Win64"}) + + # Specify the arch if using MSVC generator, but only if it doesn't + # contain a backward-compatibility arch spec already in the + # generator name. + if not single_config and not contains_arch: + cmake_args += ["-A", PLAT_TO_CMAKE[self.plat_name]] + + # Multi-config generators have a different way to specify configs + if not single_config: + cmake_args += [ + f"-DPYBIND_LIBRARY_OUTPUT_DIRECTORY_{cfg.upper()}={extdir}" + ] + build_args += ["--config", cfg] + + if sys.platform.startswith("darwin"): + # Cross-compile support for macOS - respect ARCHFLAGS if set + archs = re.findall(r"-arch (\S+)", os.environ.get("ARCHFLAGS", "")) + if archs: + cmake_args += ["-DCMAKE_OSX_ARCHITECTURES={}".format(";".join(archs))] + + # Set CMAKE_BUILD_PARALLEL_LEVEL to control the parallel build level + # across all generators. + if "CMAKE_BUILD_PARALLEL_LEVEL" not in os.environ: + # self.parallel is a Python 3 only way to set parallel jobs by hand + # using -j in the build_ext call, not supported by pip or PyPA-build. + if hasattr(self, "parallel") and self.parallel: + # CMake 3.12+ only. + build_args += [f"-j{self.parallel}"] + + build_temp = Path(self.build_temp) / ext.name + if not build_temp.exists(): + build_temp.mkdir(parents=True) + + subprocess.run( + ["cmake", ext.sourcedir, *cmake_args], cwd=build_temp, check=True + ) + subprocess.run( + ["cmake", "--build", ".", *build_args], cwd=build_temp, check=True + ) + +# The information here can also be placed in setup.cfg - better separation of +# logic and declaration, and simpler if you include description/version in a file. setup( - packages=find_packages(), - py_modules=["calico/utils", "calico/__init__"], - package_dir={"": "."}, - ext_modules=ext_modules, - cmdclass=dict(build_ext=CMakeBuild), - zip_safe=False, + ext_modules=[CMakeExtension("calico.native", "python")], + cmdclass={"build_ext": CMakeBuild}, + zip_safe=False, )