transform-tree 0.0.4__tar.gz → 0.0.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- transform_tree-0.0.6/Cargo.toml +120 -0
- {transform_tree-0.0.4/crates/tf_tree_core → transform_tree-0.0.6}/NOTICE +2 -1
- transform_tree-0.0.6/PKG-INFO +169 -0
- transform_tree-0.0.6/README.md +148 -0
- transform_tree-0.0.6/crates/tf_tree/Cargo.toml +74 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree/NOTICE +2 -1
- transform_tree-0.0.6/crates/tf_tree/README.md +104 -0
- transform_tree-0.0.6/crates/tf_tree/examples/control_loop.rs +207 -0
- transform_tree-0.0.6/crates/tf_tree/examples/gen_domain_fixture.rs +94 -0
- transform_tree-0.0.6/crates/tf_tree/examples/two_processes.rs +174 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree/src/bin/rendezvous_child.rs +165 -71
- transform_tree-0.0.6/crates/tf_tree/src/cache.rs +581 -0
- transform_tree-0.0.6/crates/tf_tree/src/cbor.rs +151 -0
- transform_tree-0.0.6/crates/tf_tree/src/frozen.rs +211 -0
- transform_tree-0.0.6/crates/tf_tree/src/lib.rs +204 -0
- transform_tree-0.0.6/crates/tf_tree/src/open.rs +1053 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree/src/tree.rs +622 -1350
- transform_tree-0.0.6/crates/tf_tree/src/unstable.rs +52 -0
- transform_tree-0.0.6/crates/tf_tree/tests/await_frames.rs +134 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree/tests/batch.rs +54 -181
- transform_tree-0.0.6/crates/tf_tree/tests/batch_phases.rs +845 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree/tests/behavior.rs +54 -144
- transform_tree-0.0.6/crates/tf_tree/tests/clock_offset.rs +308 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree/tests/common/mod.rs +9 -19
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree/tests/construction.rs +17 -66
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree/tests/counters.rs +49 -110
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree/tests/derivatives.rs +48 -228
- transform_tree-0.0.6/crates/tf_tree/tests/enumeration.rs +112 -0
- transform_tree-0.0.6/crates/tf_tree/tests/error_payloads.rs +206 -0
- transform_tree-0.0.6/crates/tf_tree/tests/feature_gates.rs +119 -0
- transform_tree-0.0.6/crates/tf_tree/tests/frozen.rs +516 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree/tests/lookup.rs +132 -15
- transform_tree-0.0.6/crates/tf_tree/tests/math_reexports.rs +92 -0
- transform_tree-0.0.6/crates/tf_tree/tests/owned_writer.rs +240 -0
- transform_tree-0.0.6/crates/tf_tree/tests/plan_cache_identity.rs +259 -0
- transform_tree-0.0.6/crates/tf_tree/tests/rendezvous.rs +4005 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree/tests/tsan.rs +5 -30
- transform_tree-0.0.6/crates/tf_tree/tests/wide_stamps.rs +172 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree_arena/NOTICE +2 -1
- transform_tree-0.0.6/crates/tf_tree_arena/README.md +45 -0
- transform_tree-0.0.6/crates/tf_tree_arena/src/check.rs +463 -0
- transform_tree-0.0.6/crates/tf_tree_arena/src/frozen.rs +939 -0
- transform_tree-0.0.6/crates/tf_tree_arena/src/header.rs +253 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree_arena/src/heap.rs +41 -137
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree_arena/src/layout.rs +108 -145
- transform_tree-0.0.6/crates/tf_tree_arena/src/lib.rs +58 -0
- transform_tree-0.0.6/crates/tf_tree_arena/src/mapped.rs +603 -0
- transform_tree-0.0.6/crates/tf_tree_arena/src/render_test.rs +67 -0
- transform_tree-0.0.6/crates/tf_tree_arena/tests/heap_alignment.rs +99 -0
- transform_tree-0.0.6/crates/tf_tree_bridge/Cargo.toml +29 -0
- transform_tree-0.0.6/crates/tf_tree_bridge/examples/offer_cost.rs +180 -0
- transform_tree-0.0.6/crates/tf_tree_bridge/src/authority.rs +347 -0
- transform_tree-0.0.6/crates/tf_tree_bridge/src/clock.rs +738 -0
- transform_tree-0.0.6/crates/tf_tree_bridge/src/config.rs +1435 -0
- transform_tree-0.0.6/crates/tf_tree_bridge/src/discover.rs +415 -0
- transform_tree-0.0.6/crates/tf_tree_bridge/src/edgeindex.rs +280 -0
- transform_tree-0.0.6/crates/tf_tree_bridge/src/edgemap.rs +34 -0
- transform_tree-0.0.6/crates/tf_tree_bridge/src/ingest.rs +2066 -0
- transform_tree-0.0.6/crates/tf_tree_bridge/src/interner.rs +142 -0
- transform_tree-0.0.6/crates/tf_tree_bridge/src/lib.rs +213 -0
- transform_tree-0.0.6/crates/tf_tree_bridge/src/names.rs +207 -0
- transform_tree-0.0.6/crates/tf_tree_bridge/src/statics.rs +483 -0
- transform_tree-0.0.6/crates/tf_tree_bridge/src/stats.rs +117 -0
- transform_tree-0.0.6/crates/tf_tree_bridge/tests/steady_state_alloc.rs +191 -0
- transform_tree-0.0.6/crates/tf_tree_core/Cargo.toml +63 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6/crates/tf_tree_core}/NOTICE +2 -1
- transform_tree-0.0.6/crates/tf_tree_core/README.md +48 -0
- transform_tree-0.0.6/crates/tf_tree_core/src/arena_view.rs +532 -0
- transform_tree-0.0.6/crates/tf_tree_core/src/bench_probe.rs +22 -0
- transform_tree-0.0.6/crates/tf_tree_core/src/buffer.rs +304 -0
- transform_tree-0.0.6/crates/tf_tree_core/src/counters.rs +139 -0
- transform_tree-0.0.6/crates/tf_tree_core/src/crash.rs +114 -0
- transform_tree-0.0.6/crates/tf_tree_core/src/crash_tests.rs +529 -0
- transform_tree-0.0.6/crates/tf_tree_core/src/edge.rs +411 -0
- transform_tree-0.0.6/crates/tf_tree_core/src/error.rs +453 -0
- transform_tree-0.0.6/crates/tf_tree_core/src/frame.rs +439 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree_core/src/layout.rs +21 -115
- transform_tree-0.0.6/crates/tf_tree_core/src/lib.rs +108 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree_core/src/loom_tests.rs +346 -334
- transform_tree-0.0.6/crates/tf_tree_core/src/participant.rs +353 -0
- transform_tree-0.0.6/crates/tf_tree_core/src/plan.rs +1977 -0
- transform_tree-0.0.6/crates/tf_tree_core/src/sample.rs +522 -0
- transform_tree-0.0.6/crates/tf_tree_core/src/sync.rs +28 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree_core/src/tests.rs +1038 -736
- transform_tree-0.0.6/crates/tf_tree_core/src/topology.rs +583 -0
- transform_tree-0.0.6/crates/tf_tree_ingest/Cargo.toml +65 -0
- transform_tree-0.0.6/crates/tf_tree_ingest/examples/gen_zstd_conformance.rs +124 -0
- transform_tree-0.0.6/crates/tf_tree_ingest/src/cdr.rs +379 -0
- transform_tree-0.0.6/crates/tf_tree_ingest/src/decompress.rs +1225 -0
- transform_tree-0.0.6/crates/tf_tree_ingest/src/fixture.rs +1292 -0
- transform_tree-0.0.6/crates/tf_tree_ingest/src/ingest.rs +1046 -0
- transform_tree-0.0.6/crates/tf_tree_ingest/src/lib.rs +315 -0
- transform_tree-0.0.6/crates/tf_tree_ingest/src/report.rs +654 -0
- transform_tree-0.0.6/crates/tf_tree_ingest/src/source.rs +622 -0
- transform_tree-0.0.6/crates/tf_tree_ingest/src/spill.rs +550 -0
- transform_tree-0.0.6/crates/tf_tree_ingest/src/tft.rs +33 -0
- transform_tree-0.0.6/crates/tf_tree_ingest/testdata/ATTRIBUTION.md +35 -0
- transform_tree-0.0.6/crates/tf_tree_ingest/testdata/zstd_conformance.mcap +0 -0
- transform_tree-0.0.6/crates/tf_tree_ingest/tests/anomaly_corpus.rs +250 -0
- transform_tree-0.0.6/crates/tf_tree_ingest/tests/codec_free.rs +108 -0
- transform_tree-0.0.6/crates/tf_tree_ingest/tests/frozen_bag.rs +85 -0
- transform_tree-0.0.6/crates/tf_tree_ingest/tests/ingest.rs +2383 -0
- transform_tree-0.0.6/crates/tf_tree_ingest/tests/memory.rs +131 -0
- transform_tree-0.0.6/crates/tf_tree_ingest/tests/record_ceiling.rs +447 -0
- transform_tree-0.0.6/crates/tf_tree_ipc/Cargo.toml +40 -0
- transform_tree-0.0.6/crates/tf_tree_ipc/NOTICE +31 -0
- transform_tree-0.0.6/crates/tf_tree_ipc/README.md +55 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree_ipc/src/bin/ipc_child.rs +12 -47
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree_ipc/src/client.rs +131 -72
- transform_tree-0.0.6/crates/tf_tree_ipc/src/error.rs +1047 -0
- transform_tree-0.0.6/crates/tf_tree_ipc/src/fork.rs +82 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree_ipc/src/identity.rs +102 -67
- transform_tree-0.0.6/crates/tf_tree_ipc/src/lib.rs +92 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree_ipc/src/lockfile.rs +128 -133
- transform_tree-0.0.6/crates/tf_tree_ipc/src/ofd.rs +161 -0
- transform_tree-0.0.6/crates/tf_tree_ipc/src/open.rs +697 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree_ipc/src/procstat.rs +90 -51
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree_ipc/src/rendezvous.rs +12 -36
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree_ipc/src/runtime_dir.rs +28 -107
- transform_tree-0.0.6/crates/tf_tree_ipc/src/server.rs +465 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree_ipc/src/wire.rs +36 -114
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree_ipc/tests/multiprocess.rs +106 -169
- transform_tree-0.0.6/crates/tf_tree_math/NOTICE +31 -0
- transform_tree-0.0.6/crates/tf_tree_math/README.md +75 -0
- transform_tree-0.0.6/crates/tf_tree_math/src/dualquat.rs +388 -0
- transform_tree-0.0.6/crates/tf_tree_math/src/interp.rs +432 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree_math/src/iso3.rs +23 -100
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree_math/src/lib.rs +5 -12
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree_math/src/quat.rs +10 -49
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree_math/src/twist.rs +23 -92
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree_math/tests/proptests.rs +16 -59
- transform_tree-0.0.6/crates/tf_tree_math/tests/slerp_public.rs +464 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree_py/Cargo.lock +232 -6
- transform_tree-0.0.6/crates/tf_tree_py/Cargo.toml +68 -0
- transform_tree-0.0.6/crates/tf_tree_py/src/errors.rs +919 -0
- transform_tree-0.0.6/crates/tf_tree_py/src/ingest.rs +114 -0
- transform_tree-0.0.6/crates/tf_tree_py/src/lib.rs +169 -0
- transform_tree-0.0.6/crates/tf_tree_py/src/offline.rs +317 -0
- transform_tree-0.0.6/crates/tf_tree_py/src/tree.rs +1645 -0
- transform_tree-0.0.6/pyproject.toml +65 -0
- transform_tree-0.0.6/python/tf_tree/__init__.py +112 -0
- transform_tree-0.0.6/python/tf_tree/_core.pyi +664 -0
- transform_tree-0.0.4/Cargo.toml +0 -231
- transform_tree-0.0.4/PKG-INFO +0 -321
- transform_tree-0.0.4/README.md +0 -300
- transform_tree-0.0.4/crates/tf_tree/Cargo.toml +0 -292
- transform_tree-0.0.4/crates/tf_tree/README.md +0 -162
- transform_tree-0.0.4/crates/tf_tree/src/cache.rs +0 -617
- transform_tree-0.0.4/crates/tf_tree/src/cbor.rs +0 -213
- transform_tree-0.0.4/crates/tf_tree/src/frozen.rs +0 -326
- transform_tree-0.0.4/crates/tf_tree/src/lib.rs +0 -372
- transform_tree-0.0.4/crates/tf_tree/src/open.rs +0 -1719
- transform_tree-0.0.4/crates/tf_tree/src/unstable.rs +0 -99
- transform_tree-0.0.4/crates/tf_tree/tests/await_frames.rs +0 -203
- transform_tree-0.0.4/crates/tf_tree/tests/enumeration.rs +0 -171
- transform_tree-0.0.4/crates/tf_tree/tests/feature_gates.rs +0 -171
- transform_tree-0.0.4/crates/tf_tree/tests/frozen.rs +0 -629
- transform_tree-0.0.4/crates/tf_tree/tests/math_reexports.rs +0 -138
- transform_tree-0.0.4/crates/tf_tree/tests/owned_writer.rs +0 -363
- transform_tree-0.0.4/crates/tf_tree/tests/plan_cache_identity.rs +0 -353
- transform_tree-0.0.4/crates/tf_tree/tests/rendezvous.rs +0 -3037
- transform_tree-0.0.4/crates/tf_tree_arena/README.md +0 -87
- transform_tree-0.0.4/crates/tf_tree_arena/src/check.rs +0 -312
- transform_tree-0.0.4/crates/tf_tree_arena/src/frozen.rs +0 -1017
- transform_tree-0.0.4/crates/tf_tree_arena/src/header.rs +0 -415
- transform_tree-0.0.4/crates/tf_tree_arena/src/lib.rs +0 -65
- transform_tree-0.0.4/crates/tf_tree_arena/src/mapped.rs +0 -870
- transform_tree-0.0.4/crates/tf_tree_arena/tests/heap_alignment.rs +0 -139
- transform_tree-0.0.4/crates/tf_tree_core/Cargo.toml +0 -69
- transform_tree-0.0.4/crates/tf_tree_core/README.md +0 -88
- transform_tree-0.0.4/crates/tf_tree_core/src/arena_view.rs +0 -688
- transform_tree-0.0.4/crates/tf_tree_core/src/bench_probe.rs +0 -57
- transform_tree-0.0.4/crates/tf_tree_core/src/buffer.rs +0 -385
- transform_tree-0.0.4/crates/tf_tree_core/src/counters.rs +0 -210
- transform_tree-0.0.4/crates/tf_tree_core/src/edge.rs +0 -551
- transform_tree-0.0.4/crates/tf_tree_core/src/error.rs +0 -338
- transform_tree-0.0.4/crates/tf_tree_core/src/frame.rs +0 -614
- transform_tree-0.0.4/crates/tf_tree_core/src/lib.rs +0 -176
- transform_tree-0.0.4/crates/tf_tree_core/src/participant.rs +0 -442
- transform_tree-0.0.4/crates/tf_tree_core/src/plan.rs +0 -2714
- transform_tree-0.0.4/crates/tf_tree_core/src/sample.rs +0 -583
- transform_tree-0.0.4/crates/tf_tree_core/src/sync.rs +0 -47
- transform_tree-0.0.4/crates/tf_tree_core/src/topology.rs +0 -820
- transform_tree-0.0.4/crates/tf_tree_ipc/Cargo.toml +0 -80
- transform_tree-0.0.4/crates/tf_tree_ipc/NOTICE +0 -30
- transform_tree-0.0.4/crates/tf_tree_ipc/README.md +0 -97
- transform_tree-0.0.4/crates/tf_tree_ipc/src/error.rs +0 -586
- transform_tree-0.0.4/crates/tf_tree_ipc/src/fork.rs +0 -161
- transform_tree-0.0.4/crates/tf_tree_ipc/src/lib.rs +0 -130
- transform_tree-0.0.4/crates/tf_tree_ipc/src/ofd.rs +0 -233
- transform_tree-0.0.4/crates/tf_tree_ipc/src/open.rs +0 -777
- transform_tree-0.0.4/crates/tf_tree_ipc/src/server.rs +0 -635
- transform_tree-0.0.4/crates/tf_tree_math/NOTICE +0 -30
- transform_tree-0.0.4/crates/tf_tree_math/README.md +0 -131
- transform_tree-0.0.4/crates/tf_tree_math/src/dualquat.rs +0 -557
- transform_tree-0.0.4/crates/tf_tree_math/src/interp.rs +0 -807
- transform_tree-0.0.4/crates/tf_tree_math/tests/slerp_public.rs +0 -654
- transform_tree-0.0.4/crates/tf_tree_py/Cargo.toml +0 -99
- transform_tree-0.0.4/crates/tf_tree_py/src/errors.rs +0 -1066
- transform_tree-0.0.4/crates/tf_tree_py/src/lib.rs +0 -296
- transform_tree-0.0.4/crates/tf_tree_py/src/offline.rs +0 -642
- transform_tree-0.0.4/crates/tf_tree_py/src/tree.rs +0 -1740
- transform_tree-0.0.4/pyproject.toml +0 -91
- transform_tree-0.0.4/python/tf_tree/__init__.py +0 -107
- transform_tree-0.0.4/python/tf_tree/_core.pyi +0 -544
- {transform_tree-0.0.4 → transform_tree-0.0.6}/LICENSE-APACHE +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/LICENSE-MIT +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree/LICENSE-APACHE +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree/LICENSE-MIT +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree_arena/Cargo.toml +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree_arena/LICENSE-APACHE +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree_arena/LICENSE-MIT +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree_core/LICENSE-APACHE +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree_core/LICENSE-MIT +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree_ipc/LICENSE-APACHE +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree_ipc/LICENSE-MIT +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree_math/Cargo.toml +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree_math/LICENSE-APACHE +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree_math/LICENSE-MIT +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/crates/tf_tree_math/src/reference.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.6}/python/tf_tree/py.typed +0 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
[workspace]
|
|
2
|
+
resolver = "2"
|
|
3
|
+
members = [
|
|
4
|
+
"crates/tf_tree_math",
|
|
5
|
+
"crates/tf_tree_arena",
|
|
6
|
+
"crates/tf_tree_core",
|
|
7
|
+
"crates/tf_tree_ipc",
|
|
8
|
+
"crates/tf_tree",
|
|
9
|
+
"crates/tf_tree_bench",
|
|
10
|
+
"crates/tf_tree_cli",
|
|
11
|
+
"crates/tf_tree_bridge",
|
|
12
|
+
"crates/tf_tree_ingest",
|
|
13
|
+
"crates/tf_tree_c",
|
|
14
|
+
"xtask",
|
|
15
|
+
]
|
|
16
|
+
# `tf_tree_tf2_sys` (ROS 2) and `tf_tree_py` (libpython) are excluded so
|
|
17
|
+
# `cargo build --workspace` works on every host.
|
|
18
|
+
exclude = [
|
|
19
|
+
"crates/tf_tree_py","crates/tf_tree_tf2_sys"]
|
|
20
|
+
|
|
21
|
+
[workspace.package]
|
|
22
|
+
# 0.0.x: every release may break every other (`SUPPORT.md`).
|
|
23
|
+
version = "0.0.6"
|
|
24
|
+
edition = "2021"
|
|
25
|
+
# 1.87 is forced by `ruzstd 0.9.0` (`tf_tree_ingest`). The `msrv` job reads this
|
|
26
|
+
# field; policy in `SUPPORT.md`.
|
|
27
|
+
rust-version = "1.87"
|
|
28
|
+
# Publishable crates symlink `LICENSE-*` and `NOTICE`; publish from Linux.
|
|
29
|
+
license = "MIT OR Apache-2.0"
|
|
30
|
+
repository = "https://github.com/NoeFontana/tf_tree"
|
|
31
|
+
homepage = "https://github.com/NoeFontana/tf_tree"
|
|
32
|
+
authors = ["Noé Fontana <noe.fontana.pro@gmail.com>"]
|
|
33
|
+
keywords = ["robotics", "tf", "transform", "se3", "ros"]
|
|
34
|
+
categories = ["science::robotics", "no-std"]
|
|
35
|
+
|
|
36
|
+
[workspace.lints.clippy]
|
|
37
|
+
unwrap_used = "deny"
|
|
38
|
+
expect_used = "deny"
|
|
39
|
+
panic = "deny"
|
|
40
|
+
todo = "deny"
|
|
41
|
+
unimplemented = "deny"
|
|
42
|
+
dbg_macro = "deny"
|
|
43
|
+
print_stdout = "warn"
|
|
44
|
+
print_stderr = "warn"
|
|
45
|
+
needless_pass_by_value = "warn"
|
|
46
|
+
redundant_clone = "warn"
|
|
47
|
+
# 0007 rule 3: every `unsafe` block has a `// SAFETY:` comment directly above it.
|
|
48
|
+
# `tf_tree_py` and `tf_tree_tf2_sys` repeat it.
|
|
49
|
+
undocumented_unsafe_blocks = "deny"
|
|
50
|
+
|
|
51
|
+
[workspace.lints.rust]
|
|
52
|
+
missing_docs = "warn"
|
|
53
|
+
unreachable_pub = "warn"
|
|
54
|
+
unused_must_use = "deny"
|
|
55
|
+
# Registers `--cfg loom` so `-D warnings` accepts it.
|
|
56
|
+
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(loom)'] }
|
|
57
|
+
|
|
58
|
+
[workspace.dependencies]
|
|
59
|
+
# Intra-workspace crates.
|
|
60
|
+
tf_tree_math = { path = "crates/tf_tree_math", version = "0.0.6" }
|
|
61
|
+
tf_tree_arena = { path = "crates/tf_tree_arena", version = "0.0.6" }
|
|
62
|
+
# `shm` (`docs/PHASE2.md` §2): `linux_raw` backend, no C build; used only by `tf_tree_ipc`.
|
|
63
|
+
libc = "0.2"
|
|
64
|
+
rustix = { version = "1.1", default-features = false, features = ["mm", "fs", "std"] }
|
|
65
|
+
# `default-features = false` is load-bearing: without it `--no-default-features`
|
|
66
|
+
# downstream still enables `counters` (`docs/PHASE5.md` §5.5).
|
|
67
|
+
tf_tree_core = { path = "crates/tf_tree_core", version = "0.0.6", default-features = false }
|
|
68
|
+
tf_tree_ipc = { path = "crates/tf_tree_ipc", version = "0.0.6" }
|
|
69
|
+
tf_tree = { path = "crates/tf_tree", version = "0.0.6", default-features = false }
|
|
70
|
+
# publish = false crates; `default-features = false` as above.
|
|
71
|
+
tf_tree_bench = { path = "crates/tf_tree_bench", default-features = false }
|
|
72
|
+
tf_tree_bridge = { path = "crates/tf_tree_bridge" }
|
|
73
|
+
# `docs/PHASE5.md` §3. The CLI decides whether the engine counts.
|
|
74
|
+
tf_tree_ingest = { path = "crates/tf_tree_ingest", default-features = false }
|
|
75
|
+
# no_std numeric + POD casting (the entire core dependency budget, plus blake3).
|
|
76
|
+
libm = "0.2"
|
|
77
|
+
bytemuck = { version = "1", features = ["derive"] }
|
|
78
|
+
blake3 = { version = "1", default-features = false }
|
|
79
|
+
# std-side facade / tooling only.
|
|
80
|
+
thiserror = "2"
|
|
81
|
+
anyhow = "1"
|
|
82
|
+
clap = { version = "4", features = ["derive"] }
|
|
83
|
+
# dev / bench only.
|
|
84
|
+
proptest = "1"
|
|
85
|
+
loom = "0.7"
|
|
86
|
+
criterion = { version = "0.5", features = ["html_reports"] }
|
|
87
|
+
serde_json = "1"
|
|
88
|
+
|
|
89
|
+
[profile.release]
|
|
90
|
+
lto = "thin"
|
|
91
|
+
codegen-units = 1
|
|
92
|
+
debug = "line-tables-only"
|
|
93
|
+
strip = "debuginfo"
|
|
94
|
+
|
|
95
|
+
[profile.dev]
|
|
96
|
+
opt-level = 0
|
|
97
|
+
|
|
98
|
+
[profile.bench]
|
|
99
|
+
lto = "thin"
|
|
100
|
+
codegen-units = 1
|
|
101
|
+
|
|
102
|
+
# Release codegen with debuginfo for profilers.
|
|
103
|
+
[profile.profiling]
|
|
104
|
+
inherits = "release"
|
|
105
|
+
debug = "full"
|
|
106
|
+
strip = "none"
|
|
107
|
+
|
|
108
|
+
# An embedder's `cargo build --release` (`docs/PHASE5.md` §9.2, `docs/API.md` §2.3):
|
|
109
|
+
# every default spelled out; `crates/tf_tree_bench/src/embed.rs` tests these values.
|
|
110
|
+
[profile.embedder]
|
|
111
|
+
inherits = "release"
|
|
112
|
+
opt-level = 3
|
|
113
|
+
lto = false
|
|
114
|
+
codegen-units = 16
|
|
115
|
+
debug = false
|
|
116
|
+
strip = "none"
|
|
117
|
+
debug-assertions = false
|
|
118
|
+
overflow-checks = false
|
|
119
|
+
incremental = false
|
|
120
|
+
panic = "unwind"
|
|
@@ -19,7 +19,8 @@ carries no third-party attributions of its own: `tf_tree` vendors no source and
|
|
|
19
19
|
statically links no code it did not write. Its Rust dependencies are ordinary
|
|
20
20
|
crates.io dependencies, each under its own licence, resolved at build time and
|
|
21
21
|
enumerated in `Cargo.lock`; the set is constrained by `deny.toml` and checked by
|
|
22
|
-
`cargo deny check
|
|
22
|
+
`cargo deny check` — the recipe `just audit`, run in CI as its own step of the
|
|
23
|
+
job named `lint`.
|
|
23
24
|
|
|
24
25
|
`tf_tree` is not affiliated with, endorsed by, or derived from the Open Source
|
|
25
26
|
Robotics Foundation, ROS, or the `tf` / `tf2` packages. It is an independent
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: transform_tree
|
|
3
|
+
Version: 0.0.6
|
|
4
|
+
Classifier: Development Status :: 3 - Alpha
|
|
5
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
6
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
8
|
+
Classifier: Programming Language :: Rust
|
|
9
|
+
Classifier: Topic :: Scientific/Engineering
|
|
10
|
+
Requires-Dist: numpy>=2.2.6
|
|
11
|
+
License-File: LICENSE-APACHE
|
|
12
|
+
License-File: LICENSE-MIT
|
|
13
|
+
License-File: NOTICE
|
|
14
|
+
Summary: A transform tree engine — faster and more scalable than ROS tf2
|
|
15
|
+
Home-Page: https://github.com/NoeFontana/tf_tree
|
|
16
|
+
Author-email: Noé Fontana <noe.fontana.pro@gmail.com>
|
|
17
|
+
License-Expression: MIT OR Apache-2.0
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
20
|
+
|
|
21
|
+
# tf_tree
|
|
22
|
+
|
|
23
|
+
[](https://crates.io/crates/tf_tree)
|
|
24
|
+
[](https://docs.rs/tf_tree)
|
|
25
|
+
[](https://pypi.org/project/transform_tree/)
|
|
26
|
+
[](https://github.com/NoeFontana/tf_tree/actions/workflows/ci.yml)
|
|
27
|
+
[](#licence)
|
|
28
|
+
|
|
29
|
+
A transform tree: it stores time-stamped poses between named frames and answers
|
|
30
|
+
"where was frame A relative to frame B at time t?". Use it in a control loop,
|
|
31
|
+
across processes on one host, or in a dataloader with no robot attached.
|
|
32
|
+
|
|
33
|
+
It is faster than ROS `tf2` (below) but is not `tf2`: no ROS dependency and no
|
|
34
|
+
drop-in `tf2_ros::Buffer`, only a one-way `/tf` ingest bridge
|
|
35
|
+
([`0008`](./docs/decisions/0008-the-name-tf-tree.md)). Linux first: shared memory
|
|
36
|
+
and frozen `.tft` files are Linux-only (`shm` feature); the in-process engine is
|
|
37
|
+
portable.
|
|
38
|
+
|
|
39
|
+
**`0.0.x` promises nothing between releases.** Pin exactly ([`CHANGELOG.md`](./CHANGELOG.md)).
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
- Rust: `cargo add tf_tree`
|
|
44
|
+
- Python: `pip install transform_tree`, then `import tf_tree`
|
|
45
|
+
- CLI (`tf_tree`, alias `tft`): a binary from the [latest release](https://github.com/NoeFontana/tf_tree/releases/latest), or `cargo install --path crates/tf_tree_cli --features shm`
|
|
46
|
+
- C ABI, C++ header, ROS 2 bridge: `just c-abi-check`, `just cpp-check`, `just ros-build`
|
|
47
|
+
|
|
48
|
+
## Quickstart
|
|
49
|
+
|
|
50
|
+
Python, from scratch (`just quickstart` runs this). Stamps are integer nanoseconds; a pose is `[qw, qx, qy, qz, x, y, z]`.
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
import tf_tree
|
|
54
|
+
|
|
55
|
+
tree = tf_tree.build([("map", "base"), ("base", "cam")])
|
|
56
|
+
tf_tree.push(tree, "base", "map", 1_000, [1, 0, 0, 0, 1.0, 2.0, 3.0])
|
|
57
|
+
tf_tree.push(tree, "base", "map", 2_000, [1, 0, 0, 0, 3.0, 4.0, 5.0])
|
|
58
|
+
|
|
59
|
+
plan = tree.plan("map", "base") # compile the route once, reuse it
|
|
60
|
+
print(plan.at(1_500)[:3, 3]) # -> [2. 3. 4.]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Rust:
|
|
64
|
+
|
|
65
|
+
```rust
|
|
66
|
+
use tf_tree::{Capacity, EdgeCfg, Iso3, Quat, Stamp, TreeBuilder, Vec3};
|
|
67
|
+
|
|
68
|
+
let tree = TreeBuilder::new()
|
|
69
|
+
.static_edge("base_link", "lidar_top", &Iso3::IDENTITY) // (parent, child)
|
|
70
|
+
.dynamic_edge("odom", "base_link", EdgeCfg::new(Capacity::history(100.0, 10.0)))
|
|
71
|
+
.build()
|
|
72
|
+
.expect("layout");
|
|
73
|
+
let odom = tree.frame("odom").expect("declared");
|
|
74
|
+
let base_link = tree.frame("base_link").expect("declared");
|
|
75
|
+
let lidar_top = tree.frame("lidar_top").expect("declared");
|
|
76
|
+
|
|
77
|
+
let w = tree.claim(base_link, odom).expect("unclaimed"); // (child, parent)
|
|
78
|
+
let at_x = |x| Iso3::new(Quat::IDENTITY, Vec3::new(x, 0.0, 0.0));
|
|
79
|
+
w.push(1_000_000_000, &at_x(0.0)).expect("monotonic");
|
|
80
|
+
w.push(1_010_000_000, &at_x(1.0)).expect("monotonic");
|
|
81
|
+
|
|
82
|
+
let plan = tree.plan(odom, lidar_top).expect("connected");
|
|
83
|
+
let g = tree.guard();
|
|
84
|
+
let t: Stamp = Stamp::from_nanos(1_005_000_000);
|
|
85
|
+
let pose = plan.at(&g, t).expect("in range");
|
|
86
|
+
assert!((pose.t.x - 0.5).abs() < 1e-12);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## From a recorded bag
|
|
90
|
+
|
|
91
|
+
MCAP only (for `.db3`, run `ros2 bag convert` first):
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
tft doctor --from-bag drive.mcap # what is wrong with this /tf traffic
|
|
95
|
+
tft freeze --from-bag drive.mcap -o drive.tft # index it
|
|
96
|
+
```
|
|
97
|
+
```python
|
|
98
|
+
tree = tf_tree.open_file("drive.tft") # an mmap; open per worker, after fork/spawn
|
|
99
|
+
poses = tree.plan("base_link", "lidar_top").at(stamps_ns, layout="quat_twist") # (N, 13)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Against `tf2`
|
|
103
|
+
|
|
104
|
+
In-process, one thread, same interpolation, `tf2` called natively; 4-core EPYC-Milan,
|
|
105
|
+
not core-pinned. Both agree to 7e-15 on 50,000 recorded queries.
|
|
106
|
+
|
|
107
|
+
| | `tf_tree` | `tf2` | |
|
|
108
|
+
|---|---|---|---|
|
|
109
|
+
| Lookup, depth 3 | 94 ns | 253 ns | 2.7× |
|
|
110
|
+
| Lookup, depth 15 | 1193 ns | 7337 ns | 6.2× |
|
|
111
|
+
| Publish | 9.4 ns | 114 ns | 12.1× |
|
|
112
|
+
|
|
113
|
+
Without LTO the lookup gap is 1.8×; through the C ABI, 1.5×. Method:
|
|
114
|
+
[`tf2.md`](./docs/benchmarks/tf2.md); sources: [`EVIDENCE.md`](./docs/benchmarks/EVIDENCE.md).
|
|
115
|
+
|
|
116
|
+
## Not for
|
|
117
|
+
|
|
118
|
+
| Need | Why | See |
|
|
119
|
+
|---|---|---|
|
|
120
|
+
| Drop-in `tf2_ros::Buffer` | gated, not scheduled | [`PHASE7.md`](./docs/PHASE7.md) |
|
|
121
|
+
| Covariance, multi-parent frames, loop closure | cut | [`0009`](./docs/decisions/0009-descoping-phase-6.md) |
|
|
122
|
+
| Transforms across hosts | not started | [`PROJECT.md`](./docs/PROJECT.md) §4 |
|
|
123
|
+
| Shared memory or `.tft` off Linux | not implemented | [`SUPPORT.md`](./SUPPORT.md) |
|
|
124
|
+
| A viewer, point-cloud deskewing | deliberately absent | [`PHASE5.md`](./docs/PHASE5.md) §8 |
|
|
125
|
+
|
|
126
|
+
## Status
|
|
127
|
+
|
|
128
|
+
| Phase | | Status |
|
|
129
|
+
|---|---|---|
|
|
130
|
+
| 1 | Engine: arena, seqlock buffers, plans, SE(3) math | Implemented |
|
|
131
|
+
| 2 | Shared memory across processes | Implemented, with gaps |
|
|
132
|
+
| 3 | Python bindings | Implemented |
|
|
133
|
+
| 4 | C ABI, C++ wrapper, ROS 2 ingest, derivatives | Implemented, with gaps |
|
|
134
|
+
| 5 | Frozen `.tft`, bag ingestion, diagnostics, `tf_tree top` | Mostly implemented |
|
|
135
|
+
| 6–8 | Continuous time, `tf2` shim, multi-host | Not started |
|
|
136
|
+
|
|
137
|
+
The `§0.0` tables in [`PHASE2`](./docs/PHASE2.md#00-implementation-status),
|
|
138
|
+
[`PHASE4`](./docs/PHASE4.md#00-implementation-status) and
|
|
139
|
+
[`PHASE5`](./docs/PHASE5.md#00-implementation-status) win over this one.
|
|
140
|
+
|
|
141
|
+
## Shared memory is not a sandbox
|
|
142
|
+
|
|
143
|
+
Processes sharing an arena trust each other: a read-write participant can corrupt
|
|
144
|
+
it ([`PHASE2.md`](./docs/PHASE2.md) §3.10; [`SECURITY.md`](./SECURITY.md)). A
|
|
145
|
+
read-only participant (the default for readers) cannot, and a crashed or hung one
|
|
146
|
+
cannot wedge the others.
|
|
147
|
+
|
|
148
|
+
`fork()` is the sharp edge: a child's inherited handles report `ChildDetached`.
|
|
149
|
+
Python's `multiprocessing` forks by default on Linux, so open the arena inside the
|
|
150
|
+
worker or use `spawn`. A frozen `.tft` survives a fork.
|
|
151
|
+
|
|
152
|
+
## Layout
|
|
153
|
+
|
|
154
|
+
`crates/`: `tf_tree_math`, `tf_tree_arena`, `tf_tree_core`, `tf_tree` (facade),
|
|
155
|
+
`tf_tree_ipc`, `tf_tree_c`, `tf_tree_bridge`, `tf_tree_ingest`, `tf_tree_py`,
|
|
156
|
+
`tf_tree_bench`, `tf_tree_tf2_sys`, `tf_tree_cli`. `ros/` holds the ROS 2
|
|
157
|
+
packages. `tf_tree_py`, `tf_tree_tf2_sys` and `ros/` sit outside the cargo
|
|
158
|
+
workspace and have their own `just` recipes. Five crates publish: `tf_tree`,
|
|
159
|
+
`tf_tree_core`, `tf_tree_math`, `tf_tree_arena`, `tf_tree_ipc`. MSRV **1.87**.
|
|
160
|
+
|
|
161
|
+
Docs: [`docs/README.md`](./docs/README.md) ·
|
|
162
|
+
[`CONTRIBUTING.md`](./CONTRIBUTING.md) · [`SUPPORT.md`](./SUPPORT.md) ·
|
|
163
|
+
[`SECURITY.md`](./SECURITY.md) · [`CODE_OF_CONDUCT.md`](./CODE_OF_CONDUCT.md)
|
|
164
|
+
|
|
165
|
+
## Licence
|
|
166
|
+
|
|
167
|
+
Dual [MIT](./LICENSE-MIT) / [Apache-2.0](./LICENSE-APACHE), at your option. See
|
|
168
|
+
[`NOTICE`](./NOTICE).
|
|
169
|
+
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# tf_tree
|
|
2
|
+
|
|
3
|
+
[](https://crates.io/crates/tf_tree)
|
|
4
|
+
[](https://docs.rs/tf_tree)
|
|
5
|
+
[](https://pypi.org/project/transform_tree/)
|
|
6
|
+
[](https://github.com/NoeFontana/tf_tree/actions/workflows/ci.yml)
|
|
7
|
+
[](#licence)
|
|
8
|
+
|
|
9
|
+
A transform tree: it stores time-stamped poses between named frames and answers
|
|
10
|
+
"where was frame A relative to frame B at time t?". Use it in a control loop,
|
|
11
|
+
across processes on one host, or in a dataloader with no robot attached.
|
|
12
|
+
|
|
13
|
+
It is faster than ROS `tf2` (below) but is not `tf2`: no ROS dependency and no
|
|
14
|
+
drop-in `tf2_ros::Buffer`, only a one-way `/tf` ingest bridge
|
|
15
|
+
([`0008`](./docs/decisions/0008-the-name-tf-tree.md)). Linux first: shared memory
|
|
16
|
+
and frozen `.tft` files are Linux-only (`shm` feature); the in-process engine is
|
|
17
|
+
portable.
|
|
18
|
+
|
|
19
|
+
**`0.0.x` promises nothing between releases.** Pin exactly ([`CHANGELOG.md`](./CHANGELOG.md)).
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
- Rust: `cargo add tf_tree`
|
|
24
|
+
- Python: `pip install transform_tree`, then `import tf_tree`
|
|
25
|
+
- CLI (`tf_tree`, alias `tft`): a binary from the [latest release](https://github.com/NoeFontana/tf_tree/releases/latest), or `cargo install --path crates/tf_tree_cli --features shm`
|
|
26
|
+
- C ABI, C++ header, ROS 2 bridge: `just c-abi-check`, `just cpp-check`, `just ros-build`
|
|
27
|
+
|
|
28
|
+
## Quickstart
|
|
29
|
+
|
|
30
|
+
Python, from scratch (`just quickstart` runs this). Stamps are integer nanoseconds; a pose is `[qw, qx, qy, qz, x, y, z]`.
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
import tf_tree
|
|
34
|
+
|
|
35
|
+
tree = tf_tree.build([("map", "base"), ("base", "cam")])
|
|
36
|
+
tf_tree.push(tree, "base", "map", 1_000, [1, 0, 0, 0, 1.0, 2.0, 3.0])
|
|
37
|
+
tf_tree.push(tree, "base", "map", 2_000, [1, 0, 0, 0, 3.0, 4.0, 5.0])
|
|
38
|
+
|
|
39
|
+
plan = tree.plan("map", "base") # compile the route once, reuse it
|
|
40
|
+
print(plan.at(1_500)[:3, 3]) # -> [2. 3. 4.]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Rust:
|
|
44
|
+
|
|
45
|
+
```rust
|
|
46
|
+
use tf_tree::{Capacity, EdgeCfg, Iso3, Quat, Stamp, TreeBuilder, Vec3};
|
|
47
|
+
|
|
48
|
+
let tree = TreeBuilder::new()
|
|
49
|
+
.static_edge("base_link", "lidar_top", &Iso3::IDENTITY) // (parent, child)
|
|
50
|
+
.dynamic_edge("odom", "base_link", EdgeCfg::new(Capacity::history(100.0, 10.0)))
|
|
51
|
+
.build()
|
|
52
|
+
.expect("layout");
|
|
53
|
+
let odom = tree.frame("odom").expect("declared");
|
|
54
|
+
let base_link = tree.frame("base_link").expect("declared");
|
|
55
|
+
let lidar_top = tree.frame("lidar_top").expect("declared");
|
|
56
|
+
|
|
57
|
+
let w = tree.claim(base_link, odom).expect("unclaimed"); // (child, parent)
|
|
58
|
+
let at_x = |x| Iso3::new(Quat::IDENTITY, Vec3::new(x, 0.0, 0.0));
|
|
59
|
+
w.push(1_000_000_000, &at_x(0.0)).expect("monotonic");
|
|
60
|
+
w.push(1_010_000_000, &at_x(1.0)).expect("monotonic");
|
|
61
|
+
|
|
62
|
+
let plan = tree.plan(odom, lidar_top).expect("connected");
|
|
63
|
+
let g = tree.guard();
|
|
64
|
+
let t: Stamp = Stamp::from_nanos(1_005_000_000);
|
|
65
|
+
let pose = plan.at(&g, t).expect("in range");
|
|
66
|
+
assert!((pose.t.x - 0.5).abs() < 1e-12);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## From a recorded bag
|
|
70
|
+
|
|
71
|
+
MCAP only (for `.db3`, run `ros2 bag convert` first):
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
tft doctor --from-bag drive.mcap # what is wrong with this /tf traffic
|
|
75
|
+
tft freeze --from-bag drive.mcap -o drive.tft # index it
|
|
76
|
+
```
|
|
77
|
+
```python
|
|
78
|
+
tree = tf_tree.open_file("drive.tft") # an mmap; open per worker, after fork/spawn
|
|
79
|
+
poses = tree.plan("base_link", "lidar_top").at(stamps_ns, layout="quat_twist") # (N, 13)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Against `tf2`
|
|
83
|
+
|
|
84
|
+
In-process, one thread, same interpolation, `tf2` called natively; 4-core EPYC-Milan,
|
|
85
|
+
not core-pinned. Both agree to 7e-15 on 50,000 recorded queries.
|
|
86
|
+
|
|
87
|
+
| | `tf_tree` | `tf2` | |
|
|
88
|
+
|---|---|---|---|
|
|
89
|
+
| Lookup, depth 3 | 94 ns | 253 ns | 2.7× |
|
|
90
|
+
| Lookup, depth 15 | 1193 ns | 7337 ns | 6.2× |
|
|
91
|
+
| Publish | 9.4 ns | 114 ns | 12.1× |
|
|
92
|
+
|
|
93
|
+
Without LTO the lookup gap is 1.8×; through the C ABI, 1.5×. Method:
|
|
94
|
+
[`tf2.md`](./docs/benchmarks/tf2.md); sources: [`EVIDENCE.md`](./docs/benchmarks/EVIDENCE.md).
|
|
95
|
+
|
|
96
|
+
## Not for
|
|
97
|
+
|
|
98
|
+
| Need | Why | See |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| Drop-in `tf2_ros::Buffer` | gated, not scheduled | [`PHASE7.md`](./docs/PHASE7.md) |
|
|
101
|
+
| Covariance, multi-parent frames, loop closure | cut | [`0009`](./docs/decisions/0009-descoping-phase-6.md) |
|
|
102
|
+
| Transforms across hosts | not started | [`PROJECT.md`](./docs/PROJECT.md) §4 |
|
|
103
|
+
| Shared memory or `.tft` off Linux | not implemented | [`SUPPORT.md`](./SUPPORT.md) |
|
|
104
|
+
| A viewer, point-cloud deskewing | deliberately absent | [`PHASE5.md`](./docs/PHASE5.md) §8 |
|
|
105
|
+
|
|
106
|
+
## Status
|
|
107
|
+
|
|
108
|
+
| Phase | | Status |
|
|
109
|
+
|---|---|---|
|
|
110
|
+
| 1 | Engine: arena, seqlock buffers, plans, SE(3) math | Implemented |
|
|
111
|
+
| 2 | Shared memory across processes | Implemented, with gaps |
|
|
112
|
+
| 3 | Python bindings | Implemented |
|
|
113
|
+
| 4 | C ABI, C++ wrapper, ROS 2 ingest, derivatives | Implemented, with gaps |
|
|
114
|
+
| 5 | Frozen `.tft`, bag ingestion, diagnostics, `tf_tree top` | Mostly implemented |
|
|
115
|
+
| 6–8 | Continuous time, `tf2` shim, multi-host | Not started |
|
|
116
|
+
|
|
117
|
+
The `§0.0` tables in [`PHASE2`](./docs/PHASE2.md#00-implementation-status),
|
|
118
|
+
[`PHASE4`](./docs/PHASE4.md#00-implementation-status) and
|
|
119
|
+
[`PHASE5`](./docs/PHASE5.md#00-implementation-status) win over this one.
|
|
120
|
+
|
|
121
|
+
## Shared memory is not a sandbox
|
|
122
|
+
|
|
123
|
+
Processes sharing an arena trust each other: a read-write participant can corrupt
|
|
124
|
+
it ([`PHASE2.md`](./docs/PHASE2.md) §3.10; [`SECURITY.md`](./SECURITY.md)). A
|
|
125
|
+
read-only participant (the default for readers) cannot, and a crashed or hung one
|
|
126
|
+
cannot wedge the others.
|
|
127
|
+
|
|
128
|
+
`fork()` is the sharp edge: a child's inherited handles report `ChildDetached`.
|
|
129
|
+
Python's `multiprocessing` forks by default on Linux, so open the arena inside the
|
|
130
|
+
worker or use `spawn`. A frozen `.tft` survives a fork.
|
|
131
|
+
|
|
132
|
+
## Layout
|
|
133
|
+
|
|
134
|
+
`crates/`: `tf_tree_math`, `tf_tree_arena`, `tf_tree_core`, `tf_tree` (facade),
|
|
135
|
+
`tf_tree_ipc`, `tf_tree_c`, `tf_tree_bridge`, `tf_tree_ingest`, `tf_tree_py`,
|
|
136
|
+
`tf_tree_bench`, `tf_tree_tf2_sys`, `tf_tree_cli`. `ros/` holds the ROS 2
|
|
137
|
+
packages. `tf_tree_py`, `tf_tree_tf2_sys` and `ros/` sit outside the cargo
|
|
138
|
+
workspace and have their own `just` recipes. Five crates publish: `tf_tree`,
|
|
139
|
+
`tf_tree_core`, `tf_tree_math`, `tf_tree_arena`, `tf_tree_ipc`. MSRV **1.87**.
|
|
140
|
+
|
|
141
|
+
Docs: [`docs/README.md`](./docs/README.md) ·
|
|
142
|
+
[`CONTRIBUTING.md`](./CONTRIBUTING.md) · [`SUPPORT.md`](./SUPPORT.md) ·
|
|
143
|
+
[`SECURITY.md`](./SECURITY.md) · [`CODE_OF_CONDUCT.md`](./CODE_OF_CONDUCT.md)
|
|
144
|
+
|
|
145
|
+
## Licence
|
|
146
|
+
|
|
147
|
+
Dual [MIT](./LICENSE-MIT) / [Apache-2.0](./LICENSE-APACHE), at your option. See
|
|
148
|
+
[`NOTICE`](./NOTICE).
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "tf_tree"
|
|
3
|
+
description = "std facade for the tf_tree transform engine: ergonomic builder, lookups, and Display errors."
|
|
4
|
+
# Per-crate crates.io front page (see `tf_tree_math`'s manifest).
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
version.workspace = true
|
|
7
|
+
edition.workspace = true
|
|
8
|
+
rust-version.workspace = true
|
|
9
|
+
license.workspace = true
|
|
10
|
+
repository.workspace = true
|
|
11
|
+
homepage.workspace = true
|
|
12
|
+
authors.workspace = true
|
|
13
|
+
keywords.workspace = true
|
|
14
|
+
# Drops the inherited `no-std`: this is the `std` facade.
|
|
15
|
+
categories = ["science::robotics"]
|
|
16
|
+
|
|
17
|
+
[lints]
|
|
18
|
+
workspace = true
|
|
19
|
+
|
|
20
|
+
[package.metadata.docs.rs]
|
|
21
|
+
all-features = true
|
|
22
|
+
rustdoc-args = ["--cfg", "docsrs"]
|
|
23
|
+
|
|
24
|
+
[features]
|
|
25
|
+
default = ["counters"]
|
|
26
|
+
# Passthrough to `tf_tree_core` (`docs/PHASE5.md` §5.5).
|
|
27
|
+
counters = ["tf_tree_core/counters"]
|
|
28
|
+
pure-hash = ["tf_tree_core/pure-hash"]
|
|
29
|
+
# `docs/PHASE2.md` §11.3's fault injection; arms `crate::CRASH_SITES` too.
|
|
30
|
+
crash-points = ["tf_tree_core/crash-points"]
|
|
31
|
+
# `TreeBuilder::build_shared` / `Tree::attach_shared`. Linux-only.
|
|
32
|
+
shm = ["tf_tree_arena/shm", "dep:tf_tree_ipc"]
|
|
33
|
+
# `docs/API.md` §6's Rust mirror of `tf_tree_unstable.h` (`docs/PHASE4.md` §3.1).
|
|
34
|
+
# Enabling is the waiver: nothing behind it is covered by semver.
|
|
35
|
+
#
|
|
36
|
+
# Enabling consumers, checked by `just stable-tier-check` against this comment and
|
|
37
|
+
# `docs/API.md` §6 row 4: `tf_tree_cli`, `tf_tree_c`, `tf_tree_bench`, `tf_tree_py`.
|
|
38
|
+
unstable = []
|
|
39
|
+
# `#[doc(hidden)]` test seams: `CLAIM_WINDOW_HOOK` (`docs/decisions/0005` §5) and
|
|
40
|
+
# `reclamation_verdict_for_test` (`docs/decisions/0028` step 2). Run by `just shm-rendezvous`.
|
|
41
|
+
test-hooks = []
|
|
42
|
+
|
|
43
|
+
[dependencies]
|
|
44
|
+
tf_tree_core.workspace = true
|
|
45
|
+
tf_tree_math.workspace = true
|
|
46
|
+
tf_tree_arena.workspace = true
|
|
47
|
+
thiserror.workspace = true
|
|
48
|
+
|
|
49
|
+
# The rendezvous (`docs/decisions/0005`); gated on `shm` and Linux.
|
|
50
|
+
[target.'cfg(target_os = "linux")'.dependencies]
|
|
51
|
+
tf_tree_ipc = { workspace = true, optional = true }
|
|
52
|
+
|
|
53
|
+
[dev-dependencies]
|
|
54
|
+
# No self dev-dependency: `cargo package` strips it. Tests gate `arena_view` call
|
|
55
|
+
# sites with `#[cfg(feature = "unstable")]` (`tests/feature_gates.rs`).
|
|
56
|
+
tf_tree_ipc = { workspace = true }
|
|
57
|
+
proptest.workspace = true
|
|
58
|
+
|
|
59
|
+
# `tests/rendezvous.rs`'s second process. Published, so the name is prefixed;
|
|
60
|
+
# `cargo install --features shm` writes it to `~/.cargo/bin` (`README.md`). It
|
|
61
|
+
# stays here because `env!("CARGO_BIN_EXE_…")` needs this package's executable.
|
|
62
|
+
[[bin]]
|
|
63
|
+
name = "tf_tree_rendezvous_child"
|
|
64
|
+
path = "src/bin/rendezvous_child.rs"
|
|
65
|
+
required-features = ["shm"]
|
|
66
|
+
|
|
67
|
+
[[test]]
|
|
68
|
+
name = "rendezvous"
|
|
69
|
+
required-features = ["shm"]
|
|
70
|
+
|
|
71
|
+
# `docs/PHASE5.md` §2; Linux-only, behind `shm`.
|
|
72
|
+
[[test]]
|
|
73
|
+
name = "frozen"
|
|
74
|
+
required-features = ["shm"]
|
|
@@ -19,7 +19,8 @@ carries no third-party attributions of its own: `tf_tree` vendors no source and
|
|
|
19
19
|
statically links no code it did not write. Its Rust dependencies are ordinary
|
|
20
20
|
crates.io dependencies, each under its own licence, resolved at build time and
|
|
21
21
|
enumerated in `Cargo.lock`; the set is constrained by `deny.toml` and checked by
|
|
22
|
-
`cargo deny check
|
|
22
|
+
`cargo deny check` — the recipe `just audit`, run in CI as its own step of the
|
|
23
|
+
job named `lint`.
|
|
23
24
|
|
|
24
25
|
`tf_tree` is not affiliated with, endorsed by, or derived from the Open Source
|
|
25
26
|
Robotics Foundation, ROS, or the `tf` / `tf2` packages. It is an independent
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# tf_tree
|
|
2
|
+
|
|
3
|
+
[](https://crates.io/crates/tf_tree)
|
|
4
|
+
[](https://docs.rs/tf_tree)
|
|
5
|
+
[](#licence)
|
|
6
|
+
|
|
7
|
+
A transform tree engine: store time-stamped rigid-body transforms between named
|
|
8
|
+
coordinate frames and answer *"where was frame A relative to frame B at time
|
|
9
|
+
t?"* — from a control loop, from many processes at once, or offline.
|
|
10
|
+
|
|
11
|
+
**This is the crate to depend on**: the `std` facade over the `no_std` engine
|
|
12
|
+
([`tf_tree_core`](https://crates.io/crates/tf_tree_core)). **It is not `tf2` and
|
|
13
|
+
not affiliated with ROS**; there is no drop-in `tf2_ros::Buffer` shim.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
cargo add tf_tree
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Shared arenas, the frozen `.tft` reader and `tf_tree::open()` are behind the
|
|
22
|
+
default-off, **Linux-only** `shm` feature: `cargo add tf_tree --features shm`.
|
|
23
|
+
Python: `pip install transform_tree`.
|
|
24
|
+
|
|
25
|
+
## In full
|
|
26
|
+
|
|
27
|
+
```rust
|
|
28
|
+
use tf_tree::{Capacity, EdgeCfg, Iso3, Quat, Stamp, TreeBuilder, Vec3};
|
|
29
|
+
|
|
30
|
+
// Topology is declared up front; nothing allocates after `build()`.
|
|
31
|
+
let tree = TreeBuilder::new()
|
|
32
|
+
.static_edge("base_link", "lidar_top", &Iso3::IDENTITY)
|
|
33
|
+
.dynamic_edge("odom", "base_link", EdgeCfg::new(Capacity::history(100.0, 10.0)))
|
|
34
|
+
.build()
|
|
35
|
+
.expect("layout");
|
|
36
|
+
|
|
37
|
+
let odom = tree.frame("odom").expect("declared");
|
|
38
|
+
let base_link = tree.frame("base_link").expect("declared");
|
|
39
|
+
let lidar_top = tree.frame("lidar_top").expect("declared");
|
|
40
|
+
|
|
41
|
+
// One writer per edge, enforced by the claim table.
|
|
42
|
+
let w = tree.claim(base_link, odom).expect("unclaimed"); // (child, parent)
|
|
43
|
+
let at_x = |x| Iso3::new(Quat::IDENTITY, Vec3::new(x, 0.0, 0.0));
|
|
44
|
+
w.push(1_000_000_000, &at_x(0.0)).expect("monotonic"); // integer nanoseconds
|
|
45
|
+
w.push(1_010_000_000, &at_x(1.0)).expect("monotonic");
|
|
46
|
+
|
|
47
|
+
// Compile the route once, evaluate it many times.
|
|
48
|
+
let plan = tree.plan(odom, lidar_top).expect("connected");
|
|
49
|
+
let g = tree.guard();
|
|
50
|
+
|
|
51
|
+
// The annotation pins the default `SystemDomain`.
|
|
52
|
+
let t: Stamp = Stamp::from_nanos(1_005_000_000);
|
|
53
|
+
match plan.at(&g, t) {
|
|
54
|
+
Ok(pose) => println!("x = {}", pose.t.x), // -> x = 0.5
|
|
55
|
+
Err(e) => println!("{}", tree.describe(e)),
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let late: Stamp = Stamp::from_nanos(3_000_000_000);
|
|
59
|
+
match plan.at(&g, late) {
|
|
60
|
+
Ok(_) => unreachable!(),
|
|
61
|
+
// -> lookup on odom->base_link (edge#2) would extrapolate:
|
|
62
|
+
// requested 3000000000 ns, history [1000000000, 1010000000] ns
|
|
63
|
+
Err(e) => println!("{}", tree.describe(e)),
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Stamps are integer nanoseconds and carry a domain in the type**; **`plan()` is
|
|
68
|
+
the object you keep** (`lookup()` by name caches one); **errors are `Copy`
|
|
69
|
+
identifiers naming the offending edge**, also `Display` and `std::error::Error`.
|
|
70
|
+
|
|
71
|
+
## Features
|
|
72
|
+
|
|
73
|
+
| Feature | Default | What it does |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| `counters` | **on** | Diagnostic counters; off does not fork the layout hash |
|
|
76
|
+
| `shm` | off | `TreeBuilder::build_shared`, `Tree::attach_shared`, `tf_tree::open()`, the frozen `.tft` reader. **Linux only** |
|
|
77
|
+
| `unstable` | off | Arena-shaped introspection (`tf_tree::unstable`, `Tree::arena_view`); **enabling it is the waiver** of semver |
|
|
78
|
+
| `test-hooks` | off | An injection point inside `Tree::claim` for the repository's tests. Not for shipped builds |
|
|
79
|
+
|
|
80
|
+
## Shared memory is not a sandbox
|
|
81
|
+
|
|
82
|
+
Processes sharing an arena are **mutually trusting, same-user, cooperating
|
|
83
|
+
processes**; a read-write participant can corrupt any part of the arena
|
|
84
|
+
([`SECURITY.md`](https://github.com/NoeFontana/tf_tree/blob/main/SECURITY.md)).
|
|
85
|
+
`fork()` is the sharp edge: a shared arena is mapped `MADV_DONTFORK`, so a child's
|
|
86
|
+
handles report `ChildDetached`; open inside the worker. A frozen `.tft` is
|
|
87
|
+
inherited intact.
|
|
88
|
+
|
|
89
|
+
## Version and docs
|
|
90
|
+
|
|
91
|
+
**`0.0.x` promises nothing**: pin exactly
|
|
92
|
+
([`CHANGELOG.md`](https://github.com/NoeFontana/tf_tree/blob/main/CHANGELOG.md)).
|
|
93
|
+
MSRV is **1.87**
|
|
94
|
+
([`SUPPORT.md`](https://github.com/NoeFontana/tf_tree/blob/main/SUPPORT.md)).
|
|
95
|
+
|
|
96
|
+
The CLI, C ABI and C++ wrapper, MCAP ingest, ROS 2 bridge and Python bindings are
|
|
97
|
+
built from source; `cargo install tf_tree` installs no command. See the repository
|
|
98
|
+
[`README.md`](https://github.com/NoeFontana/tf_tree/blob/main/README.md),
|
|
99
|
+
[`docs/PROJECT.md`](https://github.com/NoeFontana/tf_tree/blob/main/docs/PROJECT.md)
|
|
100
|
+
and [`docs/API.md`](https://github.com/NoeFontana/tf_tree/blob/main/docs/API.md).
|
|
101
|
+
|
|
102
|
+
## Licence
|
|
103
|
+
|
|
104
|
+
Dual [MIT](LICENSE-MIT) / [Apache-2.0](LICENSE-APACHE), at your option; see [`NOTICE`](NOTICE).
|