diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index b73f44f..4657454 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -25,8 +25,6 @@ jobs:
- uses: Swatinem/rust-cache@v2
- uses: astral-sh/setup-uv@v9.0.0
- run: sudo apt-get update && sudo apt-get install -y doxygen
- - run: uv tool install zensical
- - run: echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- run: docs/build.sh build
- uses: actions/upload-pages-artifact@v5
with:
diff --git a/README.md b/README.md
index 92dc532..5bf74f3 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@ QuickIK provides high-level APIs for processing consecutive frames (i.e. with wa
See the [docs site](https://nely-epfl.github.io/quickik/) for installation, usage examples, and benchmarks against KDL, Pinocchio, and RBDL.
-Not yet published to crates.io or PyPI – see [`docs/installation.md`](docs/installation.md) for building from a local clone.
+Not yet published to crates.io or PyPI – see [`docs/getting-started/installation.md`](docs/getting-started/installation.md) for building from a local clone.
## For developers
@@ -21,8 +21,8 @@ cd devtools-pyenv && uv sync && source .venv/bin/activate
### Build
- **Rust core**: `cargo build` (add `--workspace` to also build the `python`/`cpp` bindings crates and the `benchmark/` crates).
-- **Python bindings**: `cd python && maturin develop --release` – see [`docs/installation.md`](docs/installation.md) for prerequisites.
-- **C++ bindings**: CMake, from `cpp/` – see [`docs/installation.md`](docs/installation.md).
+- **Python bindings**: `cd python && maturin develop --release` – see [`docs/getting-started/installation.md`](docs/getting-started/installation.md) for prerequisites.
+- **C++ bindings**: CMake, from `cpp/` – see [`docs/getting-started/installation.md`](docs/getting-started/installation.md).
### Lint/format
diff --git a/benchmark/README.md b/benchmark/README.md
index 9b52b4e..47c749b 100644
--- a/benchmark/README.md
+++ b/benchmark/README.md
@@ -1,6 +1,6 @@
# QuickIK benchmark
-Compares QuickIK's IK solve speed against KDL, Pinocchio, and RBDL, across QuickIK's Rust API, Python bindings, and C++ bindings, on two bodies: NeuroMechFly (a fly) and G1 (a Unitree humanoid). See the [Benchmarks docs page](../docs/benchmarks.md) for what's being compared, why, and the current results – this file only covers how to reproduce them.
+Compares QuickIK's IK solve speed against KDL, Pinocchio, and RBDL, across QuickIK's Rust API, Python bindings, and C++ bindings, on two bodies: NeuroMechFly (a fly) and G1 (a Unitree humanoid). See the [Benchmarks docs page](../docs/technical/benchmarks.md) for what's being compared, why, and the current results – this file only covers how to reproduce them.
## Running it
diff --git a/benchmark/assets/g1_body_plan.json b/benchmark/assets/g1_body_plan.json
index 4005d40..d2aa2e2 100644
--- a/benchmark/assets/g1_body_plan.json
+++ b/benchmark/assets/g1_body_plan.json
@@ -1,7 +1,6 @@
{
- "metadata": {
- "name": "g1_29dof"
- },
+ "fixed_base": false,
+ "x-name": "g1_29dof",
"joints": [
{
"name": "pelvis",
diff --git a/benchmark/assets/neuromechfly_ypr_legs.json b/benchmark/assets/neuromechfly_ypr_legs.json
index ccdc52f..c9e964c 100644
--- a/benchmark/assets/neuromechfly_ypr_legs.json
+++ b/benchmark/assets/neuromechfly_ypr_legs.json
@@ -1 +1 @@
-{"metadata": {"name": "neuromechfly_ypr_legs"}, "joints": [{"name": "thorax", "parent": null, "offset_pos": [0.0, 0.0, 0.0], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": []}, {"name": "lf_thorax_coxa", "parent": "thorax", "offset_pos": [-0.161, 0.172, -0.23], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "c_thorax-lf_coxa-yaw", "axis": [1.0, 0.0, 0.0], "type": "hinge", "neutral": -0.29670597283903605, "limits": null}, {"name": "c_thorax-lf_coxa-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": 0.33161255787892263, "limits": null}, {"name": "c_thorax-lf_coxa-roll", "axis": [0.0, 0.0, 1.0], "type": "hinge", "neutral": 0.5759586531581288, "limits": null}]}, {"name": "lf_coxa_trochanterfemur", "parent": "lf_thorax_coxa", "offset_pos": [-0.00181, -0.00172, -0.365], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "lf_coxa-lf_trochanterfemur-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": -2.426007660272118, "limits": null}, {"name": "lf_coxa-lf_trochanterfemur-roll", "axis": [0.0, 0.0, 1.0], "type": "hinge", "neutral": -0.2617993877991494, "limits": null}]}, {"name": "lf_trochanterfemur_tibia", "parent": "lf_coxa_trochanterfemur", "offset_pos": [-0.00465, -0.00149, -0.705], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "lf_trochanterfemur-lf_tibia-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": 1.361356816555577, "limits": null}]}, {"name": "lf_tibia_tarsus", "parent": "lf_trochanterfemur_tibia", "offset_pos": [-0.000144, -0.000952, -0.518], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "lf_tibia-lf_tarsus1-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": -0.17453292519943295, "limits": null}]}, {"name": "lf_claw", "parent": "lf_tibia_tarsus", "offset_pos": [0.09203502219396992, 0.010583907142921345, -0.6712937582145876], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": []}, {"name": "lm_thorax_coxa", "parent": "thorax", "offset_pos": [-0.557, 0.125, -0.482], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "c_thorax-lm_coxa-yaw", "axis": [1.0, 0.0, 0.0], "type": "hinge", "neutral": -0.17453292519943295, "limits": null}, {"name": "c_thorax-lm_coxa-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": 0.13962634015954636, "limits": null}, {"name": "c_thorax-lm_coxa-roll", "axis": [0.0, 0.0, 1.0], "type": "hinge", "neutral": 1.5184364492350666, "limits": null}]}, {"name": "lm_coxa_trochanterfemur", "parent": "lm_thorax_coxa", "offset_pos": [-0.000216, 0.000149, -0.181], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "lm_coxa-lm_trochanterfemur-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": -2.426007660272118, "limits": null}, {"name": "lm_coxa-lm_trochanterfemur-roll", "axis": [0.0, 0.0, 1.0], "type": "hinge", "neutral": 0.2617993877991494, "limits": null}]}, {"name": "lm_trochanterfemur_tibia", "parent": "lm_coxa_trochanterfemur", "offset_pos": [-0.000971, 5.52e-05, -0.784], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "lm_trochanterfemur-lm_tibia-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": 1.7976891295541595, "limits": null}]}, {"name": "lm_tibia_tarsus", "parent": "lm_trochanterfemur_tibia", "offset_pos": [6.58e-05, -0.00037, -0.667], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "lm_tibia-lm_tarsus1-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": -0.3490658503988659, "limits": null}]}, {"name": "lm_claw", "parent": "lm_tibia_tarsus", "offset_pos": [0.07429188105829865, 0.03035037624127261, -0.709561608388346], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": []}, {"name": "lh_thorax_coxa", "parent": "thorax", "offset_pos": [-0.784, 0.087, -0.497], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "c_thorax-lh_coxa-yaw", "axis": [1.0, 0.0, 0.0], "type": "hinge", "neutral": 0.05235987755982989, "limits": null}, {"name": "c_thorax-lh_coxa-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": 0.4188790204786391, "limits": null}, {"name": "c_thorax-lh_coxa-roll", "axis": [0.0, 0.0, 1.0], "type": "hinge", "neutral": 2.3387411976724017, "limits": null}]}, {"name": "lh_coxa_trochanterfemur", "parent": "lh_thorax_coxa", "offset_pos": [-0.000177, -0.000227, -0.199], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "lh_coxa-lh_trochanterfemur-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": -1.9373154697137058, "limits": null}, {"name": "lh_coxa-lh_trochanterfemur-roll", "axis": [0.0, 0.0, 1.0], "type": "hinge", "neutral": 0.06981317007977318, "limits": null}]}, {"name": "lh_trochanterfemur_tibia", "parent": "lh_coxa_trochanterfemur", "offset_pos": [0.000108, 0.000294, -0.836], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "lh_trochanterfemur-lh_tibia-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": 1.7627825445142729, "limits": null}]}, {"name": "lh_tibia_tarsus", "parent": "lh_trochanterfemur_tibia", "offset_pos": [-8.94e-07, -0.000324, -0.684], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "lh_tibia-lh_tarsus1-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": -0.08726646259971647, "limits": null}]}, {"name": "lh_claw", "parent": "lh_tibia_tarsus", "offset_pos": [0.07590329969089399, 0.026866855276183835, -0.7977379203801962], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": []}, {"name": "rf_thorax_coxa", "parent": "thorax", "offset_pos": [-0.161, -0.172, -0.23], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "c_thorax-rf_coxa-yaw", "axis": [-1.0, 0.0, 0.0], "type": "hinge", "neutral": -0.29670597283903605, "limits": null}, {"name": "c_thorax-rf_coxa-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": 0.33161255787892263, "limits": null}, {"name": "c_thorax-rf_coxa-roll", "axis": [0.0, 0.0, -1.0], "type": "hinge", "neutral": 0.5759586531581288, "limits": null}]}, {"name": "rf_coxa_trochanterfemur", "parent": "rf_thorax_coxa", "offset_pos": [-0.00181, 0.00172, -0.365], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "rf_coxa-rf_trochanterfemur-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": -2.426007660272118, "limits": null}, {"name": "rf_coxa-rf_trochanterfemur-roll", "axis": [0.0, 0.0, -1.0], "type": "hinge", "neutral": -0.2617993877991494, "limits": null}]}, {"name": "rf_trochanterfemur_tibia", "parent": "rf_coxa_trochanterfemur", "offset_pos": [-0.00465, 0.00149, -0.705], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "rf_trochanterfemur-rf_tibia-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": 1.361356816555577, "limits": null}]}, {"name": "rf_tibia_tarsus", "parent": "rf_trochanterfemur_tibia", "offset_pos": [-0.000144, 0.000952, -0.518], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "rf_tibia-rf_tarsus1-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": -0.17453292519943295, "limits": null}]}, {"name": "rf_claw", "parent": "rf_tibia_tarsus", "offset_pos": [0.09203502219396992, -0.010583907142921345, -0.6712937582145876], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": []}, {"name": "rm_thorax_coxa", "parent": "thorax", "offset_pos": [-0.557, -0.125, -0.482], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "c_thorax-rm_coxa-yaw", "axis": [-1.0, 0.0, 0.0], "type": "hinge", "neutral": -0.17453292519943295, "limits": null}, {"name": "c_thorax-rm_coxa-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": 0.13962634015954636, "limits": null}, {"name": "c_thorax-rm_coxa-roll", "axis": [0.0, 0.0, -1.0], "type": "hinge", "neutral": 1.5184364492350666, "limits": null}]}, {"name": "rm_coxa_trochanterfemur", "parent": "rm_thorax_coxa", "offset_pos": [-0.000216, -0.000149, -0.181], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "rm_coxa-rm_trochanterfemur-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": -2.426007660272118, "limits": null}, {"name": "rm_coxa-rm_trochanterfemur-roll", "axis": [0.0, 0.0, -1.0], "type": "hinge", "neutral": 0.2617993877991494, "limits": null}]}, {"name": "rm_trochanterfemur_tibia", "parent": "rm_coxa_trochanterfemur", "offset_pos": [-0.000971, -5.52e-05, -0.784], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "rm_trochanterfemur-rm_tibia-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": 1.7976891295541595, "limits": null}]}, {"name": "rm_tibia_tarsus", "parent": "rm_trochanterfemur_tibia", "offset_pos": [6.58e-05, 0.00037, -0.667], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "rm_tibia-rm_tarsus1-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": -0.3490658503988659, "limits": null}]}, {"name": "rm_claw", "parent": "rm_tibia_tarsus", "offset_pos": [0.07429188105829866, -0.03035037624127261, -0.709561608388346], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": []}, {"name": "rh_thorax_coxa", "parent": "thorax", "offset_pos": [-0.784, -0.087, -0.497], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "c_thorax-rh_coxa-yaw", "axis": [-1.0, 0.0, 0.0], "type": "hinge", "neutral": 0.05235987755982989, "limits": null}, {"name": "c_thorax-rh_coxa-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": 0.4188790204786391, "limits": null}, {"name": "c_thorax-rh_coxa-roll", "axis": [0.0, 0.0, -1.0], "type": "hinge", "neutral": 2.3387411976724017, "limits": null}]}, {"name": "rh_coxa_trochanterfemur", "parent": "rh_thorax_coxa", "offset_pos": [-0.000177, 0.000227, -0.199], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "rh_coxa-rh_trochanterfemur-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": -1.9373154697137058, "limits": null}, {"name": "rh_coxa-rh_trochanterfemur-roll", "axis": [0.0, 0.0, -1.0], "type": "hinge", "neutral": 0.06981317007977318, "limits": null}]}, {"name": "rh_trochanterfemur_tibia", "parent": "rh_coxa_trochanterfemur", "offset_pos": [0.000108, -0.000294, -0.836], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "rh_trochanterfemur-rh_tibia-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": 1.7627825445142729, "limits": null}]}, {"name": "rh_tibia_tarsus", "parent": "rh_trochanterfemur_tibia", "offset_pos": [-8.94e-07, 0.000324, -0.684], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "rh_tibia-rh_tarsus1-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": -0.08726646259971647, "limits": null}]}, {"name": "rh_claw", "parent": "rh_tibia_tarsus", "offset_pos": [0.07590329969089399, -0.026866855276183835, -0.7977379203801962], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": []}]}
\ No newline at end of file
+{"fixed_base": false, "x-name": "neuromechfly_ypr_legs", "joints": [{"name": "thorax", "parent": null, "offset_pos": [0.0, 0.0, 0.0], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": []}, {"name": "lf_thorax_coxa", "parent": "thorax", "offset_pos": [-0.161, 0.172, -0.23], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "c_thorax-lf_coxa-yaw", "axis": [1.0, 0.0, 0.0], "type": "hinge", "neutral": -0.29670597283903605, "limits": null}, {"name": "c_thorax-lf_coxa-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": 0.33161255787892263, "limits": null}, {"name": "c_thorax-lf_coxa-roll", "axis": [0.0, 0.0, 1.0], "type": "hinge", "neutral": 0.5759586531581288, "limits": null}]}, {"name": "lf_coxa_trochanterfemur", "parent": "lf_thorax_coxa", "offset_pos": [-0.00181, -0.00172, -0.365], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "lf_coxa-lf_trochanterfemur-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": -2.426007660272118, "limits": null}, {"name": "lf_coxa-lf_trochanterfemur-roll", "axis": [0.0, 0.0, 1.0], "type": "hinge", "neutral": -0.2617993877991494, "limits": null}]}, {"name": "lf_trochanterfemur_tibia", "parent": "lf_coxa_trochanterfemur", "offset_pos": [-0.00465, -0.00149, -0.705], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "lf_trochanterfemur-lf_tibia-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": 1.361356816555577, "limits": null}]}, {"name": "lf_tibia_tarsus", "parent": "lf_trochanterfemur_tibia", "offset_pos": [-0.000144, -0.000952, -0.518], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "lf_tibia-lf_tarsus1-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": -0.17453292519943295, "limits": null}]}, {"name": "lf_claw", "parent": "lf_tibia_tarsus", "offset_pos": [0.09203502219396992, 0.010583907142921345, -0.6712937582145876], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": []}, {"name": "lm_thorax_coxa", "parent": "thorax", "offset_pos": [-0.557, 0.125, -0.482], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "c_thorax-lm_coxa-yaw", "axis": [1.0, 0.0, 0.0], "type": "hinge", "neutral": -0.17453292519943295, "limits": null}, {"name": "c_thorax-lm_coxa-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": 0.13962634015954636, "limits": null}, {"name": "c_thorax-lm_coxa-roll", "axis": [0.0, 0.0, 1.0], "type": "hinge", "neutral": 1.5184364492350666, "limits": null}]}, {"name": "lm_coxa_trochanterfemur", "parent": "lm_thorax_coxa", "offset_pos": [-0.000216, 0.000149, -0.181], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "lm_coxa-lm_trochanterfemur-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": -2.426007660272118, "limits": null}, {"name": "lm_coxa-lm_trochanterfemur-roll", "axis": [0.0, 0.0, 1.0], "type": "hinge", "neutral": 0.2617993877991494, "limits": null}]}, {"name": "lm_trochanterfemur_tibia", "parent": "lm_coxa_trochanterfemur", "offset_pos": [-0.000971, 5.52e-05, -0.784], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "lm_trochanterfemur-lm_tibia-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": 1.7976891295541595, "limits": null}]}, {"name": "lm_tibia_tarsus", "parent": "lm_trochanterfemur_tibia", "offset_pos": [6.58e-05, -0.00037, -0.667], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "lm_tibia-lm_tarsus1-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": -0.3490658503988659, "limits": null}]}, {"name": "lm_claw", "parent": "lm_tibia_tarsus", "offset_pos": [0.07429188105829865, 0.03035037624127261, -0.709561608388346], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": []}, {"name": "lh_thorax_coxa", "parent": "thorax", "offset_pos": [-0.784, 0.087, -0.497], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "c_thorax-lh_coxa-yaw", "axis": [1.0, 0.0, 0.0], "type": "hinge", "neutral": 0.05235987755982989, "limits": null}, {"name": "c_thorax-lh_coxa-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": 0.4188790204786391, "limits": null}, {"name": "c_thorax-lh_coxa-roll", "axis": [0.0, 0.0, 1.0], "type": "hinge", "neutral": 2.3387411976724017, "limits": null}]}, {"name": "lh_coxa_trochanterfemur", "parent": "lh_thorax_coxa", "offset_pos": [-0.000177, -0.000227, -0.199], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "lh_coxa-lh_trochanterfemur-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": -1.9373154697137058, "limits": null}, {"name": "lh_coxa-lh_trochanterfemur-roll", "axis": [0.0, 0.0, 1.0], "type": "hinge", "neutral": 0.06981317007977318, "limits": null}]}, {"name": "lh_trochanterfemur_tibia", "parent": "lh_coxa_trochanterfemur", "offset_pos": [0.000108, 0.000294, -0.836], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "lh_trochanterfemur-lh_tibia-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": 1.7627825445142729, "limits": null}]}, {"name": "lh_tibia_tarsus", "parent": "lh_trochanterfemur_tibia", "offset_pos": [-8.94e-07, -0.000324, -0.684], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "lh_tibia-lh_tarsus1-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": -0.08726646259971647, "limits": null}]}, {"name": "lh_claw", "parent": "lh_tibia_tarsus", "offset_pos": [0.07590329969089399, 0.026866855276183835, -0.7977379203801962], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": []}, {"name": "rf_thorax_coxa", "parent": "thorax", "offset_pos": [-0.161, -0.172, -0.23], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "c_thorax-rf_coxa-yaw", "axis": [-1.0, 0.0, 0.0], "type": "hinge", "neutral": -0.29670597283903605, "limits": null}, {"name": "c_thorax-rf_coxa-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": 0.33161255787892263, "limits": null}, {"name": "c_thorax-rf_coxa-roll", "axis": [0.0, 0.0, -1.0], "type": "hinge", "neutral": 0.5759586531581288, "limits": null}]}, {"name": "rf_coxa_trochanterfemur", "parent": "rf_thorax_coxa", "offset_pos": [-0.00181, 0.00172, -0.365], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "rf_coxa-rf_trochanterfemur-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": -2.426007660272118, "limits": null}, {"name": "rf_coxa-rf_trochanterfemur-roll", "axis": [0.0, 0.0, -1.0], "type": "hinge", "neutral": -0.2617993877991494, "limits": null}]}, {"name": "rf_trochanterfemur_tibia", "parent": "rf_coxa_trochanterfemur", "offset_pos": [-0.00465, 0.00149, -0.705], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "rf_trochanterfemur-rf_tibia-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": 1.361356816555577, "limits": null}]}, {"name": "rf_tibia_tarsus", "parent": "rf_trochanterfemur_tibia", "offset_pos": [-0.000144, 0.000952, -0.518], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "rf_tibia-rf_tarsus1-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": -0.17453292519943295, "limits": null}]}, {"name": "rf_claw", "parent": "rf_tibia_tarsus", "offset_pos": [0.09203502219396992, -0.010583907142921345, -0.6712937582145876], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": []}, {"name": "rm_thorax_coxa", "parent": "thorax", "offset_pos": [-0.557, -0.125, -0.482], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "c_thorax-rm_coxa-yaw", "axis": [-1.0, 0.0, 0.0], "type": "hinge", "neutral": -0.17453292519943295, "limits": null}, {"name": "c_thorax-rm_coxa-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": 0.13962634015954636, "limits": null}, {"name": "c_thorax-rm_coxa-roll", "axis": [0.0, 0.0, -1.0], "type": "hinge", "neutral": 1.5184364492350666, "limits": null}]}, {"name": "rm_coxa_trochanterfemur", "parent": "rm_thorax_coxa", "offset_pos": [-0.000216, -0.000149, -0.181], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "rm_coxa-rm_trochanterfemur-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": -2.426007660272118, "limits": null}, {"name": "rm_coxa-rm_trochanterfemur-roll", "axis": [0.0, 0.0, -1.0], "type": "hinge", "neutral": 0.2617993877991494, "limits": null}]}, {"name": "rm_trochanterfemur_tibia", "parent": "rm_coxa_trochanterfemur", "offset_pos": [-0.000971, -5.52e-05, -0.784], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "rm_trochanterfemur-rm_tibia-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": 1.7976891295541595, "limits": null}]}, {"name": "rm_tibia_tarsus", "parent": "rm_trochanterfemur_tibia", "offset_pos": [6.58e-05, 0.00037, -0.667], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "rm_tibia-rm_tarsus1-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": -0.3490658503988659, "limits": null}]}, {"name": "rm_claw", "parent": "rm_tibia_tarsus", "offset_pos": [0.07429188105829866, -0.03035037624127261, -0.709561608388346], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": []}, {"name": "rh_thorax_coxa", "parent": "thorax", "offset_pos": [-0.784, -0.087, -0.497], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "c_thorax-rh_coxa-yaw", "axis": [-1.0, 0.0, 0.0], "type": "hinge", "neutral": 0.05235987755982989, "limits": null}, {"name": "c_thorax-rh_coxa-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": 0.4188790204786391, "limits": null}, {"name": "c_thorax-rh_coxa-roll", "axis": [0.0, 0.0, -1.0], "type": "hinge", "neutral": 2.3387411976724017, "limits": null}]}, {"name": "rh_coxa_trochanterfemur", "parent": "rh_thorax_coxa", "offset_pos": [-0.000177, 0.000227, -0.199], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "rh_coxa-rh_trochanterfemur-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": -1.9373154697137058, "limits": null}, {"name": "rh_coxa-rh_trochanterfemur-roll", "axis": [0.0, 0.0, -1.0], "type": "hinge", "neutral": 0.06981317007977318, "limits": null}]}, {"name": "rh_trochanterfemur_tibia", "parent": "rh_coxa_trochanterfemur", "offset_pos": [0.000108, -0.000294, -0.836], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "rh_trochanterfemur-rh_tibia-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": 1.7627825445142729, "limits": null}]}, {"name": "rh_tibia_tarsus", "parent": "rh_trochanterfemur_tibia", "offset_pos": [-8.94e-07, 0.000324, -0.684], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": [{"name": "rh_tibia-rh_tarsus1-pitch", "axis": [0.0, 1.0, 0.0], "type": "hinge", "neutral": -0.08726646259971647, "limits": null}]}, {"name": "rh_claw", "parent": "rh_tibia_tarsus", "offset_pos": [0.07590329969089399, -0.026866855276183835, -0.7977379203801962], "offset_quat": [1.0, 0.0, 0.0, 0.0], "dofs": []}]}
diff --git a/benchmark/extern/kdl/README.md b/benchmark/extern/kdl/README.md
index 0a13728..3e3f7f9 100644
--- a/benchmark/extern/kdl/README.md
+++ b/benchmark/extern/kdl/README.md
@@ -1,6 +1,6 @@
# KDL benchmark
-Benchmarks Orocos KDL's tree-based inverse kinematics against QuickIK, on both bodies (see `../../README.md`). See the [Benchmarks docs page](../../../docs/benchmarks.md) for KDL's modeling compromises and results; `bench_kdl.cpp`'s header comment has the full write-up.
+Benchmarks Orocos KDL's tree-based inverse kinematics against QuickIK, on both bodies (see `../../README.md`). See the [Benchmarks docs page](../../../docs/technical/benchmarks.md) for KDL's modeling compromises and results; `bench_kdl.cpp`'s header comment has the full write-up.
## Build
diff --git a/benchmark/extern/kdl/bench_kdl.cpp b/benchmark/extern/kdl/bench_kdl.cpp
index 38178d2..331e5a5 100644
--- a/benchmark/extern/kdl/bench_kdl.cpp
+++ b/benchmark/extern/kdl/bench_kdl.cpp
@@ -396,23 +396,12 @@ void write_results_json(const std::string &body, double single_frame_latency_us,
"uses simple contiguous chunking (8 independent, "
"internally-warm-started, externally-cold-started chunks), not "
"QuickIK's overlap-stitched segmented solve, since KDL has no "
- "parallel solve path to mirror. single_frame_latency_max_us forces "
- "max_iter=10 (not this file's own kMaxIter=100 ceiling) so the "
- "forced-worst-case number is comparable to quickik/RBDL/Pinocchio's, "
- "which all cap at 10.";
- if (body == "g1") {
- notes +=
- " Unlike the fly, G1's step-size early-stop NEVER triggers within "
- "kMaxIter=100 -- every single_frame_latency_us call burns the full "
- "100 iterations, landing at a real but looser ~3-4mm position "
- "residual (vs. the fly's ~10-micron near-exact fit), which is why "
- "single_frame_latency_us (100 real iterations) is larger than "
- "single_frame_latency_max_us (only 10 forced iterations) for this "
- "body -- backwards from every other body/library combination in "
- "this comparison, and not a bug: it reflects KDL's own actual "
- "default behavior being worse than the shared 10-iteration cap on "
- "this harder problem, not an inconsistency in the measurement.";
- }
+ "parallel solve path to mirror. Both single_frame_latency_us and "
+ "single_frame_latency_max_us force max_iter=10 (not this file's own "
+ "kMaxIter=100 ceiling, which only bounds the warm-started throughput "
+ "sequence below) so both are comparable to quickik/RBDL/Pinocchio's, "
+ "which also cap at 10; only step_tol differs (kStepTol vs. 0, i.e. "
+ "early stopping allowed vs. forced to the full 10 iterations).";
out << "{\n"
<< " \"name\": \"kdl\",\n"
<< " \"body\": \"" << body << "\",\n"
@@ -457,13 +446,17 @@ void run_body(const BodyConfig &body, const std::filesystem::path &assets_dir) {
auto target = to_vec3s(fixtures["synthetic_frames"][0]["target_ego"]);
Frames target_frames = build_target_frames(sb.model, target);
+ // max_iter is forced to 10 here (not this file's own kMaxIter=100) to
+ // match QuickIK/RBDL/Pinocchio's shared iteration cap -- early stopping
+ // (step_tol=kStepTol, the default) still applies within that budget.
std::printf("-- single-frame time (latency) --\n");
- double single_frame_latency_us = summarize(
- "CartToJnt() (cold)", bench_single_frame_latency(sb, q_neutral, target_frames, kLatencyNCalls, 500));
+ double single_frame_latency_us =
+ summarize("CartToJnt() (cold)",
+ bench_single_frame_latency(sb, q_neutral, target_frames, kLatencyNCalls, 500, /*max_iter=*/10));
- // step_tol=0 disables early stopping; max_iter is forced to 10 here (not
- // this file's own kMaxIter=100) to match QuickIK/RBDL/Pinocchio's shared
- // iteration cap -- the worst case if a frame never converges early.
+ // step_tol=0 additionally disables early stopping, forcing every solve to
+ // run the full 10 iterations -- the worst case if a frame never converges
+ // early.
std::printf("\n-- single-frame time (latency), early stop disabled (10 iterations) --\n");
double single_frame_latency_max_us =
summarize("CartToJnt() (forced max iterations)",
diff --git a/benchmark/extern/pinocchio/README.md b/benchmark/extern/pinocchio/README.md
index a8a4a10..1522cbc 100644
--- a/benchmark/extern/pinocchio/README.md
+++ b/benchmark/extern/pinocchio/README.md
@@ -1,6 +1,6 @@
# Pinocchio benchmark
-Benchmarks [Pinocchio](https://github.com/stack-of-tasks/pinocchio) against QuickIK, on both bodies (see `../../README.md`). Methodology mirrors `../../quickik_python/bench.py` and `../../quickik_rust/src/perf.rs` exactly (same fixtures, same metrics, same config values) so the numbers are directly comparable. See the [Benchmarks docs page](../../../docs/benchmarks.md) for Pinocchio's modeling compromises and results.
+Benchmarks [Pinocchio](https://github.com/stack-of-tasks/pinocchio) against QuickIK, on both bodies (see `../../README.md`). Methodology mirrors `../../quickik_python/bench.py` and `../../quickik_rust/src/perf.rs` exactly (same fixtures, same metrics, same config values) so the numbers are directly comparable. See the [Benchmarks docs page](../../../docs/technical/benchmarks.md) for Pinocchio's modeling compromises and results.
## Running
diff --git a/benchmark/extern/rbdl/README.md b/benchmark/extern/rbdl/README.md
index 023ac71..97b02a9 100644
--- a/benchmark/extern/rbdl/README.md
+++ b/benchmark/extern/rbdl/README.md
@@ -1,6 +1,6 @@
# RBDL benchmark
-Benchmarks RBDL's core-library `InverseKinematicsConstraintSet` (not an addon – see `rbdl-src/include/rbdl/Kinematics.h`) against QuickIK, on both bodies (see `../../README.md`). See the [Benchmarks docs page](../../../docs/benchmarks.md) for RBDL's modeling compromises and results; `bench_rbdl.cpp`'s header comment has the full write-up. `leg_poc.cpp` is the earlier one-leg proof of concept this was built up from.
+Benchmarks RBDL's core-library `InverseKinematicsConstraintSet` (not an addon – see `rbdl-src/include/rbdl/Kinematics.h`) against QuickIK, on both bodies (see `../../README.md`). See the [Benchmarks docs page](../../../docs/technical/benchmarks.md) for RBDL's modeling compromises and results; `bench_rbdl.cpp`'s header comment has the full write-up. `leg_poc.cpp` is the earlier one-leg proof of concept this was built up from.
## Build
diff --git a/benchmark/plot/plot_comparison.py b/benchmark/plot/plot_comparison.py
index 7727892..0f4bae4 100644
--- a/benchmark/plot/plot_comparison.py
+++ b/benchmark/plot/plot_comparison.py
@@ -79,16 +79,6 @@
},
]
-# Metric key -> the library whose bar, in *both* bodies, should be drawn
-# capped at CAP_MULTIPLE-x the next-highest bar's value (the max across
-# every other library's bar in either body) instead of running out to its
-# true value, which would flatten every other bar down near zero on a
-# linear axis -- see `draw_body_bars`. Only KDL's mean-latency result is
-# extreme enough (up to ~60x the next-highest bar, for g1) to need this;
-# everywhere else a plain, uncapped bar is clearer.
-CAPPED_METRIC_BARS = {"single_frame_latency_us": "kdl"}
-CAP_MULTIPLE = 2
-
# Explicit display order (not alphabetical): language variants of the same
# library grouped together, fastest-per-library first.
ORDER = [
@@ -155,7 +145,14 @@ def load_results():
data = json.loads(path.read_text())
# quickik-scaling.json is a list of weak-scaling data points (see
# plot_scaling.py), not a single-library result -- skip it here.
- if isinstance(data, dict):
+ # errors-
.json is plot_2d_comparison.py's own per-frame
+ # fit-residual distribution data (no "name"/"formulation" keys),
+ # not a per-library result either -- skip that too. So is
+ # quickik-rust-2d-xyview-.json (an "observation" override of
+ # quickik-rust's own 3D result, also only used by
+ # plot_2d_comparison.py) -- without this it'd show up here as a
+ # second, indistinguishable "quickik-rust" row.
+ if isinstance(data, dict) and "name" in data and "observation" not in data:
all_results.append(data)
included = [r for r in all_results if r.get("formulation") == "whole-tree"]
excluded = [r for r in all_results if r not in included]
@@ -303,24 +300,6 @@ def register_fonts():
BAR_OFFSET = 0.20
BAR_HEIGHT = 0.40
-# A capped bar's "keeps going" dashes past its tip (see draw_body_bars):
-# DASH_COUNT blocks, each DASH_WIDTH*cap_length wide, separated by
-# DASH_GAP*cap_length (also used as the gap right after the bar's tip).
-# Sized so the whole run -- DASH_GAP + DASH_COUNT*DASH_WIDTH +
-# (DASH_COUNT-1)*DASH_GAP, as a fraction of cap_length -- lands comfortably
-# inside XLIM_PAD's headroom past the widest bar.
-DASH_COUNT = 5
-DASH_WIDTH = 0.012
-DASH_GAP = 0.01
-
-
-def dash_positions(cap_length):
- """Left-edge x-positions for DASH_COUNT dashes continuing a capped bar
- of length `cap_length` past its own tip."""
- step = (DASH_WIDTH + DASH_GAP) * cap_length
- first = cap_length + DASH_GAP * cap_length
- return [first + i * step for i in range(DASH_COUNT)]
-
def draw_body_bars(
ax,
@@ -332,37 +311,18 @@ def draw_body_bars(
unit,
small_unit,
color,
- cap_lib=None,
- cap_length=None,
):
"""Draws one body's bars (one per library in `lib_names`, at the matching
`y_positions`) on `ax`, skipping libraries with no value for `key`, and
annotates each with its value (see `format_value`) just past its tip --
plain "xxx unit", no parens.
- If `cap_lib` names one of `lib_names` and `cap_length` is given, that
- bar is instead drawn at the (already-scaled) `cap_length` rather than
- its true length -- since running it out to its true value would flatten
- every other bar down near zero on a linear axis. The caller computes
- `cap_length` once per metric (shared across both bodies -- see
- CAPPED_METRIC_BARS), so both bodies' bars for `cap_lib` end up the same
- length even though their true values differ. That one bar's true value
- plus "(capped in chart)" is written inside it, and a few small
- same-color, same-height blocks continue past its tip (see
- dash_positions()) -- a dashed-line cue that the bar keeps going -- in
- place of the plain label every other bar gets there. Those blocks are
- deliberately excluded from this function's returned widest extent (they
- size themselves off of it, via dash_positions(cap_length)), so they land
- inside the caller's XLIM_PAD headroom rather than pushing it out further.
-
Every QuickIK bar is drawn at QUICKIK_ALPHA with its value label bold;
every other library's bar gets OTHER_ALPHA and a plain-weight label.
Returns the widest x-extent actually drawn (unpadded, so the caller
decides how much room to leave past it), or 0.0 if no library had data.
"""
- from matplotlib.patches import Rectangle
-
present = [
(y, lib, results_by_name[lib][key] * scale)
for y, lib in zip(y_positions, lib_names, strict=True)
@@ -373,44 +333,14 @@ def draw_body_bars(
ys = [y for y, _, _ in present]
values = [v for _, _, v in present]
- display_values = list(values)
- cap_idx = next((i for i, (_, lib, _) in enumerate(present) if lib == cap_lib), None)
- if cap_idx is not None and cap_length is not None:
- display_values[cap_idx] = cap_length
- bars = ax.barh(ys, display_values, height=BAR_HEIGHT, color=color, zorder=3)
- for i, (bar, (_, lib, _)) in enumerate(zip(bars, present, strict=True)):
+ bars = ax.barh(ys, values, height=BAR_HEIGHT, color=color, zorder=3)
+ for bar, (_, lib, value) in zip(bars, present, strict=True):
is_quickik = lib.startswith("quickik-")
bar.set_alpha(QUICKIK_ALPHA if is_quickik else OTHER_ALPHA)
weight = "bold" if is_quickik else "normal"
- text = format_value(values[i], unit, small_unit)
- if i == cap_idx and cap_length is not None:
- ax.annotate(
- f"{text} (capped in chart)",
- (bar.get_width(), bar.get_y() + bar.get_height() / 2),
- xytext=(-4, 0),
- fontsize=TEXT_NUMBER_FONTSIZE,
- fontweight=weight,
- textcoords="offset points",
- ha="right",
- va="center",
- zorder=4,
- )
- for dash_x in dash_positions(cap_length):
- ax.add_patch(
- Rectangle(
- (dash_x, bar.get_y()),
- DASH_WIDTH * cap_length,
- bar.get_height(),
- facecolor=color,
- alpha=bar.get_alpha(),
- edgecolor="none",
- zorder=3,
- )
- )
- continue
ax.annotate(
- text,
+ format_value(value, unit, small_unit),
(bar.get_width(), bar.get_y() + bar.get_height() / 2),
xytext=(4, 0),
fontsize=TEXT_NUMBER_FONTSIZE,
@@ -420,7 +350,7 @@ def draw_body_bars(
va="center",
zorder=4,
)
- return max(display_values)
+ return max(values)
def plot_chart(results_by_body):
@@ -483,18 +413,6 @@ def plot_chart(results_by_body):
metric["scale"],
)
- cap_lib = CAPPED_METRIC_BARS.get(key)
- cap_length = None
- if cap_lib:
- other_values = [
- r[key]
- for by_name in (nmf_by_name, g1_by_name)
- for name, r in by_name.items()
- if name != cap_lib and r.get(key) is not None
- ]
- if other_values:
- cap_length = CAP_MULTIPLE * max(other_values) * scale
-
nmf_ys = [y + BAR_OFFSET for y in group_ys]
g1_ys = [y - BAR_OFFSET for y in group_ys]
widest_nmf = draw_body_bars(
@@ -507,8 +425,6 @@ def plot_chart(results_by_body):
unit,
small_unit,
NEUROMECHFLY_COLOR,
- cap_lib,
- cap_length,
)
widest_g1 = draw_body_bars(
ax,
@@ -520,8 +436,6 @@ def plot_chart(results_by_body):
unit,
small_unit,
G1_COLOR,
- cap_lib,
- cap_length,
)
widest = max(widest_nmf, widest_g1)
if widest == 0.0:
@@ -546,10 +460,6 @@ def plot_chart(results_by_body):
label.set_fontweight("bold")
ax.set_ylim(-0.5, len(ordered_libs) - 1 + 0.5)
despine(ax)
- # Even a capped bar's tip gets the usual padding now: its "..."
- # label past the tip (see draw_body_bars) needs the room, and the
- # in-bar "(capped in chart)" text already marks it as truncated, so
- # a flush-to-the-edge tip is no longer the only cue for that.
ax.set_xlim(0, widest * XLIM_PAD)
legend_handles = [
diff --git a/benchmark/plot/render_video.py b/benchmark/plot/render_video.py
index 6de46f2..b9a7bdc 100644
--- a/benchmark/plot/render_video.py
+++ b/benchmark/plot/render_video.py
@@ -235,7 +235,7 @@ def solve_sequence(tree, fixtures, missing_keypoints=(), weight=None):
config = (
quickik.SolverConfig()
if weight is None
- else quickik.SolverConfig(weight=weight)
+ else quickik.SolverConfig(neutral_weight=weight)
)
seq = quickik.SequenceSolver(tree, config)
return [
diff --git a/benchmark/plot/render_video_2d.py b/benchmark/plot/render_video_2d.py
index 6fa8b19..3e2990b 100644
--- a/benchmark/plot/render_video_2d.py
+++ b/benchmark/plot/render_video_2d.py
@@ -95,7 +95,7 @@ def solve_sequence_xyview(tree, fixtures):
"""Warm-started XYView solve, mirroring `render_video.solve_sequence` but
with a `quickik.XYView()` mapper, 2D observations, and a stronger
neutral-pose prior -- see `WEIGHT_2D_XYVIEW`."""
- config = quickik.SolverConfig(weight=WEIGHT_2D_XYVIEW)
+ config = quickik.SolverConfig(neutral_weight=WEIGHT_2D_XYVIEW)
seq = quickik.SequenceSolver(tree, config, mapper=quickik.XYView())
return [
seq.solve_frame(build_observations_2d_xyview(f["target_ego"]))
@@ -325,7 +325,7 @@ def bones(key, color, width, alpha):
body["lo"][2],
"X-Y projection",
size=x_range * 0.06,
- color="0.4",
+ color="0.7",
)
return artists
diff --git a/benchmark/plot/results/comparison-2d.svg b/benchmark/plot/results/comparison-2d.svg
index 7a94b55..ae14701 100644
--- a/benchmark/plot/results/comparison-2d.svg
+++ b/benchmark/plot/results/comparison-2d.svg
@@ -6,7 +6,7 @@
- 2026-07-25T03:40:12.084112
+ 2026-07-27T01:01:04.649962
image/svg+xml
@@ -41,12 +41,12 @@ z
-
-
+
@@ -56,100 +56,110 @@ L 0 3.5
-
+
- 0.01
+ 0.01
-
+
- 0.02
+ 0.02
-
+
- 0.03
+ 0.03
-
+
- 0.04
+ 0.04
-
+
- 0.05
+ 0.05
-
+
- 0.06
+ 0.06
-
+
- 0.07
+ 0.07
-
+
+
+
+
+
+
+
+ 0.08
+
+
+
Latency (ms)
-
+
-
-
+
-
+
3D
-
+
-
+
-
+
XYView
@@ -170,24 +180,24 @@ L 339.01875 73.334767
L 339.01875 60.609353
L 47.41875 60.609353
z
-" clip-path="url(#p384aec4d75)" style="fill: #4051b5"/>
+" clip-path="url(#p1ec19649fb)" style="fill: #4051b5"/>
-
-
- Mean single-frame latency (with early stopping)
+" clip-path="url(#p1ec19649fb)" style="fill: #2fb170"/>
- 66.3 μs
+ Mean single-frame latency (with early stopping)
- 57.8 μs
+ 66.8 μs
+
+
+ 58.6 μs
@@ -200,108 +210,108 @@ z
" style="fill: #ffffff"/>
-
-
-
-
-
-
-
- 0.00
-
-
-
+
- 0.02
+ 0.00
-
+
- 0.04
+ 0.02
-
+
- 0.06
+ 0.04
-
+
- 0.08
+ 0.06
-
+
- 0.10
+ 0.08
-
+
- 0.12
+ 0.10
-
+
- 0.14
+ 0.12
+
+
+
+
+
+
+
+
+
+ 0.14
-
+
Latency (ms)
-
+
-
+
-
+
3D
-
+
-
+
-
+
XYView
@@ -322,24 +332,24 @@ L 339.01875 176.410617
L 339.01875 163.685203
L 47.41875 163.685203
z
-" clip-path="url(#p653fc14074)" style="fill: #4051b5"/>
+" clip-path="url(#p6bb2df0dac)" style="fill: #4051b5"/>
-
-
- Single-frame latency (fixed 10 iterations)
+" clip-path="url(#p6bb2df0dac)" style="fill: #2fb170"/>
- 132 μs
+ Single-frame latency (fixed 10 iterations)
- 114 μs
+ 133 μs
+
+
+ 115 μs
@@ -352,118 +362,118 @@ z
" style="fill: #ffffff"/>
-
-
-
-
-
-
-
- 0.0
-
-
-
+
- 2.5
+ 0.0
-
+
- 5.0
+ 2.5
-
+
- 7.5
+ 5.0
-
+
- 10.0
+ 7.5
-
+
- 12.5
+ 10.0
-
+
- 15.0
+ 12.5
-
+
- 17.5
+ 15.0
-
+
- 20.0
+ 17.5
-
+
+
+
+
+
+
+
+ 20.0
+
+
+
Throughput (kFPS)
-
+
-
+
-
+
3D
-
+
-
+
-
+
XYView
@@ -480,11 +490,11 @@ L 397.33875 295.393233
+" clip-path="url(#pdde0a735f9)" style="fill: #4051b5"/>
-
-
- Throughput (sequential frames, single thread)
+" clip-path="url(#pdde0a735f9)" style="fill: #2fb170"/>
- 11.1 kFPS
+ Throughput (sequential frames, single thread)
- 17.4 kFPS
+ 10.9 kFPS
+
+
+ 18.2 kFPS
@@ -514,118 +524,98 @@ z
" style="fill: #ffffff"/>
-
-
-
-
-
-
-
- 0
-
-
-
+
- 10
+ 0
-
+
- 20
+ 20
-
+
- 30
+ 40
-
+
- 40
+ 60
-
+
- 50
+ 80
-
+
- 60
+ 100
-
+
- 70
+ 120
-
-
-
-
-
-
-
- 80
-
-
-
+
Throughput (kFPS)
-
+
-
+
-
+
3D
-
+
-
+
-
+
XYView
@@ -642,11 +632,11 @@ L 397.33875 398.469083
+" clip-path="url(#pbd9577dced)" style="fill: #4051b5"/>
+" clip-path="url(#pbd9577dced)" style="fill: #2fb170"/>
-
+
Throughput (sequential frames, 8 threads)
-
- 52.9 kFPS
+
+ 73.5 kFPS
-
- 71.5 kFPS
+
+ 101 kFPS
@@ -676,148 +666,148 @@ z
" style="fill: #ffffff"/>
+
+
+
+
+
+
+
+ 0.000
+
+
-
+
- 0.000
+ 0.025
-
+
- 0.025
+ 0.050
-
+
- 0.050
+ 0.075
-
+
- 0.075
+ 0.100
-
+
- 0.100
+ 0.125
-
+
- 0.125
+ 0.150
-
+
- 0.150
+ 0.175
-
+
- 0.175
-
-
-
-
-
-
-
-
-
0.200
-
+
3D distance from solved pose to target (model units)
-
+
-
+
-
+
0
-
+
-
+
-
+
25
-
+
-
+
-
+
50
-
+
-
+
-
+
75
-
+
density
-
-
-
+
+
-
-
-
+
+
@@ -2447,7 +2437,7 @@ L 47.41875 469.731398
L 397.33875 565.172
" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/>
-
+
+" clip-path="url(#p3b18a400d2)" style="fill: none; stroke: #4051b5; stroke-width: 2; stroke-linecap: square"/>
-
+
+" clip-path="url(#p3b18a400d2)" style="fill: none; stroke: #2fb170; stroke-width: 2; stroke-linecap: square"/>
-
+
Fit-residual distribution (per-frame average, n=22 real mocap frames)
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2632,7 +2622,7 @@ L 127.093047 15.657324
z
" style="fill: #4051b5"/>
-
+
3D
@@ -2643,25 +2633,25 @@ L 194.163828 15.657324
z
" style="fill: #2fb170"/>
-
+
XYView
-
+
-
+
-
+
-
+
-
+
diff --git a/benchmark/plot/results/comparison.svg b/benchmark/plot/results/comparison.svg
index 7f5a6f1..565591a 100644
--- a/benchmark/plot/results/comparison.svg
+++ b/benchmark/plot/results/comparison.svg
@@ -1,12 +1,12 @@
-