Skip to content

Commit

Permalink
Merge pull request #2 from borglab/full-working-example
Browse files Browse the repository at this point in the history
Full Working Example
  • Loading branch information
varunagrawal authored Jul 1, 2020
2 parents 1e1f34f + b4904bf commit 60bed89
Show file tree
Hide file tree
Showing 10 changed files with 308 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,5 @@

# Directories
.ipynb_checkpoints/*
build/*
**/build/*
example/__pycache__
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# gtsam-project-python
Project template using GTSAM + python wrapping

# DISCLAIMER
Under construction, doesn't work yet!! :(
Please refer to the [tutorial](TUTORIAL.md) to get started.
124 changes: 124 additions & 0 deletions TUTORIAL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Tutorial

This is a tutorial on how to wrap your own C++ projects using GTSAM's python wrapper.

# Prerequisites

We assume you have Python 3 installed. We support Python 3.6 and up.

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.
Using this template project, you should only need to update the metadata information about your project. Check out the [python packaging website](https://packaging.python.org/tutorials/packaging-projects/) to learn more.

As a bonus, if you understand Cython's build process, this tutorial should be fairly intuitive.

**NOTE** This tutorial has been tested using GTSAM version 4.0.x and above.

# Project Setup

As a set of minimal requirements, the project should be set up as follows:

```
top-level-directory
|
|- CMakeLists.txt
|- <project>.h
|- __init__.py.in
|- setup.py
|- src/
```

The files are

1. `CMakeLists.txt`: The cmake definition file.
2. `<project>.h`: The header file which specifies all the code components to be wrapped.
3. `__init__.py.in`: Template __init__.py file used by cmake.
4. `setup.py`: The file used by setuptools to generate the egg/wheel.
5. `src/`: All your C++ source code goes here.


# CMake Configuration

In this section, we will go through a step-by-step process of defining the `CMakeLists.txt` file which will generated our wrapped code.

An illustrative example is provided in the `src` directory of this repository.

1. Define project name.
2. Optionally, set the Python version you'd like to target. This should ideally be the same as the version you used to build the wrapper.
3. Include `GTSAM` package. This allows use to use the cython install path automatically. CMake will take care of the rest.

```cmake
find_package(GTSAM REQUIRED)
include_directories(${GTSAM_CYTHON_INSTALL_PATH})
include_directories(${GTSAM_EIGENCY_INSTALL_PATH})
```
4. The second package is `GTSAMCMakeTools`. This gives us access to the wrapping functions which we will use later on.
```cmake
find_package(GTSAMCMakeTools CONFIG)
include(GtsamCythonWrap) # Automatic Cython wrapper generation
```
5. These next few steps should be familiar for CMake users. We first include the project source directory.
```cmake
include_directories(BEFORE "${PROJECT_SOURCE_DIR}")
```
6. Now we can specify the building and linking of our project code as a shared library.
```cmake
add_library(${PROJECT_NAME} SHARED src/greeting.h src/greeting.cpp)
target_link_libraries(${PROJECT_NAME} gtsam)
```
7. And finally, we can install the shared library.
```cmake
install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION lib ARCHIVE DESTINATION lib RUNTIME DESTINATION bin)
```
8. Now we get to the wrapping part. To specify our project as a package we need to include an `__init__.py` file at the top level. This will allow python imports to work correctly. We can use the basic `__init__.py.in` template in this repo since it is pretty generic.
```cmake
configure_file(${PROJECT_SOURCE_DIR}/__init__.py.in ${PROJECT_BINARY_DIR}/cython/${PROJECT_NAME}/__init__.py)
```
9. To help build and install the wrapped project, we make use of a `setup.py` file. This file can be customized as per your requirements. **NOTE** This command only copies over the `setup.py` file, so make sure you make any updates **BEFORE** you run `cmake`.
```cmake
configure_file(${PROJECT_SOURCE_DIR}/setup.py ${PROJECT_BINARY_DIR}/cython/setup.py COPYONLY)
```
10. Finally, we specify the wrapping function so that the GTSAM wrapper can do its job. We require only one function `wrap_and_install_library_cython` which takes the following 5 arguments:
1. Interface Header: A `.h` file which defines what classes, functions, etc., are to be wrapped.
2. Extra Imports: This is a set of `cython` imports included in the generated Cython files. You can use this to specify any additional imports your project may be dependent on.
3. Install Path: This is the location where the wrapped package will be installed on running `make install`.
4. Libraries: A semi-colon separated list of libraries which the project will be linked against. At the very least, you should link against `gtsam` and the generated shared object file.
5. Dependencies: This is a semi-colon separated list of dependency targets that need to be built before the code can be compiled and wrapped. This is nothing but a list of CMake targets.
```cmake
wrap_and_install_library_cython("example.h" # interface_header
"" # extra imports
"./${PROJECT_NAME}" # install path
"gtsam;${PROJECT_NAME}" # library to link with
"wrap;gtsam" # dependencies which need to be built before wrapping
)
```
# Compiling
To compile and wrap the code, the familiar CMake process is followed. Starting from the directory where the `setup.py` file is located, we create a build directory and run `cmake` and `make`.
```sh
mkdir build && cd build
cmake .. && make
```

Finally, we go into the generated `cython` directory where the `setup.py` file is present, and run `python setup.py build` to generate the final package.

# Installing

To install the package, in the `cython` directory we can run `python setup.py build`.
71 changes: 71 additions & 0 deletions example/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# This file should be used as a template for creating new projects with Python wrapping using the CMake tools

###################################################################################
# 1. To create your own project, replace "example" with the actual name of your project
cmake_minimum_required(VERSION 3.0)
project(example CXX C)

###################################################################################
# 2. Set the python version
set(GTSAM_PYTHON_VERSION "3.6")

###################################################################################
# 3. Find GTSAM components so we have access to the GTSAM Cython install path
find_package(GTSAM REQUIRED) # Uses installed package
# Note: Since Jan-2019, GTSAMConfig.cmake defines exported CMake targets
# that automatically do include the include_directories() without the need
# to call include_directories(), just target_link_libraries(NAME gtsam)
#include_directories(${GTSAM_INCLUDE_DIR})

# Include the required GTSAM Cython libraries
include_directories(${GTSAM_CYTHON_INSTALL_PATH})
include_directories(${GTSAM_EIGENCY_INSTALL_PATH})

###################################################################################
# 4. Get the wrapping functions
# Include GTSAM CMake tools
find_package(GTSAMCMakeTools CONFIG)
#include(GtsamBuildTypes) # Load build type flags and default to Debug mode
#include(GtsamTesting) # Easy functions for creating unit tests and scripts
include(GtsamCythonWrap) # Automatic Cython wrapper generation

###################################################################################
# 5. Add the local source directory for CMake
# Ensure that local folder is searched before library folders
include_directories(BEFORE "${PROJECT_SOURCE_DIR}")


###################################################################################
# 6. Build static library from common sources
add_library(${PROJECT_NAME} SHARED src/greeting.h src/greeting.cpp)
target_link_libraries(${PROJECT_NAME} gtsam)

###################################################################################
# 7. Install library
install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION lib ARCHIVE DESTINATION lib RUNTIME DESTINATION bin)

###################################################################################
# # Build tests (CMake tracks the dependecy to link with GTSAM through our project's static library)
# gtsamAddTestsGlob("${PROJECT_NAME}" "tests/test*.cpp" "" "${PROJECT_NAME}")

###################################################################################
# # Build scripts (CMake tracks the dependecy to link with GTSAM through our project's static library)
# gtsamAddExamplesGlob("*.cpp" "" "${PROJECT_NAME}")

###################################################################################
# 8. Copy the __init__.py file so Cython recognizes this as a package.
# This function also updates the contents to use the correct package name.
configure_file(${PROJECT_SOURCE_DIR}/__init__.py.in ${PROJECT_BINARY_DIR}/cython/${PROJECT_NAME}/__init__.py)

###################################################################################
# 9. (Strict) Copy over the setup.py file so we can build and install the package.
configure_file(${PROJECT_SOURCE_DIR}/setup.py ${PROJECT_BINARY_DIR}/cython/setup.py COPYONLY)

###################################################################################
# 10. Build Cython wrapper (CMake tracks the dependecy to link with GTSAM through our project's static library)
wrap_and_install_library_cython("example.h" # interface_header
"" # extra imports
"./${PROJECT_NAME}" # install path
"gtsam;${PROJECT_NAME}" # library to link with
"wrap;gtsam" # dependencies which need to be built before wrapping
)
Empty file removed example/__init__.py
Empty file.
1 change: 1 addition & 0 deletions example/__init__.py.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .${PROJECT_NAME} import *
31 changes: 31 additions & 0 deletions example/example.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/* ----------------------------------------------------------------------------
* GTSAM Copyright 2010, Georgia Tech Research Corporation,
* Atlanta, Georgia 30332-0415
* All Rights Reserved
* Authors: Frank Dellaert, et al. (see THANKS for the full author list)
* See LICENSE for the license information
* -------------------------------------------------------------------------- */

/**
* @file example.h
* @brief Example wrapper interface file for Python
* @author Varun Agrawal
*/

// This is an interface file for automatic Python wrapper generation. See
// gtsam.h for full documentation and more examples.

#include <src/greeting.h>

namespace example {

class Greeting {
Greeting();
void sayHello() const;
void sayGoodbye() const;
};

}
47 changes: 47 additions & 0 deletions example/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import os
import sys
try:
from setuptools import setup, find_packages
except ImportError:
from distutils.core import setup, find_packages

packages = find_packages()

setup(
name='example',
description='Simple example of wrapping projects with Python and GTSAM',
url='https://gtsam.org/',
version='1.0.0',
author='Varun Agrawal',
author_email='[email protected]',
license='Simplified BSD license',
keywords='wrapper tutorial example',
long_description="",
long_description_content_type='text/markdown',
python_requires='>=3.6',
# https://pypi.org/pypi?%3Aaction=list_classifiers
classifiers=[
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Education',
'Intended Audience :: Developers',
'Intended Audience :: Science/Research',
'Operating System :: MacOS',
'Operating System :: Microsoft :: Windows',
'Operating System :: POSIX',
'License :: OSI Approved :: BSD License',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 3',
],

packages=packages,
# Load the built shared object files
package_data={package:
[f for f in os.listdir(package.replace('.', os.path.sep)) if os.path.splitext(f)[1] in ('.so', '.pyd')]
for package in packages
},
install_requires=[line.strip() for line in '''
Cython>=0.25.2
backports_abc>=0.5
numpy>=1.12.0
'''.splitlines() if len(line.strip()) > 0 and not line.strip().startswith('#')]
)
16 changes: 16 additions & 0 deletions example/src/greeting.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#include "greeting.h"

namespace example {

/// Print a greeting
void Greeting::sayHello() const {
std::cout << "Hello from GTSAM!" << std::endl;
std::cout << "Here's a Rot3 for you " << gtsam::Rot3() << std::endl;
}

/// Print a farewell
void Greeting::sayGoodbye() const {
std::cout << "Goodbye, robot" << std::endl;
}

};
16 changes: 16 additions & 0 deletions example/src/greeting.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#include <gtsam/geometry/Rot3.h>
#include <iostream>
#include <string>

namespace example {

class Greeting {
public:
/// Print a greeting
void sayHello() const;

/// Print a farewell
void sayGoodbye() const;
};

} // namespace example

0 comments on commit 60bed89

Please sign in to comment.