Skip to content

Commit 7efa7cd

Browse files
committed
[SymForce-External] Misc docs, CMake, and packaging fixes
- CMake now fails if reading the version or generating the manifest fails - Fixed skymarshal package classifiers - Fixes #136 - Fixes broken link to paper and broken anchors in the README - Fixes #141 - Clean up sidebar (no redundant link to home, bigger section titles) - Consolidate tutorial symlinks into one symlink to a notebooks/tutorials directory - Add READMEs for examples, do some cleanup there - Rename 2d triangulation example to 2d localization, since I think that's more fitting? I've gone ahead and republished the docs with the stylesheet fixes here, since that's just an immediate improvement IMO Closes #148 GitOrigin-RevId: a4278b2638efa1dc8a0d2f49cce04df0428b180d
1 parent 322c564 commit 7efa7cd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+289
-62
lines changed

CMakeLists.txt

+17
Original file line numberDiff line numberDiff line change
@@ -191,8 +191,16 @@ else()
191191
execute_process(
192192
COMMAND ${SYMFORCE_PYTHON} -c "from _version import version; print(version, end='')"
193193
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/symforce
194+
RESULT_VARIABLE STATUS
194195
OUTPUT_VARIABLE SYMFORCE_VERSION
196+
ERROR_VARIABLE SYMFORCE_VERSION_STDERR
195197
)
198+
if(STATUS AND NOT STATUS EQUAL 0)
199+
message(FATAL_ERROR
200+
"Failed getting symforce version with exit code ${STATUS} and output ${SYMFORCE_VERSION}, stderr ${SYMFORCE_VERSION_STDERR}"
201+
)
202+
endif()
203+
196204
file(WRITE
197205
${CMAKE_CURRENT_BINARY_DIR}/lcmtypes/python2.7/setup.py
198206
"
@@ -290,7 +298,16 @@ if(SYMFORCE_GENERATE_MANIFEST)
290298
--binary_output_dir "${CMAKE_BINARY_DIR}"
291299
# TODO(aaron): Put this somewhere smarter
292300
--manifest_path ${CMAKE_CURRENT_SOURCE_DIR}/build/manifest.json
301+
RESULT_VARIABLE STATUS
302+
OUTPUT_VARIABLE GENERATE_MANIFEST_OUTPUT
303+
ERROR_VARIABLE GENERATE_MANIFEST_OUTPUT
293304
)
305+
306+
if(STATUS AND NOT STATUS EQUAL 0)
307+
message(FATAL_ERROR
308+
"Failed generating manifest with exit code ${STATUS} and output ${GENERATE_MANIFEST_OUTPUT}"
309+
)
310+
endif()
294311
endfunction()
295312

296313
generate_manifest()

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ PY_EXTENSION_MODULE_PATH?=$(BUILD_DIR)/pybind
168168

169169
docs_html: docs_apidoc
170170
export PYTHONPATH=$(PY_EXTENSION_MODULE_PATH):$(PYTHONPATH); \
171-
SYMFORCE_LOGLEVEL=WARNING $(PYTHON) -m sphinx -b html docs $(DOCS_DIR) -j $$(nproc)
171+
SYMFORCE_LOGLEVEL=WARNING $(PYTHON) -m sphinx -b html docs $(DOCS_DIR) -j auto
172172
mkdir $(DOCS_DIR)/docs
173173
ln -s ../_static $(DOCS_DIR)/docs/static
174174

README.md

+9-9
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ SymForce is developed and maintained by [Skydio](https://skydio.com/). It is use
4545
+ Embedded-friendly C++ generation of templated Eigen code with zero dynamic memory allocation
4646
+ Highly performant, modular, tested, and extensible code
4747

48-
### Read the paper: https://arxiv.org/abs/2204.07889
48+
### Read the paper: <a href="https://arxiv.org/abs/2204.07889">https://arxiv.org/abs/2204.07889</a>
4949

5050
SymForce was published to [RSS 2022](https://roboticsconference.org/). Please cite it as follows:
5151

@@ -73,7 +73,7 @@ Verify the installation in Python:
7373
>>> geo.Rot3()
7474
```
7575

76-
This installs pre-compiled C++ components of SymForce on Linux and Mac using pip wheels, but does not include C++ headers. If you want to compile against C++ SymForce types (like `sym::Optimizer`), you currently need to [build from source](#build-from-source).
76+
This installs pre-compiled C++ components of SymForce on Linux and Mac using pip wheels, but does not include C++ headers. If you want to compile against C++ SymForce types (like `sym::Optimizer`), you currently need to <a href="#build-from-source">build from source</a>.
7777

7878
# Tutorial
7979

@@ -86,7 +86,7 @@ The robot measures:
8686

8787
The robot's heading angle is defined counter-clockwise from the x-axis, and its relative bearing measurements are defined from the robot's forward direction:
8888

89-
<img alt="Robot 2D Triangulation Figure" src="docs/static/images/robot_2d_triangulation/robot_2d_triangulation_figure.png" width="350px"/>
89+
<img alt="Robot 2D Localization Figure" src="docs/static/images/robot_2d_localization/problem_setup.png" width="350px"/>
9090

9191
## Explore the math
9292

@@ -228,7 +228,7 @@ for i in range(num_poses - 1):
228228

229229
Here is a visualization of the structure of this factor graph:
230230

231-
<img alt="Robot 2D Triangulation Factor Graph" src="docs/static/images/robot_2d_triangulation/robot_2d_triangulation_factor_graph.png" width="600px"/>
231+
<img alt="Robot 2D Localization Factor Graph" src="docs/static/images/robot_2d_localization/factor_graph.png" width="600px"/>
232232

233233
## Solve the problem
234234

@@ -271,12 +271,12 @@ Let's visualize what the optimizer did. The orange circles represent the fixed l
271271
circles represent the robot, and the dotted lines represent the bearing measurements.
272272

273273
```python
274-
from symforce.examples.robot_2d_triangulation.plotting import plot_solution
274+
from symforce.examples.robot_2d_localization.plotting import plot_solution
275275
plot_solution(optimizer, result)
276276
```
277-
<img alt="Robot 2D Triangulation Solution" src="docs/static/images/robot_2d_triangulation/robot_2d_triangulation_iterations.gif" width="600px"/>
277+
<img alt="Robot 2D Localization Solution" src="docs/static/images/robot_2d_localization/iterations.gif" width="600px"/>
278278

279-
All of the code for this example can also be found in `symforce/examples/robot_2d_triangulation`.
279+
All of the code for this example can also be found in `symforce/examples/robot_2d_localization`.
280280

281281
## Symbolic vs Numerical Types
282282

@@ -520,7 +520,7 @@ To learn more, visit the SymForce tutorials [here](https://symforce.org/#guides)
520520

521521
# Build from Source
522522

523-
SymForce requires Python 3.8 or later. We strongly suggest creating a virtual python environment.
523+
SymForce requires Python 3.8 or later. The build is currently tested on Linux and macOS, SymForce on Windows is untested (see [#145](https://github.com/symforce-org/symforce/issues/145)). We strongly suggest creating a virtual python environment.
524524

525525
Install the `gmp` package with one of:
526526
```bash
@@ -540,7 +540,7 @@ The recommended way to build and install SymForce if you only plan on making Pyt
540540
pip install -e .
541541
```
542542

543-
This will build the C++ components of SymForce, but you won't be able to run `pip install -e .` repeatedly if you need to rebuild C++ code. If you're changing C++ code and rebuilding, you should build with CMake directly as described [below](#build-with-cmake).
543+
This will build the C++ components of SymForce, but you won't be able to run `pip install -e .` repeatedly if you need to rebuild C++ code. If you're changing C++ code and rebuilding, you should build with CMake directly as described <a href="#build-with-cmake">below</a>.
544544

545545
`pip install .` will not install pinned versions of SymForce's dependencies, it'll install any compatible versions. It also won't install all packages required to run all of the SymForce tests and build all of the targets (e.g. building the docs or running the linters). If you want all packages required for that, you should `pip install .[dev]` instead (or one of the other groups of extra requirements in our `setup.py`). If you additionally want pinned versions of our dependencies, which are the exact versions guaranteed by CI to pass all of our tests, you can install them from `pip install -r dev_requirements.txt`.
546546

docs/conf.py

+4
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@
5959
"myst_parser",
6060
]
6161

62+
# This doesn't seem to fix things, but would be good to fix - currently you have to manually write
63+
# anchor links in html
64+
myst_heading_anchors = 3
65+
6266
# Add any paths that contain templates here, relative to this directory.
6367
templates_path = ["_templates"]
6468

docs/development.rst

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
Development
2-
===========
1+
Development Guide
2+
=================
3+
34
Guide for how to build, configure, and develop SymForce.
45

56
.. contents:: :local:

docs/examples

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../symforce/examples

docs/index.rst

+34-28
Original file line numberDiff line numberDiff line change
@@ -4,56 +4,62 @@ SymForce Home
44
.. include:: ../README.md
55
:parser: myst_parser.sphinx_
66

7-
.. toctree::
8-
:caption: Pages
9-
:hidden:
10-
11-
self
12-
development
13-
14-
.. toctree::
15-
:caption: Tutorials
16-
:hidden:
17-
18-
notebooks/sympy_tutorial
19-
notebooks/geometry_tutorial
20-
notebooks/ops_tutorial
21-
notebooks/cameras_tutorial
22-
notebooks/values_tutorial
23-
notebooks/codegen_tutorial
24-
notebooks/optimization_tutorial
25-
notebooks/epsilon_tutorial
26-
277
Guides
288
======
299

3010
:doc:`development`
3111
How to build, configure, and develop
3212

33-
:doc:`notebooks/sympy_tutorial`
13+
:doc:`tutorials/sympy_tutorial`
3414
Basic introduction to SymPy
3515

36-
:doc:`notebooks/geometry_tutorial`
16+
:doc:`tutorials/geometry_tutorial`
3717
Introductory guide to doing math and geometry
3818

39-
:doc:`notebooks/ops_tutorial`
19+
:doc:`tutorials/ops_tutorial`
4020
Introductory guide to using Ops in symforce
4121

42-
:doc:`notebooks/cameras_tutorial`
22+
:doc:`tutorials/cameras_tutorial`
4323
Introductory guide to using camera models
4424

45-
:doc:`notebooks/values_tutorial`
25+
:doc:`tutorials/values_tutorial`
4626
How to structure large groups of symbols and expressions
4727

48-
:doc:`notebooks/codegen_tutorial`
28+
:doc:`tutorials/codegen_tutorial`
4929
How to generate functions from symbolic expressions
5030

51-
:doc:`notebooks/optimization_tutorial`
31+
:doc:`tutorials/optimization_tutorial`
5232
Basic example of using generated code to do optimization
5333

54-
:doc:`notebooks/epsilon_tutorial`
34+
:doc:`tutorials/epsilon_tutorial`
5535
Guide to how Epsilon is used to prevent singularities
5636

37+
.. toctree::
38+
:hidden:
39+
40+
development
41+
42+
.. toctree::
43+
:caption: Tutorials
44+
:hidden:
45+
46+
tutorials/sympy_tutorial
47+
tutorials/geometry_tutorial
48+
tutorials/ops_tutorial
49+
tutorials/cameras_tutorial
50+
tutorials/values_tutorial
51+
tutorials/codegen_tutorial
52+
tutorials/optimization_tutorial
53+
tutorials/epsilon_tutorial
54+
55+
.. toctree::
56+
:caption: Examples
57+
:hidden:
58+
:glob:
59+
:titlesonly:
60+
61+
examples/*/README
62+
5763
.. _api-reference:
5864
.. toctree::
5965
:hidden:

docs/notebooks/cameras_tutorial.ipynb

-1
This file was deleted.

docs/notebooks/codegen_tutorial.ipynb

-1
This file was deleted.

docs/notebooks/epsilon_tutorial.ipynb

-1
This file was deleted.

docs/notebooks/geometry_tutorial.ipynb

-1
This file was deleted.

docs/notebooks/ops_tutorial.ipynb

-1
This file was deleted.

docs/notebooks/optimization_tutorial.ipynb

-1
This file was deleted.

docs/notebooks/sympy_tutorial.ipynb

-1
This file was deleted.

docs/notebooks/values_tutorial.ipynb

-1
This file was deleted.

docs/static/css/custom.css

+14
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,17 @@ section#symforce-home h1 {
1010
div.sphinxsidebarwrapper h3:first-of-type {
1111
display: none;
1212
}
13+
14+
/**
15+
* Fix for:
16+
* https://github.com/symforce-org/symforce/issues/141
17+
* https://github.com/bitprophet/alabaster/issues/181
18+
*/
19+
dl.py {
20+
margin-bottom: 15px;
21+
}
22+
23+
div.sphinxsidebarwrapper p.caption span.caption-text {
24+
font-size: 140%;
25+
font-weight: bold;
26+
}

docs/tutorials

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../notebooks/tutorials
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
Bundle Adjustment
2+
=================
3+
4+
[Source on GitHub](https://github.com/symforce-org/symforce/tree/main/symforce/examples/bundle_adjustment)
5+
6+
This example demonstrates bundle adjustment of camera extrinsics and landmarks using factors built into SymForce. This particular example is set up so that the number of cameras and landmarks is unknown at code generation time and can be changed at runtime; in contrast, the `Fixed Size Bundle Adjustment` example shows how fixing the number of cameras or landmarks at codegen time can be more efficient.
7+
8+
We randomly generate a set of camera poses and feature correspondences with noise and some outliers, and perform bundle adjustment with feature reprojection residuals and pose priors.
9+
10+
## Files:
11+
12+
### `build_example_state.*`:
13+
14+
Utilities for building up the problem, by randomly sampling camera poses and feature correspondences, and perturbing initial guesses. This is all returned in the form of a `Values` produced by the `BuildValues` function.
15+
16+
### `run_bundle_adjustment.cc`
17+
18+
This is the C++ file that actually runs the optimization. It builds up the `Values` for the problem, builds a factor graph, and performs bundle adjustment. See the comments there for more information.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
Fixed Size Bundle Adjustment
2+
============================
3+
4+
[Source on GitHub](https://github.com/symforce-org/symforce/tree/main/symforce/examples/bundle_adjustment_fixed_size)
5+
6+
This example demonstrates bundle adjustment of camera extrinsics and landmarks using factors built into SymForce. This particular example is set up so that the number of cameras and landmarks is set at code generation time; in contrast, the `Bundle Adjustment` example shows how to make them configurable at runtime. Fixing the size of the problem at generation time can produce significantly more efficient linearization functions, because common subexpression elimination can be applied across multiple factors. For instance, multiple factors that reproject different features into the same cameras will often share computation.
7+
8+
We randomly generate a set of camera poses and feature correspondences with noise and some outliers, and perform bundle adjustment with feature reprojection residuals and pose priors.
9+
10+
## Files:
11+
12+
### `build_example_state.*`:
13+
14+
Utilities for building up the problem, by randomly sampling camera poses and feature correspondences, and perturbing initial guesses. This is all returned in the form of a `Values` produced by the `BuildValues` function.
15+
16+
### `build_values.py`:
17+
18+
Builds a symbolic Python `Values` with all of the variables in the problem. This is used to build up the symbolic problem in `generate_fixed_problem.py`.
19+
20+
### `generate_fixed_problem.py`:
21+
22+
This actually defines the fixed-size problem, taking the `Values` built by `build_values.py` and constructing all of the residuals. We can then generate the entire problem into C++, with common subexpression elimination running across the entire problem together. The `FixedBundleAdjustmentProblem.generate` method is called by `test/symforce_examples_bundle_adjustment_fixed_size_codegen_test.py` to actually generate the linearization function in `gen`.
23+
24+
### `run_bundle_adjustment.cc`
25+
26+
This is the C++ file that actually runs the optimization. It builds up the `Values` for the problem and builds a factor graph. In this example, the C++ optimization consists of one `sym::Factor`, with a single generated linearization function that contains all of the symbolic residuals.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
Bundle-Adjustment-in-the-Large
2+
======================================
3+
4+
[Source on GitHub](https://github.com/symforce-org/symforce/tree/main/symforce/examples/bundle_adjustment_in_the_large)
5+
6+
This example demonstrates bundle adjustment of camera extrinsics and intrinsics, as well as 3D landmark positions, for a Structure-from-Motion problem. The example isn't particularly optimized for performance, but demonstrates the simplest way to set this up with SymForce.
7+
8+
We use the Bundle-Adjustment-in-the-Large dataset, as described here: https://grail.cs.washington.edu/projects/bal/
9+
10+
Feature correspondences have already been selected, and we're given initial guesses for all of the variables; our only task is to perform bundle adjustment.
11+
12+
The camera model is a simple polynomial model, and each image is assumed to be captured by a different camera with its own intrinsics.
13+
14+
Ceres and GTSAM also have reference implementations for this dataset, see [here](https://github.com/ceres-solver/ceres-solver/blob/master/examples/simple_bundle_adjuster.cc) for Ceres and [here](https://github.com/devbharat/gtsam/blob/master/examples/SFMExample_bal.cpp) for GTSAM.
15+
16+
## Files:
17+
18+
### `download_dataset.py`:
19+
20+
Script to download the dataset files into the `./data` folder, run this first if you'd like to run the example
21+
22+
### `bundle_adjustment_in_the_large.py`
23+
24+
Defines the symbolic residual function for the reprojection error factor, and a function to generate the symbolic factor into C++. The `generate` function is called by `symforce/test/symforce_examples_bundle_adjustment_in_the_large_codegen_test.py` to generate everything in the `gen` directory.
25+
26+
### `bundle_adjustment_in_the_large.cc`
27+
28+
This is the C++ file that actually runs the optimization. It loads a dataset, builds a factor graph,
29+
and performs bundle adjustment. See the comments there for more information.

symforce/examples/bundle_adjustment_in_the_large/bundle_adjustment_in_the_large.cc

+18
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,14 @@
1616

1717
using namespace sym::Keys;
1818

19+
/**
20+
* Create a `sym::Factor` for the reprojection residual, attached to the given camera and point
21+
* variables. It's also attached to fixed entries in the Values for the pixel measurement and the
22+
* constant EPSILON.
23+
*/
1924
sym::Factord MakeFactor(int camera, int point, int pixel) {
2025
return sym::Factord::Hessian(sym::SnavelyReprojectionFactor<double>,
26+
/* all_keys = */
2127
{
2228
sym::Key::WithSuper(CAM_T_WORLD, camera),
2329
sym::Key::WithSuper(INTRINSICS, camera),
@@ -33,6 +39,9 @@ sym::Factord MakeFactor(int camera, int point, int pixel) {
3339
});
3440
}
3541

42+
/**
43+
* A struct to represent the problem definition
44+
*/
3645
struct Problem {
3746
std::vector<sym::Factord> factors;
3847
sym::Valuesd values;
@@ -102,14 +111,23 @@ Problem ReadProblem(const std::string& filename) {
102111
return {std::move(factors), std::move(values), num_cameras, num_points, num_observations};
103112
}
104113

114+
/**
115+
* Example usage: `bundle_adjustment_in_the_large_example data/problem-21-11315-pre.txt`
116+
*/
105117
int main(int argc, char** argv) {
106118
sym::internal::SetLogLevel("info");
107119

108120
SYM_ASSERT(argc == 2);
121+
122+
// Read the problem from disk, and create the Values and factors
109123
const auto problem = ReadProblem(argv[1]);
110124

125+
// Create a copy of the Values - we'll optimize this one in place
111126
sym::Valuesd optimized_values = problem.values;
127+
128+
// Optimize
112129
sym::Optimizerd optimizer{sym::DefaultOptimizerParams(), std::move(problem.factors)};
113130
const auto stats = optimizer.Optimize(&optimized_values);
131+
114132
spdlog::info("Finished in {} iterations", stats.iterations.size());
115133
}

0 commit comments

Comments
 (0)