├── .gitignore ├── CMakeLists.txt ├── Example.ipynb ├── LICENSE ├── README.md ├── TUTORIAL.md ├── cpp ├── greeting.cpp ├── greeting.h └── sub_dir │ ├── curse.cpp │ ├── curse.h │ └── sub_dir.i ├── gtsam_example.i ├── python ├── .gitignore ├── CMakeLists.txt ├── main.py ├── requirements.txt └── tests │ └── test_example.py └── wrapper ├── __init__.py.in ├── preamble ├── gtsam_example.h └── sub_dir.h ├── pybind_wrapper.tpl.example ├── setup.py.in └── specializations ├── gtsam_example.h └── sub_dir.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # Directories 35 | .ipynb_checkpoints/* 36 | **/build/* 37 | example/__pycache__ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file should be used as a template for creating new projects with Python 2 | # wrapping using the CMake tools 3 | 4 | # ############################################################################## 5 | # * 1. To create your own project, replace "gtsam_example" with the actual name 6 | # of your project 7 | cmake_minimum_required(VERSION 3.9) 8 | project(gtsam_example CXX C) 9 | set(CXX_STANDARD 11) 10 | 11 | # ############################################################################## 12 | # * 2. Find GTSAM components so we have access to the GTSAM Cython install path 13 | find_package(GTSAM REQUIRED) # Uses installed package 14 | # Note: Since Jan-2019, GTSAMConfig.cmake defines exported CMake targets that 15 | # automatically do include the include_directories() without the need to call 16 | # include_directories(), just target_link_libraries(NAME gtsam) 17 | # include_directories(${GTSAM_INCLUDE_DIR}) 18 | 19 | # ############################################################################## 20 | # * 3. Add the local source directory for CMake Ensure that local folder is 21 | # searched before library folders 22 | include_directories(BEFORE "${PROJECT_SOURCE_DIR}") 23 | 24 | # ############################################################################## 25 | # * 4. Build static library from common sources. This is a common cmake step 26 | # where we load all the source files and link against the GTSAM library. 27 | add_library(${PROJECT_NAME} SHARED cpp/greeting.h cpp/greeting.cpp 28 | cpp/sub_dir/curse.h cpp/sub_dir/curse.cpp) 29 | target_link_libraries(${PROJECT_NAME} gtsam) 30 | 31 | # ############################################################################## 32 | # * 5. Install library 33 | install( 34 | TARGETS ${PROJECT_NAME} 35 | LIBRARY DESTINATION lib 36 | ARCHIVE DESTINATION lib 37 | RUNTIME DESTINATION bin) 38 | 39 | # ############################################################################## 40 | # # Build tests (CMake tracks the dependecy to link with GTSAM through our 41 | # project's static library) gtsamAddTestsGlob("${PROJECT_NAME}" 42 | # "tests/test*.cpp" "" "${PROJECT_NAME}") 43 | 44 | # ############################################################################## 45 | # # Build scripts (CMake tracks the dependecy to link with GTSAM through our 46 | # project's static library) gtsamAddExamplesGlob("*.cpp" "" "${PROJECT_NAME}") 47 | 48 | # ############################################################################## 49 | # Upto this point, we've only focused on building the C++ library. For details 50 | # on how to build the python wrapper, please see the CMakeLists.txt file in the 51 | # `python` directory. 52 | add_subdirectory(python) 53 | -------------------------------------------------------------------------------- /Example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Example Notebook" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": null, 13 | "metadata": {}, 14 | "outputs": [], 15 | "source": [ 16 | "import math\n", 17 | "import unittest\n", 18 | "\n", 19 | "# import gtsam\n", 20 | "import gtsam\n", 21 | "from gtsam.utils.test_case import GtsamTestCase\n", 22 | "\n", 23 | "# import local package\n", 24 | "import example.module as mod" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": null, 30 | "metadata": {}, 31 | "outputs": [], 32 | "source": [ 33 | "class TestGTSamFunctionality(GtsamTestCase):\n", 34 | " \"\"\"testing GTSAM functionality.\"\"\"\n", 35 | "\n", 36 | " def test_range(self):\n", 37 | " \"\"\"Test range method.\"\"\"\n", 38 | " l2 = gtsam.Point3(1, 1, 0)\n", 39 | " x1 = gtsam.Pose3()\n", 40 | "\n", 41 | " # establish range is indeed sqrt2\n", 42 | " self.assertEqual(math.sqrt(2.0), x1.range(point=l2))\n", 43 | "\n", 44 | "class TestLocalModule(GtsamTestCase):\n", 45 | " \"\"\"testing GTSAM functionality through local module.\"\"\"\n", 46 | " \n", 47 | " def test_meaning_of_everything(self):\n", 48 | " \"\"\"Test simple function in module.\"\"\"\n", 49 | " self.assertEqual(mod.meaning_of_everything(), 42)\n", 50 | "\n", 51 | " def test_create_special_2d_pose(self):\n", 52 | " \"\"\"Test GTSAM function in module.\"\"\"\n", 53 | " actual = mod.create_special_2d_pose()\n", 54 | " self.assertIsInstance(actual, gtsam.Pose2)\n", 55 | " self.gtsamAssertEquals(actual, gtsam.Pose2(1,2,3), 1e-7)\n", 56 | "\n", 57 | "if __name__ == '__main__':\n", 58 | " unittest.main(argv=['first-arg-is-ignored'], exit=False)" 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": null, 64 | "metadata": {}, 65 | "outputs": [], 66 | "source": [] 67 | } 68 | ], 69 | "metadata": { 70 | "kernelspec": { 71 | "display_name": "Python 3", 72 | "language": "python", 73 | "name": "python3" 74 | }, 75 | "language_info": { 76 | "codemirror_mode": { 77 | "name": "ipython", 78 | "version": 3 79 | }, 80 | "file_extension": ".py", 81 | "mimetype": "text/x-python", 82 | "name": "python", 83 | "nbconvert_exporter": "python", 84 | "pygments_lexer": "ipython3", 85 | "version": "3.7.3" 86 | } 87 | }, 88 | "nbformat": 4, 89 | "nbformat_minor": 2 90 | } 91 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2019, BORG Lab 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gtsam-project-python 2 | 3 | Project template using GTSAM + Python wrapping 4 | 5 | ## PREREQUISITES 6 | 7 | - Python 3.6+ is required, since we support these versions. 8 | - To install the wrap package via `GTSAM`: 9 | 10 | - Set the CMake flag `GTSAM_BUILD_PYTHON` to `ON` to enable building the Pybind11 wrapper. 11 | - Set the CMake flag `GTSAM_PYTHON_VERSION` to `3.x` (e.g. `3.7`), otherwise the default interpreter will be used. 12 | - You can do this on the command line as follows: 13 | 14 | ```sh 15 | cmake -DGTSAM_BUILD_PYTHON=ON -DGTSAM_PYTHON_VERSION=3.7 .. 16 | ``` 17 | - Alternatively, you can install the wrap package directly from the [repo](https://github.com/borglab/wrap), but you will still need to install `GTSAM`. 18 | 19 | ## INSTALL 20 | 21 | - In the top-level directory, create the `build` directory and `cd` into it. 22 | - Run `cmake ..`. 23 | - Run `make`, and the wrapped module will be installed to the `python` directory in the top-level. 24 | - To install the wrapped module, simply run `make python-install`. 25 | - You can also run `python main.py` which calls the wrapped module, to get a flavor of how it works. 26 | 27 | ## DOCUMENTATION 28 | 29 | For more detailed information, please refer to the [tutorial](TUTORIAL.md). 30 | -------------------------------------------------------------------------------- /TUTORIAL.md: -------------------------------------------------------------------------------- 1 | # Tutorial 2 | 3 | This is a tutorial on how to wrap your own C++ projects using GTSAM's Pybind11 wrapper. 4 | 5 | # Prerequisites 6 | 7 | We assume you have Python 3 installed. We support Python 3.6 and up. 8 | 9 | We also assume some knowledge of how Python packaging and setup works. If you understand how to write your own basic `setup.py` file, you should be fine. 10 | Using this template project, you should only need to update the metadata information about your project. 11 | 12 | Check out the [python packaging website](https://packaging.python.org/tutorials/packaging-projects/) to learn more. 13 | 14 | As a bonus, if you understand Pybind11's build process, this tutorial should be fairly intuitive. 15 | 16 | **NOTE** This tutorial has been tested using GTSAM version 4.1.0 and above. 17 | 18 | # Project Setup 19 | 20 | As a set of minimal requirements, the project should be set up as follows: 21 | 22 | ``` 23 | top-level-directory 24 | | 25 | |- python/ 26 | |- tests/ 27 | |- __init__.py.in 28 | |- preamble.h 29 | |- requirements.txt 30 | |- setup.py.in 31 | |- specializations.h 32 | |- src/ 33 | |- wrap 34 | |- CMakeLists.txt 35 | |- .h 36 | ``` 37 | 38 | The files are 39 | 40 | 1. `python`: Directory with Python related files and meta-files. 41 | - `tests`: Directory of tests for the python module. 42 | - `__init__.py.in`: Template file for `__init__.py`. 43 | - `preamble.h`: 44 | - `requirements.txt`: Set of requirements needed by the wrapped module. 45 | - `setup.py.in`: Template file for `setup.py`. 46 | - `specializations.h`: 47 | 2. `src/`: All your C++ source code goes here. 48 | 3. `CMakeLists.txt`: The cmake definition file. 49 | 4. `.h`: The header file which specifies all the code components to be wrapped. 50 | 51 | You can add the `wrap` repository by running the file `update_wrap.sh`. 52 | 53 | # CMake Configuration 54 | 55 | In this section, we will go through a step-by-step process of defining the `CMakeLists.txt` file which will generated our wrapped code. 56 | 57 | An illustrative example is provided in the `src` directory of this repository. 58 | 59 | 1. Define project name. 60 | 2. Optionally, set the Python version you'd like to target. This should ideally be the same as the version you used to build GTSAM and the GTSAM wrapper. 61 | 3. Include `GTSAM` package. This allows use to use the cython install path automatically. CMake will take care of the rest. 62 | 63 | ```cmake 64 | find_package(GTSAM REQUIRED) 65 | ``` 66 | 67 | 4. These next few steps should be familiar for CMake users. We first include the project source directory. 68 | 69 | ```cmake 70 | include_directories(BEFORE "${PROJECT_SOURCE_DIR}") 71 | ``` 72 | 73 | 5. Now we can specify the building and linking of our project code as a shared library. 74 | 75 | ```cmake 76 | add_library(${PROJECT_NAME} SHARED 77 | src/greeting.h 78 | src/greeting.cpp) 79 | target_link_libraries(${PROJECT_NAME} gtsam) 80 | ``` 81 | 82 | 6. And finally, we can install the shared library. 83 | 84 | ```cmake 85 | install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION lib ARCHIVE DESTINATION lib RUNTIME DESTINATION bin) 86 | ``` 87 | 88 | 7. Now we get to the wrapping part. We simply need to find the `gtwrap` package which contains the code for wrapping, and this will automatically include the CMake code for the Pybind wrapper. 89 | 90 | ```cmake 91 | find_package(gtwrap REQUIRED) 92 | ``` 93 | 94 | 8. Next, to specify our project as a package, we need to include some files and metafiles, such as an `__init__.py` file at the top level and a `setup.py` file to install the wrapped code correctly. We also need a Pybind11 template file so that the wrapper can generate the C++ file that will be used by Pybind11 to generate the wrapped .so file. 95 | 96 | We can use the basic `__init__.py.in`, `setup.py.in`, and `pybind_wrapper.tpl.example` templates in this repo for convenience. Please adjust them as you see fit. 97 | 98 | - We set the version string to be set in the `setup.py`: 99 | ```cmake 100 | set(GTSAM_VERSION_STRING 0.0.1) 101 | ``` 102 | 103 | - Now, we can include the various module and build files: 104 | ```cmake 105 | # We use this as a convenience variable. 106 | set(GTSAM_MODULE_PATH ${PROJECT_BINARY_DIR}/${PROJECT_NAME}) 107 | 108 | # Add the setup.py file 109 | configure_file(${PROJECT_SOURCE_DIR}/python/setup.py.in 110 | ${GTSAM_MODULE_PATH}/setup.py) 111 | 112 | # Add the __init__.py file 113 | configure_file(${PROJECT_SOURCE_DIR}/python/__init__.py.in 114 | ${GTSAM_MODULE_PATH}/${PROJECT_NAME}/__init__.py) 115 | 116 | # Add the Pybind11 template file. 117 | configure_file(${PROJECT_SOURCE_DIR}/wrap/pybind_wrapper.tpl.example 118 | ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.tpl) 119 | 120 | ``` 121 | 122 | - Finally, we copy over the tests. More such copy operations can be added for other types of files. 123 | 124 | ```cmake 125 | # Copy all the tests files to module folder. 126 | file(COPY "${PROJECT_SOURCE_DIR}/python/tests" 127 | DESTINATION "${GTSAM_MODULE_PATH}") 128 | ``` 129 | 130 | 9. We now specify the wrapping function so that the GTSAM wrapper can do its job. We require only one function `pybind_wrap` which takes the following 9 arguments: 131 | 132 | 1. Target: The name of the make target with which the wrapping process is associated. 133 | 2. Interface Header: A `.h` (or `.i`) file which defines what classes, functions, etc., are to be wrapped. 134 | 3. Generated Cpp: The name of the generated .cpp file which Pybind11 will use. 135 | 4. Module Name: The name of the Python module. Required for Pybdind11. 136 | 5. Top Namespace: The top-level namespace in the C++ source code. 137 | 6. Classes To Ignore: The list of classes to ignore from wrapping. This is generally applied for classes which are type aliases (aka `typedef`). 138 | 7. Pybind11 Template: The template file which will be used to generated the .cpp Pybind11 file. 139 | 8. Library Name: The name of the library .so file that will be generated on compilation. 140 | 9. CMake Dependencies: A comma-separated list of CMake dependencies that need to be built before the code is to be wrapped. At the very least, this should be the library we defined in step 6. 141 | 142 | ```cmake 143 | pybind_wrap(${PROJECT_NAME}_py # target 144 | ${PROJECT_SOURCE_DIR}/${PROJECT_NAME}.h # interface header file (gtsam_example.h in the root directory) 145 | "${PROJECT_NAME}.cpp" # the generated cpp (gtsam_example.cpp) 146 | "${PROJECT_NAME}" # module_name (gtsam_example) 147 | "${PROJECT_NAME}" # top namespace in the cpp file (gtsam_example) 148 | "" # ignore classes 149 | ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.tpl 150 | ${PROJECT_NAME} # libs 151 | "${PROJECT_NAME}" # dependencies, we need the library built in step 6 as the minimum. 152 | ) 153 | ``` 154 | 155 | To save the generated .so library in the correct place and ensure correct naming, we set the appropriate properties on the Make target: 156 | 157 | ```cmake 158 | set_target_properties(${PROJECT_NAME}_py PROPERTIES 159 | OUTPUT_NAME "${PROJECT_NAME}" 160 | LIBRARY_OUTPUT_DIRECTORY "${GTSAM_MODULE_PATH}/${PROJECT_NAME}" 161 | DEBUG_POSTFIX "" # Otherwise you will have a wrong name 162 | RELWITHDEBINFO_POSTFIX "" # Otherwise you will have a wrong name 163 | ) 164 | ``` 165 | 166 | 10. Finally, we add a custom `make` command to make (pun unintended) installing the generated python package super easy. 167 | 168 | ```cmake 169 | add_custom_target(python-install 170 | COMMAND ${PYTHON_EXECUTABLE} ${GTSAM_MODULE_PATH}/setup.py install 171 | DEPENDS gtsam_example_py 172 | WORKING_DIRECTORY ${GTSAM_MODULE_PATH}) 173 | ``` 174 | 175 | # Compiling 176 | 177 | To compile and wrap the code, the familiar CMake process is followed. Starting from the top-level root directory where the `gtsam_example.h` file is located, we create a build directory and run `cmake` and `make`. 178 | 179 | ```sh 180 | mkdir build && cd build 181 | cmake .. && make 182 | ``` 183 | 184 | # Installing 185 | 186 | To install the package, in the `build` directory we can run `make python-install`. 187 | -------------------------------------------------------------------------------- /cpp/greeting.cpp: -------------------------------------------------------------------------------- 1 | #include "greeting.h" 2 | 3 | namespace gtsam_example { 4 | 5 | /// Print a greeting 6 | void Greeting::sayHello() const { 7 | std::cout << "Hello from GTSAM!" << std::endl; 8 | std::cout << "Here's a Rot3 for you " << gtsam::Rot3() << std::endl; 9 | } 10 | 11 | void Greeting::takeAPose3(const gtsam::Pose3& pose) const { 12 | std::cout << "Got\n" << pose << std::endl; 13 | } 14 | 15 | /// Process a GTSAM object 16 | gtsam::Rot3 Greeting::invertRot3(gtsam::Rot3 rot) const { 17 | return rot.inverse(); 18 | } 19 | 20 | /// Print a farewell 21 | void Greeting::sayGoodbye() const { 22 | std::cout << "Goodbye, robot" << std::endl; 23 | } 24 | 25 | }; // namespace gtsam_example 26 | -------------------------------------------------------------------------------- /cpp/greeting.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace gtsam_example { 11 | 12 | class Greeting { 13 | std::vector names_; 14 | 15 | public: 16 | gtsam::noiseModel::Base::shared_ptr model = 17 | gtsam::noiseModel::Isotropic::Sigma(1, 0.1); 18 | 19 | /// Print a greeting 20 | void sayHello() const; 21 | 22 | void takeAPose3(const gtsam::Pose3& pose) const; 23 | 24 | /// Process a GTSAM object 25 | gtsam::Rot3 invertRot3(gtsam::Rot3 rot) const; 26 | 27 | /// Print a farewell 28 | void sayGoodbye() const; 29 | 30 | gtsam::Matrix getMatrix(gtsam::noiseModel::Base::shared_ptr model) const { 31 | return gtsam::Matrix3::Zero(); 32 | } 33 | 34 | std::string operator()(const std::string& name) const { 35 | return "Function invoked by " + name; 36 | } 37 | 38 | void insertName(const std::string& name) { names_.push_back(name); } 39 | 40 | std::string operator[](size_t idx) const { return this->names_[idx]; } 41 | 42 | void print() const { 43 | std::cout << "This is a greeting" << std::endl; 44 | } 45 | }; 46 | 47 | } // namespace gtsam_example 48 | -------------------------------------------------------------------------------- /cpp/sub_dir/curse.cpp: -------------------------------------------------------------------------------- 1 | #include "curse.h" 2 | 3 | namespace gtsam_example { 4 | void Curse::speak() const { std::cout << "Thundering typhoons!!" << std::endl; } 5 | } // namespace gtsam_example 6 | -------------------------------------------------------------------------------- /cpp/sub_dir/curse.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace gtsam_example { 6 | 7 | class Curse { 8 | public: 9 | /// Print some foul-mouthed slang 10 | void speak() const; 11 | 12 | void print() const { std::cout << "Some vile cursing!" << std::endl; } 13 | }; 14 | 15 | } // namespace gtsam_example 16 | -------------------------------------------------------------------------------- /cpp/sub_dir/sub_dir.i: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace gtsam_example { 4 | 5 | class Curse { 6 | Curse(); 7 | void speak() const; 8 | void print() const; 9 | }; 10 | } -------------------------------------------------------------------------------- /gtsam_example.i: -------------------------------------------------------------------------------- 1 | /* ---------------------------------------------------------------------------- 2 | * GTSAM Copyright 2010, Georgia Tech Research Corporation, 3 | * Atlanta, Georgia 30332-0415 4 | * All Rights Reserved 5 | * Authors: Frank Dellaert, et al. (see THANKS for the full author list) 6 | * 7 | * See LICENSE for the license information 8 | * -------------------------------------------------------------------------- */ 9 | 10 | /** 11 | * @file gtsam_example.h 12 | * @brief Example wrapper interface file for Python 13 | * @author Varun Agrawal 14 | */ 15 | 16 | // This is an interface file for automatic Python wrapper generation. 17 | // See gtsam.h for full documentation and more examples. 18 | 19 | #include 20 | 21 | // The namespace should be the same as in the c++ source code. 22 | namespace gtsam_example { 23 | 24 | class Greeting { 25 | Greeting(); 26 | gtsam::noiseModel::Base* model; 27 | 28 | void sayHello() const; 29 | void takeAPose3(const gtsam::Pose3& pose) const; 30 | gtsam::Rot3 invertRot3(gtsam::Rot3 rot) const; 31 | void sayGoodbye() const; 32 | Matrix getMatrix(gtsam::noiseModel::Base* model) const; 33 | 34 | string operator()(const string& name) const; 35 | 36 | void insertName(const string& name); 37 | string operator[](size_t idx) const; 38 | 39 | void print() const; 40 | }; 41 | 42 | } // namespace gtsam_example 43 | -------------------------------------------------------------------------------- /python/.gitignore: -------------------------------------------------------------------------------- 1 | gtsam_example/ 2 | setup.py 3 | 4 | dist 5 | *.egg-info 6 | **/__pycache__ -------------------------------------------------------------------------------- /python/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # ############################################################################## 2 | # * 1. Find the gtwrap package (either installed via GTSAM or as a standalone 3 | # package) The cmake code for wrapping is included automatically. 4 | find_package(gtwrap REQUIRED) 5 | 6 | # ############################################################################## 7 | # * 2. Set the python version Load the default Python version strings and 8 | # variables. 9 | set(WRAP_PYTHON_VERSION 10 | "Default" 11 | CACHE STRING "The Python version to use for wrapping") 12 | 13 | gtwrap_get_python_version(${WRAP_PYTHON_VERSION}) 14 | 15 | # ############################################################################## 16 | # * 3. Configure all the files for the Python module. Set the version string, 17 | # needed for generating setup.py 18 | set(GTSAM_VERSION_STRING 0.0.1) 19 | 20 | # We use this as a convenience variable. It points to `python/` in this case. 21 | set(PROJECT_PY_MODULE_PATH ${PROJECT_BINARY_DIR}/python) 22 | 23 | # Copy over the setup.py.in file. This will become `python/setup.py`. 24 | configure_file(${PROJECT_SOURCE_DIR}/wrapper/setup.py.in 25 | ${PROJECT_PY_MODULE_PATH}/setup.py) 26 | 27 | # Copy over the __init__.py file. This is now 28 | # `python/gtsam_example/__init__.py`. 29 | configure_file(${PROJECT_SOURCE_DIR}/wrapper/__init__.py.in 30 | ${PROJECT_PY_MODULE_PATH}/${PROJECT_NAME}/__init__.py) 31 | 32 | # Copy over the pybind .tpl file. This is now `build/gtsam_example.tpl`. 33 | configure_file(${PROJECT_SOURCE_DIR}/wrapper/pybind_wrapper.tpl.example 34 | ${PROJECT_PY_MODULE_PATH}/${PROJECT_NAME}.tpl) 35 | 36 | # Define the list of interface files. 37 | set(interface_list ${PROJECT_SOURCE_DIR}/${PROJECT_NAME}.i 38 | ${PROJECT_SOURCE_DIR}/cpp/sub_dir/sub_dir.i) 39 | 40 | # Copy over C++ headers to use for wrapping 41 | foreach(interface ${interface_list}) 42 | get_filename_component(interface_name ${interface} NAME_WE) 43 | 44 | file(COPY "${PROJECT_SOURCE_DIR}/wrapper/preamble/${interface_name}.h" 45 | DESTINATION "${PROJECT_PY_MODULE_PATH}/${PROJECT_NAME}/preamble") 46 | 47 | file(COPY "${PROJECT_SOURCE_DIR}/wrapper/specializations/${interface_name}.h" 48 | DESTINATION "${PROJECT_PY_MODULE_PATH}/${PROJECT_NAME}/specializations") 49 | 50 | endforeach() 51 | 52 | # Copy all the tests files to module folder. 53 | file(COPY "${PROJECT_SOURCE_DIR}/python/tests" 54 | DESTINATION "${PROJECT_PY_MODULE_PATH}") 55 | 56 | # We print out our configuration for an easy visual check 57 | message("========== Configuration Options ==========") 58 | message(STATUS "Project: ${PROJECT_NAME}") 59 | message(STATUS "Python Version: ${WRAP_PYTHON_VERSION}") 60 | message(STATUS "Python Module Path: ${PROJECT_PY_MODULE_PATH}") 61 | message("===========================================") 62 | 63 | # ############################################################################## 64 | # * 4. Build Pybind wrapper This is where the crux of the wrapping happens. 65 | 66 | # Ignoring the non-concrete types (type aliases and typedefs). We don't have any 67 | # in our current project so this is left as empty. 68 | set(ignore "") 69 | 70 | # This is the main function that generates the cpp file which Pybind11 will use. 71 | pybind_wrap( 72 | ${PROJECT_NAME}_py # target 73 | "${interface_list}" # interface header files (gtsam_example.i in the root 74 | # directory) 75 | "${PROJECT_NAME}.cpp" # the generated cpp (gtsam_example.cpp) 76 | "${PROJECT_NAME}" # module_name (gtsam_example) 77 | "${PROJECT_NAME}" # top namespace in the cpp file (gtsam_example) 78 | "${ignore}" # ignore classes 79 | ${PROJECT_PY_MODULE_PATH}/${PROJECT_NAME}.tpl 80 | ${PROJECT_NAME} # libs 81 | "${PROJECT_NAME}" # dependencies, we need the library built in step 6 as the 82 | # minimum. 83 | ON # we are using boost::shared_ptr not std 84 | ) 85 | 86 | # We define where we wish to save the wrapped .so file once we run `make`. It 87 | # will be stored in `build/gtsam_example/gtsam_example` to conform to standard 88 | # python module structure. 89 | set_target_properties( 90 | ${PROJECT_NAME}_py 91 | PROPERTIES OUTPUT_NAME "${PROJECT_NAME}" 92 | LINKER_LANGUAGE CXX 93 | LIBRARY_OUTPUT_DIRECTORY 94 | "${PROJECT_PY_MODULE_PATH}/${PROJECT_NAME}" 95 | DEBUG_POSTFIX "" # Otherwise you will have a wrong name 96 | RELWITHDEBINFO_POSTFIX "" # Otherwise you will have a wrong name 97 | ) 98 | 99 | # ############################################################################## 100 | # * 5. Python install command. We add a custom make command which we can invoke 101 | # to install the generated module. Simply type `make python-install` and we 102 | # can now access the wrapped module as an installed library. 103 | add_custom_target( 104 | python-install 105 | COMMAND ${PYTHON_EXECUTABLE} ${PROJECT_PY_MODULE_PATH}/setup.py install 106 | DEPENDS ${PROJECT_NAME}_py 107 | WORKING_DIRECTORY ${PROJECT_PY_MODULE_PATH}) 108 | 109 | # ############################################################################## 110 | # * 6. Python test command. Simply type `make python-test` to run all the python 111 | # tests in this project. 112 | add_custom_target( 113 | python-test 114 | COMMAND 115 | ${CMAKE_COMMAND} -E env # add package to python path so no need to install 116 | "PYTHONPATH=${PROJECT_BINARY_DIR}/$ENV{PYTHONPATH}" ${PYTHON_EXECUTABLE} -m 117 | unittest discover -v -s . 118 | DEPENDS ${PROJECT_NAME}_py 119 | WORKING_DIRECTORY "${PROJECT_BINARY_DIR}/tests") 120 | -------------------------------------------------------------------------------- /python/main.py: -------------------------------------------------------------------------------- 1 | """ Example python module. """ 2 | 3 | import argparse 4 | 5 | import gtsam 6 | 7 | import gtsam_example 8 | 9 | # pylint: disable = no-member 10 | 11 | 12 | def meaning_of_everything(): 13 | """ return 42. """ 14 | return 42 15 | 16 | 17 | def create_special_2d_pose(): 18 | """ Create a pose with (x,y) = (1,2) and theta = 3.""" 19 | return gtsam.Pose2(1, 2, 3) 20 | 21 | 22 | def greet(): 23 | """ 24 | Call the wrapped Greeting code so it can say Hello and Goodbye. 25 | """ 26 | greeter = gtsam_example.Greeting() 27 | greeter.sayHello() 28 | greeter.sayGoodbye() 29 | 30 | 31 | def parse_arguments(): 32 | parser = argparse.ArgumentParser("Main runner for GTSAM-wrapped code") 33 | parser.add_argument("command", 34 | help="The function to execute \ 35 | (e.g. 'meaning_of_everything', 'create_special_2d_pose', 'greet')") 36 | return parser.parse_args() 37 | 38 | 39 | if __name__ == "__main__": 40 | func_map = { 41 | 'meaning_of_everything': meaning_of_everything, 42 | 'create_special_2d_pose': create_special_2d_pose, 43 | 'greet': greet 44 | } 45 | args = parse_arguments() 46 | func = func_map.get(args.command, meaning_of_everything) 47 | print(func()) 48 | -------------------------------------------------------------------------------- /python/requirements.txt: -------------------------------------------------------------------------------- 1 | numpy>=1.11.0 2 | pyparsing>=2.4.2 3 | gtsam>=4.1 -------------------------------------------------------------------------------- /python/tests/test_example.py: -------------------------------------------------------------------------------- 1 | """ 2 | GTSAM Copyright 2010-2019, Georgia Tech Research Corporation, 3 | Atlanta, Georgia 30332-0415 4 | All Rights Reserved 5 | 6 | See LICENSE for the license information 7 | 8 | Cal3Unified unit tests. 9 | Author: Frank Dellaert & Duy Nguyen Ta (Python) 10 | """ 11 | import unittest 12 | 13 | import numpy as np 14 | 15 | import gtsam 16 | import gtsam_example 17 | from gtsam.utils.test_case import GtsamTestCase 18 | 19 | 20 | class TestExample(GtsamTestCase): 21 | 22 | def test_Hello(self): 23 | g = gtsam_example.Greeting() 24 | g.sayHello() 25 | 26 | def test_invertRot3(self): 27 | g = gtsam_example.Greeting() 28 | r = gtsam.Rot3() 29 | g.invertRot3(r) 30 | 31 | 32 | if __name__ == "__main__": 33 | unittest.main() 34 | -------------------------------------------------------------------------------- /wrapper/__init__.py.in: -------------------------------------------------------------------------------- 1 | import gtsam 2 | from .${PROJECT_NAME} import * 3 | -------------------------------------------------------------------------------- /wrapper/preamble/gtsam_example.h: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a type caster for `boost::optional` so we can use optionals in Python. 3 | * 4 | * Example: 5 | * In C++: void foo(boost::optional &opt); 6 | * In Python: 7 | * - foo(None) 8 | * - foo(bar_instance) 9 | */ 10 | #include 11 | 12 | namespace pybind11 { namespace detail { 13 | template 14 | struct type_caster> : optional_caster> {}; 15 | }} 16 | -------------------------------------------------------------------------------- /wrapper/preamble/sub_dir.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borglab/gtsam-project-python/904cb6b2428fc6f10af0e520a8467d8f7d91e682/wrapper/preamble/sub_dir.h -------------------------------------------------------------------------------- /wrapper/pybind_wrapper.tpl.example: -------------------------------------------------------------------------------- 1 | {include_boost} 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "gtsam/base/utilities.h" // for RedirectCout. 10 | 11 | {includes} 12 | #include 13 | 14 | {boost_class_export} 15 | 16 | {holder_type} 17 | 18 | #include "gtsam_example/preamble/{module_name}.h" 19 | 20 | using namespace std; 21 | 22 | namespace py = pybind11; 23 | 24 | {submodules} 25 | 26 | {module_def} {{ 27 | m_.doc() = "pybind11 wrapper of {module_name}"; 28 | 29 | {submodules_init} 30 | 31 | {wrapped_namespace} 32 | 33 | #include "gtsam_example/specializations/{module_name}.h" 34 | 35 | }} 36 | 37 | -------------------------------------------------------------------------------- /wrapper/setup.py.in: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | try: 5 | from setuptools import setup, find_packages 6 | except ImportError: 7 | from distutils.core import setup, find_packages 8 | 9 | packages = find_packages() 10 | 11 | package_data = { 12 | package: [ 13 | f 14 | for f in os.listdir(package.replace(".", os.path.sep)) 15 | if os.path.splitext(f)[1] in (".so", ".pyd") 16 | ] 17 | for package in packages 18 | } 19 | 20 | dependency_list = open("${PROJECT_SOURCE_DIR}/python/requirements.txt").read().split('\n') 21 | dependencies = [x for x in dependency_list if x[0] != '#'] 22 | 23 | setup( 24 | name='${PROJECT_NAME}', 25 | description='Demo Library using GTSAM.', 26 | url='https://gtsam.org/', 27 | version='${GTSAM_VERSION_STRING}', 28 | author="Varun Agrawal, Fan Jiang", 29 | author_email="varunagrawal@gatech.edu", 30 | license='Simplified BSD license', 31 | keywords="gtsam wrapper tutorial example", 32 | long_description=open("${PROJECT_SOURCE_DIR}/README.md").read(), 33 | long_description_content_type="text/markdown", 34 | python_requires=">=3.6", 35 | # https://pypi.org/pypi?%3Aaction=list_classifiers 36 | classifiers=[ 37 | 'Development Status :: 5 - Production/Stable', 38 | 'Intended Audience :: Education', 39 | 'Intended Audience :: Developers', 40 | 'Intended Audience :: Science/Research', 41 | 'Operating System :: MacOS', 42 | 'Operating System :: Microsoft :: Windows', 43 | 'Operating System :: POSIX', 44 | 'License :: OSI Approved :: BSD License', 45 | 'Programming Language :: Python :: 2', 46 | 'Programming Language :: Python :: 3', 47 | ], 48 | packages=packages, 49 | # Load the built shared object files 50 | package_data=package_data, 51 | include_package_data=True, 52 | test_suite="gtsam_example.tests", 53 | # Ensure that the compiled .so file is properly packaged 54 | zip_safe=False, 55 | platforms="any", 56 | install_requires=dependencies, 57 | ) 58 | -------------------------------------------------------------------------------- /wrapper/specializations/gtsam_example.h: -------------------------------------------------------------------------------- 1 | //PYBIND11_MAKE_OPAQUE(std::vector); 2 | //PYBIND11_MAKE_OPAQUE(std::vector); 3 | //PYBIND11_MAKE_OPAQUE(std::vector); 4 | //PYBIND11_MAKE_OPAQUE(std::vector>); 5 | 6 | // gtsam::BetweenFactorPose3s 7 | // gtsam::Point2Vector 8 | // gtsam::Pose3Vector 9 | // gtsam::KeyVector 10 | 11 | // py::bind_vector >(m_, "KeyVector"); 12 | // py::bind_vector >(m_, "Point2Vector"); 13 | // py::bind_vector >(m_, "Pose3Vector"); 14 | // py::bind_vector > > >(m_, "BetweenFactorPose3s"); -------------------------------------------------------------------------------- /wrapper/specializations/sub_dir.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borglab/gtsam-project-python/904cb6b2428fc6f10af0e520a8467d8f7d91e682/wrapper/specializations/sub_dir.h --------------------------------------------------------------------------------