Tutorials¶
Interactive Jupyter notebooks with detailed walkthroughs, progressing from basics to advanced workflows.
Available tutorials¶
- Introduction to pybvh — the BVH file format, reading/writing, the
Bvhobject, basic inspection - Spatial coordinates and skeleton operations — forward kinematics, centering modes, skeleton operations (
retarget,scale,extract_joints) - Rotations — Euler angles, rotation matrices, quaternions, SLERP, 6D representation, axis-angle, Euler order changes, gimbal lock and discontinuity illustrations
- Visualization with bvhplot — static snapshots, video export, interactive playback, side-by-side comparison, camera control,
follow=Truetracking, backend options - Transforms and augmentation —
mirror,rotate_vertical,translate_root,add_rotation_noise,add_position_noise,perturb_speed,drop_frames, composing transforms, reproducibility - Motion features and analysis — joint velocities and accelerations, angular velocities, root-relative positions, root trajectory, foot contacts,
to_feature_array() - Batch processing —
read_bvh_directory,harmonize,batch_to_numpy, save/load pattern - Motion descriptors — geometry (
curvature,bounding_box,center_of_mass), dynamics (node_jerk,smoothness,kinetic_energy), gait (gait_parametersand itscontacts=-sharing projections), SE(3) (relative_transform,se3_log,rotation_geodesic_distance), and reusing FK output viacoords=— with closed-form sanity checks
A reader who finishes all eight has a solid working understanding of BVH data and the complete pybvh API.
Running locally¶
Editing the tutorials (for contributors)¶
Each tutorial is a Jupytext-paired pair: a .ipynb file (the canonical rendered artifact, with outputs and plots) and a .py file in the Percent format (the plain-text source, git-friendly, reviewable as Python).
Both files are committed. Every cell — code, markdown, and cell-level tags like skip-execution or slow-on-pr — is mirrored in both files.
How to edit¶
Install the dev extras (includes jupytext and nbmake):
Then edit either side:
- In Jupyter Lab — edit the
.ipynbas usual. On save, Jupytext rewrites the paired.pyautomatically. - In VS Code — open the
.py; the Jupyter extension recognizes Percent-format cells and lets you run them with output inline. Save syncs back to the.ipynb. - In any text editor — edit the
.py, then run: Jupytext picks the newer file by mtime and updates the other side. Outputs on unchanged cells are preserved; outputs on modified cells are cleared (re-run the notebook to regenerate).
Re-executing a tutorial¶
The .ipynb is the artifact GitHub renders, straight from its committed outputs — nothing re-executes it at view time, so a notebook committed without its figures teaches nothing. After changing code cells, regenerate the outputs:
Run it from the repository root; cells tagged skip-execution are honoured automatically.
Each plotting tutorial runs %matplotlib inline in its setup cell (written as # %matplotlib inline in the .py, which jupytext uncomments into the notebook). Leave it there. Matplotlib picks its backend from the MPLBACKEND environment variable, and if that names a non-interactive backend — CI sets MPLBACKEND=Agg for the headless runner — then executing the notebook drops every figure and replaces it with a FigureCanvasAgg is non-interactive warning on stderr. The magic pins the inline backend regardless of the environment, so the same command produces the same figures on any machine.
tests/test_tutorial_notebooks.py enforces all of this: the jupytext pair must match, execution counts must be sequential 1..N, the magic must be present in every tutorial that imports pyplot, every plt.show() cell must carry a figure, and no backend warning or traceback may reach the committed outputs.
Animated clips: markdown images, never cell outputs¶
Static figures reach GitHub as image/png cell outputs, but that route does not exist for animations: GitHub's notebook renderer silently drops image/gif outputs — the reader sees <IPython.core.display.Image object> where the clip should play — and it does not resolve relative image paths in markdown cells either (the reader sees only the alt text). The one form it renders is a markdown image with an absolute URL, which works because this repo is public:
So for any animated clip: write the GIF to a committed location (tutorials/assets/ for tutorials, gallery/ for the gallery — the rendering cell just returns the path), commit the file, and display it from a markdown cell as above. Never return IPython.display.Image from a cell — besides being invisible on GitHub, it embeds the GIF a second time as base64 inside the .ipynb. tests/test_tutorial_notebooks.py and tests/test_gallery_notebook.py enforce both halves: no image/gif cell outputs anywhere, and every markdown image an absolute raw.githubusercontent.com URL that resolves to a committed, non-gitignored file.
Cell-level execution control on CI¶
The tutorial CI (.github/workflows/tutorials.yml) executes every tutorial under nbmake with two tag conventions:
skip-execution— cell is always skipped (used for interactivebvh.play()calls that open a window or widget).slow-on-pr— cell is skipped on pull-request builds, executed on pushes tomain/devand on manual dispatch. Used forbvh.render(...)calls that produce videos.
Add a tag in the .py by writing it into the cell header:
Then jupytext --sync propagates the tag into the .ipynb metadata.
Keeping the pair in sync¶
If you ever suspect the two files have drifted, run:
This is also safe to run as a pre-commit step. A future commit may add a pre-commit hook to enforce sync automatically.
The Feature Gallery page¶
The Gallery docs page is generated from gallery/feature_gallery.ipynb (a Jupytext pair like the tutorials, executed and committed with outputs). CI regenerates it on every deploy; docs/gallery/ is gitignored — never edit it by hand. To preview locally:
After editing gallery code cells, re-execute the notebook (jupyter nbconvert --to notebook --execute --inplace gallery/feature_gallery.ipynb) before exporting, so the committed outputs stay in sync with the source. CI enforces this: the gallery notebook executes under nbmake in tutorials.yml (GIF cells are tagged slow-on-pr, like the tutorials), and tests/test_gallery_notebook.py fails if the jupytext pair drifts, the committed outputs are stale (non-sequential execution counts, error/stderr outputs), or the figures have gone missing.
The gallery's setup cell pins %matplotlib inline for the same reason the tutorials do, and it matters more here: no gallery cell calls plt.show(), so every figure arrives via the inline backend's end-of-cell flush of open figures. Under a different MPLBACKEND they vanish without even a warning — sequential execution counts, clean stderr, and an empty docs page.
A handful of figures are also embedded inline in the guide pages via stable names (docs/gallery/img/centered-modes.png, …) declared in STABLE_FIGURES inside scripts/export_gallery.py; the exporter fails loudly if a gallery refactor breaks one of those matches.