From d8d80925b7af503c1d92d6c1d62b711e507d11df Mon Sep 17 00:00:00 2001 From: Arash Sal Moslehian Date: Tue, 20 Jan 2026 00:18:24 +0100 Subject: [PATCH 01/14] docs: set up developer documentation --- .github/workflows/publish-book.yml | 10 ++ _toc.yml | 5 + .../docs/assets/favicon.ico | Bin 0 -> 17531 bytes .../docs/assets/javascripts/.gitkeep | 0 .../docs/assets/logo.png | Bin 0 -> 50422 bytes .../docs/assets/stylesheets/.gitkeep | 0 deeplabcut/pose_estimation_pytorch/mkdocs.yml | 120 ++++++++++++++++++ docs/dev.md | 5 + 8 files changed, 140 insertions(+) create mode 100644 deeplabcut/pose_estimation_pytorch/docs/assets/favicon.ico create mode 100644 deeplabcut/pose_estimation_pytorch/docs/assets/javascripts/.gitkeep create mode 100644 deeplabcut/pose_estimation_pytorch/docs/assets/logo.png create mode 100644 deeplabcut/pose_estimation_pytorch/docs/assets/stylesheets/.gitkeep create mode 100644 deeplabcut/pose_estimation_pytorch/mkdocs.yml create mode 100644 docs/dev.md diff --git a/.github/workflows/publish-book.yml b/.github/workflows/publish-book.yml index 7996b0672b..c0d815266a 100644 --- a/.github/workflows/publish-book.yml +++ b/.github/workflows/publish-book.yml @@ -33,3 +33,13 @@ jobs: with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: site + + - name: Build developer docs + run: | + mkdocs build -v -f deeplabcut/pose_estimation_pytorch/mkdocs.yml --site-dir ../../_build/html/dev + + - name: GitHub Pages action + uses: peaceiris/actions-gh-pages@v3.9.3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./_build/html diff --git a/_toc.yml b/_toc.yml index 6bb51801ff..ec0372b30a 100644 --- a/_toc.yml +++ b/_toc.yml @@ -124,3 +124,8 @@ parts: - caption: Citations for DeepLabCut chapters: - file: docs/citation + +- caption: Development + chapters: + - file: docs/dev + title: Developer Documentation diff --git a/deeplabcut/pose_estimation_pytorch/docs/assets/favicon.ico b/deeplabcut/pose_estimation_pytorch/docs/assets/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..e1bff2679061c22d3b3dce6ecdf0f7996d598f9c GIT binary patch literal 17531 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⋙cIOF@e zUZuS?M%{rTQyR7<7rPt-x76^|&xX0;SOL0?O|JV(qI_SXPeOf^Adi~tCiNzE+as*M za^)gT1qqm5Y9BPV&8UAbSrV3yGHEAot^D(BwoQNysL9?9V|d!UvJBk4PBvkFHjz2G zr1lS{$3H67GrhWHrVD-DhLDfFn`v0yf)z`Yjk~%fD%!_onn!i)5tggA+3@tHOHSLw z*f2uDY@%V@=t|yMQUoN}z_cluXRN|~IaLl10M5ye1 zE0CP5Q+={sc*kMyiC%MZ=8KT&lI!#C?@ATa_JHFu2p2tZ{xLT&IVpfWXOShqDxSY0 zIVWXuf>G*VpEJ3(2_^JH6Ix9X+NkO=duB%NmEKjgfVO#)pDY$Ego+H_|DCEBWud}b z9dEkNy8J0V#WJ-j6QU%UM3_|In6;){USSb^6GbZOEk`+rzjZ8uZ1 z0)t5f8kU1JARWrz(cRqKS{R?2o#BLBaerlvRzV}23d=7(-c+gaV=lXD(=jSNR8=}y zq9co={3uPfc_cXX3nr5u7@o7Y8|Yu4gP#<##kDwrZC3rekatC7_nQ^Am(|+WfU^wC zO@4&Mj>W76|FQ{j%&Nxyf5xx$^=6d(`*yiHiGc5~Eqb}?Bp7D%AG#25Rj7|g?(TMB zrN>T88``%?Ki__Bi}b96d8dCZde*mk#nr<(KTWceg6X zbl9pf3Tz|?FJt`wDI{63%5MeZQ@`y_9ekSE2M&W7l`9yxm+kfGxK|R*ysmVYs8h)a zeyIXxjW=aW1#Fiw+_}(T`Jk#@TKP!bZoV^_4xBR__wfpS3}oBK=(V=8A-{3{jjE!J z0(;e&@)pVcO3YTfCPoKow$7sRf|7_X*uXLI*X}&?KRr_v3t4KhPaylLM!4PI6n|p*nhXwsicv^ z^x1xf8rCW2>dg1PY|71l)BQfSiAC*I?Ste8ydmoqT()a0-gk2kL(Tk-4|cnZ{$6pl zw!1momhpf=0oxCT3T}y;ucvWsZ0B0xxlZ!R_1~-wYyJOClV$$UWO^)p`MK?NR{{<% z(tP0iAol@_LFX&Ul;qwQEdpPc)OSQ(spQ?dC7sXopfuB*-M=)wds}n=AH1*hI^o#t UOWW;%$A>U@y85}Sb4q9e0GCs<=Kufz literal 0 HcmV?d00001 diff --git a/deeplabcut/pose_estimation_pytorch/docs/assets/javascripts/.gitkeep b/deeplabcut/pose_estimation_pytorch/docs/assets/javascripts/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/deeplabcut/pose_estimation_pytorch/docs/assets/logo.png b/deeplabcut/pose_estimation_pytorch/docs/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..1c99d7d1c122a4641d8094d1e6264763e33295d0 GIT binary patch literal 50422 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 literal 0 HcmV?d00001 diff --git a/deeplabcut/pose_estimation_pytorch/docs/assets/stylesheets/.gitkeep b/deeplabcut/pose_estimation_pytorch/docs/assets/stylesheets/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/deeplabcut/pose_estimation_pytorch/mkdocs.yml b/deeplabcut/pose_estimation_pytorch/mkdocs.yml new file mode 100644 index 0000000000..d1493a983a --- /dev/null +++ b/deeplabcut/pose_estimation_pytorch/mkdocs.yml @@ -0,0 +1,120 @@ +site_name: DeepLabCut Developer Documentation +site_description: Developer documentation for DeepLabCut +site_url: https://deeplabcut.github.io/DeepLabCut/dev + +repo_name: DeepLabCut/DeepLabCut +repo_url: https://github.com/DeepLabCut/DeepLabCut + +theme: + name: material + custom_dir: "docs/theme" + logo: assets/logo.png + favicon: assets/favicon.ico + + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + primary: deep purple + toggle: + icon: material/brightness-7 + name: Switch to dark mode + + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: deep purple + toggle: + icon: material/brightness-4 + name: Switch to light mode + + features: + - announce.dismiss # allow dismissing announcements + - content.code.annotate # enable code annotations + - content.code.copy # add copy button to code blocks + - content.tabs.link # link content tabs across pages + - navigation.indexes # enable navigation index pages + - navigation.instant # enable instant navigation + - navigation.instant.prefetch # prefetch pages for instant navigation + - navigation.instant.preview # show instant previews + - navigation.instant.progress # show loading progress + - navigation.path # show breadcrumb navigation path + - navigation.sections # enable navigation sections + - navigation.tabs # enable navigation tabs + - navigation.top # add back-to-top button + - navigation.tracking # track navigation in URL + - search.highlight # highlight search results + - search.suggest # show search suggestions + - toc.follow # follow active heading in table of contents + +plugins: + - autorefs # link to a heading or attribute (id) from any other page in the site + - mike # for versioning + - mkdocs-jupyter # adds support for rendering Jupyter Notebooks + - api-autonav: # automatically generates API reference navigation based on the file structure + modules: ["../"] + module_options: {} + exclude: [] + nav_section_title: "API Reference" + api_root_uri: "reference" + nav_item_prefix: "" + exclude_private: true + show_full_namespace: false + on_implicit_namespace_package: "warn" + - mkdocstrings: + default_handler: python + handlers: + python: + options: + summary: true + show_root_heading: true + docstring_options: + returns_multiple_items: false + docstring_section_style: table + inventories: + - url: https://docs.python.org/3/objects.inv + domains: [py, std] + - url: https://typing-extensions.readthedocs.io/en/latest/objects.inv + - url: https://pytorch.org/docs/stable/objects.inv + - search # adds a search bar to the header + - social # generates social cards rendering as preview images on social media + +markdown_extensions: + - attr_list # adds support for setting attributes (ids) on elements + - pymdownx.highlight: # adds support for syntax highlighting of code blocks + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.arithmatex # adds support for rendering LaTeX math expressions + - pymdownx.inlinehilite # add support for syntax highlighting of inline code blocks + - pymdownx.snippets # adds the ability to embed content from arbitrary files into a document + - pymdownx.superfences # allows for arbitrary nesting of code and content blocks inside each other + - pymdownx.tabbed: # provides a syntax to easily add tabbed Markdown content. + alternate_style: true + - pymdownx.blocks.details # allows for the creation of collapsible details/summary constructs. + - pymdownx.blocks.admonition # allows for the creation of admonitions. + - pymdownx.blocks.definition # allows for the creation of definition lists. + - pymdownx.blocks.tab # allows for the creation of tab containers. + - pymdownx.blocks.html # allows for the arbitrary creation of HTML elements of various types. + +extra: + version: + provider: mike # TODO: use mike to manage versions + +copyright: "Copyright © 2026 The DeepLabCut Team" + +nav: + - Get Started: + - Overview: index.md + - Installation: installation.md + - Quick Start: quickstart.md + - Developer Guides: + - Overview: devguides.md + - Model Architecture: models.md + - Configuration Files: configuration.md + - Data Preparation: dataprep.md + - Training Models: training.md + - Inference & Analysis: inference.md + - Adding Custom Models: custom_models.md + - Multi-animal Tracking: tracking.md + - Examples: + - Practical Examples: examples.md + - API Reference diff --git a/docs/dev.md b/docs/dev.md new file mode 100644 index 0000000000..945f98066e --- /dev/null +++ b/docs/dev.md @@ -0,0 +1,5 @@ +# Developer Documentation + + + +If you are not redirected automatically, please go to [Developer docs](dev/index/). \ No newline at end of file From 2b7d7102ef6095958b5b76072805b835f94a0767 Mon Sep 17 00:00:00 2001 From: Arash Sal Moslehian Date: Tue, 20 Jan 2026 00:19:10 +0100 Subject: [PATCH 02/14] docs: add stub files for dev documentation --- .../docs/configuration.md | 1 + .../docs/custom_models.md | 1 + .../pose_estimation_pytorch/docs/dataprep.md | 1 + .../pose_estimation_pytorch/docs/devguides.md | 1 + .../pose_estimation_pytorch/docs/examples.md | 1 + .../pose_estimation_pytorch/docs/index.md | 19 +++++++++++++++++++ .../pose_estimation_pytorch/docs/inference.md | 1 + .../docs/installation.md | 2 ++ .../pose_estimation_pytorch/docs/models.md | 1 + .../docs/quickstart.md | 1 + .../pose_estimation_pytorch/docs/tracking.md | 1 + .../pose_estimation_pytorch/docs/training.md | 1 + 12 files changed, 31 insertions(+) create mode 100644 deeplabcut/pose_estimation_pytorch/docs/configuration.md create mode 100644 deeplabcut/pose_estimation_pytorch/docs/custom_models.md create mode 100644 deeplabcut/pose_estimation_pytorch/docs/dataprep.md create mode 100644 deeplabcut/pose_estimation_pytorch/docs/devguides.md create mode 100644 deeplabcut/pose_estimation_pytorch/docs/examples.md create mode 100644 deeplabcut/pose_estimation_pytorch/docs/index.md create mode 100644 deeplabcut/pose_estimation_pytorch/docs/inference.md create mode 100644 deeplabcut/pose_estimation_pytorch/docs/installation.md create mode 100644 deeplabcut/pose_estimation_pytorch/docs/models.md create mode 100644 deeplabcut/pose_estimation_pytorch/docs/quickstart.md create mode 100644 deeplabcut/pose_estimation_pytorch/docs/tracking.md create mode 100644 deeplabcut/pose_estimation_pytorch/docs/training.md diff --git a/deeplabcut/pose_estimation_pytorch/docs/configuration.md b/deeplabcut/pose_estimation_pytorch/docs/configuration.md new file mode 100644 index 0000000000..58611adbac --- /dev/null +++ b/deeplabcut/pose_estimation_pytorch/docs/configuration.md @@ -0,0 +1 @@ +# Model Configuration diff --git a/deeplabcut/pose_estimation_pytorch/docs/custom_models.md b/deeplabcut/pose_estimation_pytorch/docs/custom_models.md new file mode 100644 index 0000000000..2f5b696872 --- /dev/null +++ b/deeplabcut/pose_estimation_pytorch/docs/custom_models.md @@ -0,0 +1 @@ +# Adding Custom Models diff --git a/deeplabcut/pose_estimation_pytorch/docs/dataprep.md b/deeplabcut/pose_estimation_pytorch/docs/dataprep.md new file mode 100644 index 0000000000..5bcbf2dc89 --- /dev/null +++ b/deeplabcut/pose_estimation_pytorch/docs/dataprep.md @@ -0,0 +1 @@ +# Data Preparation and Loading diff --git a/deeplabcut/pose_estimation_pytorch/docs/devguides.md b/deeplabcut/pose_estimation_pytorch/docs/devguides.md new file mode 100644 index 0000000000..1f287f76aa --- /dev/null +++ b/deeplabcut/pose_estimation_pytorch/docs/devguides.md @@ -0,0 +1 @@ +# Developer Guides diff --git a/deeplabcut/pose_estimation_pytorch/docs/examples.md b/deeplabcut/pose_estimation_pytorch/docs/examples.md new file mode 100644 index 0000000000..a0fc203fe8 --- /dev/null +++ b/deeplabcut/pose_estimation_pytorch/docs/examples.md @@ -0,0 +1 @@ +# Code Examples diff --git a/deeplabcut/pose_estimation_pytorch/docs/index.md b/deeplabcut/pose_estimation_pytorch/docs/index.md new file mode 100644 index 0000000000..5ecf6bdf38 --- /dev/null +++ b/deeplabcut/pose_estimation_pytorch/docs/index.md @@ -0,0 +1,19 @@ +# DeepLabCut PyTorch API Documentation + +
+ +

+ +

+ +
+ +[![PyPI](https://img.shields.io/pypi/v/deeplabcut?label=PyPI)](https://pypi.org/project/deeplabcut) +![PyPI - Python Version](https://img.shields.io/pypi/pyversions/deeplabcut) +[![License](https://img.shields.io/github/license/DeepLabCut/DeepLabCut)](https://github.com/DeepLabCut/DeepLabCut/blob/main/LICENSE) + +This documentation is designed for maintainers, developers, and expert users who want to understand and extend the PyTorch backend of DeepLabCut 3.0+. It provides detailed information about the architecture, APIs, and practical examples for building and training state-of-the-art pose estimation models. + +## Overview + +The [`deeplabcut.pose_estimation_pytorch`][] package provides a complete framework for training and deploying deep learning models for pose estimation. The API is designed to be modular, flexible, and extensible, allowing developers to easily add new models, customize training pipelines, and integrate with existing workflows. \ No newline at end of file diff --git a/deeplabcut/pose_estimation_pytorch/docs/inference.md b/deeplabcut/pose_estimation_pytorch/docs/inference.md new file mode 100644 index 0000000000..48d9ecc8bf --- /dev/null +++ b/deeplabcut/pose_estimation_pytorch/docs/inference.md @@ -0,0 +1 @@ +# Inference and Video Analysis diff --git a/deeplabcut/pose_estimation_pytorch/docs/installation.md b/deeplabcut/pose_estimation_pytorch/docs/installation.md new file mode 100644 index 0000000000..4026317a86 --- /dev/null +++ b/deeplabcut/pose_estimation_pytorch/docs/installation.md @@ -0,0 +1,2 @@ +# Installation + diff --git a/deeplabcut/pose_estimation_pytorch/docs/models.md b/deeplabcut/pose_estimation_pytorch/docs/models.md new file mode 100644 index 0000000000..dc34891ec2 --- /dev/null +++ b/deeplabcut/pose_estimation_pytorch/docs/models.md @@ -0,0 +1 @@ +# Model Architecture diff --git a/deeplabcut/pose_estimation_pytorch/docs/quickstart.md b/deeplabcut/pose_estimation_pytorch/docs/quickstart.md new file mode 100644 index 0000000000..05cf8c1fd0 --- /dev/null +++ b/deeplabcut/pose_estimation_pytorch/docs/quickstart.md @@ -0,0 +1 @@ +# Quick Start diff --git a/deeplabcut/pose_estimation_pytorch/docs/tracking.md b/deeplabcut/pose_estimation_pytorch/docs/tracking.md new file mode 100644 index 0000000000..2c8895288d --- /dev/null +++ b/deeplabcut/pose_estimation_pytorch/docs/tracking.md @@ -0,0 +1 @@ +# Tracking Guide diff --git a/deeplabcut/pose_estimation_pytorch/docs/training.md b/deeplabcut/pose_estimation_pytorch/docs/training.md new file mode 100644 index 0000000000..7a3bf0b704 --- /dev/null +++ b/deeplabcut/pose_estimation_pytorch/docs/training.md @@ -0,0 +1 @@ +# Training Models From 880f72ee98af9cc8ea4a6289a0a4daf8f2e6c306 Mon Sep 17 00:00:00 2001 From: Arash Sal Moslehian Date: Tue, 20 Jan 2026 10:27:21 +0100 Subject: [PATCH 03/14] docs: add preliminary dev docs --- .../docs/configuration.md | 94 +++++++++ .../docs/custom_models.md | 12 ++ .../pose_estimation_pytorch/docs/dataprep.md | 139 ++++++++++++++ .../pose_estimation_pytorch/docs/devguides.md | 180 ++++++++++++++++++ .../pose_estimation_pytorch/docs/examples.md | 134 +++++++++++++ .../pose_estimation_pytorch/docs/index.md | 59 +++++- .../pose_estimation_pytorch/docs/inference.md | 103 ++++++++++ .../pose_estimation_pytorch/docs/models.md | 88 +++++++++ .../docs/quickstart.md | 68 +++++++ .../pose_estimation_pytorch/docs/training.md | 52 +++++ 10 files changed, 928 insertions(+), 1 deletion(-) diff --git a/deeplabcut/pose_estimation_pytorch/docs/configuration.md b/deeplabcut/pose_estimation_pytorch/docs/configuration.md index 58611adbac..e384466b77 100644 --- a/deeplabcut/pose_estimation_pytorch/docs/configuration.md +++ b/deeplabcut/pose_estimation_pytorch/docs/configuration.md @@ -1 +1,95 @@ # Model Configuration + +Model architectures in DeepLabCut PyTorch are defined using configuration files written in YAML format. These configuration files specify the model architecture, training hyperparameters, data augmentation settings, and more. + +## Configuration File Structure + +The primary configuration file is named `pytorch_cfg.yaml` and is stored in the model's training directory. This file is automatically generated during the standard DeepLabCut workflow, but can also be created manually for custom projects. + +## Creating Configuration Files + +The [`deeplabcut.pose_estimation_pytorch.config`][] module provides functions to create and manipulate configuration files. + +### Basic Configuration Creation + +Use [`make_pytorch_pose_config`][deeplabcut.pose_estimation_pytorch.config.make_pytorch_pose_config] to generate a model configuration: + +```python +from pathlib import Path +import deeplabcut.pose_estimation_pytorch as dlc_torch + +# Configuration for a DeepLabCut project +project_cfg = { + "bodyparts": ["nose", "left_ear", "right_ear", "tail_base"], + "individuals": ["mouse1", "mouse2"], + # ... other project settings +} + +pose_config_path = Path("/path/to/model/train") +model_cfg = dlc_torch.config.make_pytorch_pose_config( + project_config=project_cfg, + pose_config_path=pose_config_path, + net_type="hrnet_w32", + top_down=True, + save=True, # Save the configuration to disk +) +``` + +### Configuration for COCO Datasets + +For COCO-format datasets without a DeepLabCut project, use [`make_basic_project_config`][deeplabcut.pose_estimation_pytorch.config.make_basic_project_config]: + +```python +from pathlib import Path +import deeplabcut.pose_estimation_pytorch as dlc_torch + +# Create a minimal project configuration +project_cfg = dlc_torch.config.make_basic_project_config( + dataset_path="/path/to/COCOProject", + bodyparts=["nose", "left_eye", "right_eye", "left_ear", "right_ear"], + max_individuals=2, + multi_animal=True, +) + +# Generate model configuration +model_cfg = dlc_torch.config.make_pytorch_pose_config( + project_config=project_cfg, + pose_config_path=Path("/path/to/experiment/train"), + net_type="dlcrnet_ms5", + top_down=False, + save=True, +) +``` + +## Configuration File Components + +A complete `pytorch_cfg.yaml` file contains several sections: + +- Model Architecture: specifies the backbone, neck, and head components: + +- Training Parameters: Defines the optimization strategy and training loop settings: + +- Data Configuration: Specifies data augmentation and preprocessing for training and inference: + +- Data Augmentation Options: transformations are available for the `train` and `inference` configurations. Most augmentations should only be applied during training, not inference. + +- Training Settings: Training-specific parameters control batch size, epochs, and data loading: + +- Runner Configuration: The runner manages training execution, optimization, and checkpointing: + +- Optimizers + +- Learning Rate Schedulers: use any scheduler from [`torch.optim.lr_scheduler`](https://pytorch.org/docs/stable/optim.html#how-to-adjust-learning-rate): + +- Logging: training runs are logged locally by default. Optionally integrate with [Weights & Biases](https://wandb.ai/site): + + +- Resuming Training: resume from a specific checkpoint by specifying its path: + +```yaml +resume_training_from: /path/to/model/train/snapshot-010.pt +``` + +- Inference Configuration: configure inference-specific behavior independently of training: + +- Top-Down Detector Configuration: Top-down models require a separate detector configuration: \ No newline at end of file diff --git a/deeplabcut/pose_estimation_pytorch/docs/custom_models.md b/deeplabcut/pose_estimation_pytorch/docs/custom_models.md index 2f5b696872..4acc2cf484 100644 --- a/deeplabcut/pose_estimation_pytorch/docs/custom_models.md +++ b/deeplabcut/pose_estimation_pytorch/docs/custom_models.md @@ -1 +1,13 @@ # Adding Custom Models + +DeepLabCut's PyTorch backend uses a registry system that makes it easy to add custom model components. This guide explains how to extend the framework with your own backbones, necks, heads, predictors, and target generators. + +## Model Registry System + +The registry system allows you to register custom components that can be instantiated from configuration files. Five registries are available for different model building blocks: + +- [`BACKBONES`][deeplabcut.pose_estimation_pytorch.models.backbones] - Feature extraction networks +- [`NECKS`][deeplabcut.pose_estimation_pytorch.models.necks] - Intermediate processing layers +- [`HEADS`][deeplabcut.pose_estimation_pytorch.models.heads] - Task-specific output layers +- [`PREDICTORS`][deeplabcut.pose_estimation_pytorch.models.predictors] - Output-to-keypoint converters +- [`TARGET_GENERATORS`][deeplabcut.pose_estimation_pytorch.models.target_generators] - Annotation-to-target converters \ No newline at end of file diff --git a/deeplabcut/pose_estimation_pytorch/docs/dataprep.md b/deeplabcut/pose_estimation_pytorch/docs/dataprep.md index 5bcbf2dc89..905cb24512 100644 --- a/deeplabcut/pose_estimation_pytorch/docs/dataprep.md +++ b/deeplabcut/pose_estimation_pytorch/docs/dataprep.md @@ -1 +1,140 @@ # Data Preparation and Loading + +The [`deeplabcut.pose_estimation_pytorch.data`][] package provides comprehensive tools for dataset creation, train/test splitting, and data augmentation. This guide covers the data loaders, datasets, and transforms available in the PyTorch backend. + +## Data Loaders + +DeepLabCut provides two main data loader classes for different dataset formats: + +### DLCLoader + +The [`DLCLoader`][deeplabcut.pose_estimation_pytorch.data.DLCLoader] class loads labeled data from a DeepLabCut project. It handles train/test splitting, configuration loading, and dataset creation for a specific shuffle. + +**Basic Usage:** + +```python +import deeplabcut.pose_estimation_pytorch as dlc_torch + +loader = dlc_torch.DLCLoader( + config="/path/to/project/config.yaml", + trainset_index=0, + shuffle=1, +) + +# Access loader properties +print(loader.model_folder) # Path to model directory +print(loader.evaluation_folder) # Path to evaluation directory +print(loader.pose_task) # Task type (BOTTOM_UP, TOP_DOWN, etc.) + +# View the data +print(loader.df) # Complete dataset as DataFrame +print(loader.df_train) # Training split +print(loader.df_test) # Test split +``` + +**Creating Datasets:** + +```python +# Create training dataset +train_dataset = loader.create_dataset( + transform=dlc_torch.build_transforms(loader.model_cfg["data"]["train"]), + mode="train", + task=loader.pose_task, +) + +# Create validation dataset +valid_dataset = loader.create_dataset( + transform=dlc_torch.build_transforms(loader.model_cfg["data"]["inference"]), + mode="test", + task=loader.pose_task, +) + +# Check dataset size +print(f"Training samples: {len(train_dataset)}") +print(f"Validation samples: {len(valid_dataset)}") +``` + +### COCOLoader + +The [`COCOLoader`][deeplabcut.pose_estimation_pytorch.data.COCOLoader] class enables training on datasets in COCO format without creating a DeepLabCut project. This is useful for working with public datasets or custom data pipelines. + +**COCO Dataset Structure:** + +``` +COCOProject/ +├── annotations/ +│ ├── train.json +│ └── test.json +└── images/ + ├── img0000.png + ├── img0001.png + └── ... +``` + +**Working with COCO Data:** + +```python +from pathlib import Path +import deeplabcut.pose_estimation_pytorch as dlc_torch + +project_root = Path("/path/to/COCOProject") + +# Parse dataset information +train_dict = dlc_torch.COCOLoader.load_json( + project_root, + filename="train.json" +) +max_num_individuals, bodyparts = dlc_torch.COCOLoader.get_project_parameters(train_dict) + +# Create model configuration +model_cfg = dlc_torch.config.make_pytorch_pose_config( + project_config=dlc_torch.config.make_basic_project_config( + dataset_path=str(project_root), + bodyparts=bodyparts, + max_individuals=max_num_individuals, + multi_animal=True, + ), + pose_config_path=project_root / "experiments" / "hrnet_w32" / "train", + net_type="hrnet_w32", + top_down=True, + save=True, +) + +# Create loader +loader = dlc_torch.COCOLoader( + project_root=project_root, + model_config_path=project_root / "experiments" / "hrnet_w32" / "train" / "pytorch_cfg.yaml", + train_json_filename="train.json", + test_json_filename="test.json", +) + +# Create datasets +train_dataset = loader.create_dataset( + transform=dlc_torch.build_transforms(loader.model_cfg["data"]["train"]), + mode="train", + task=loader.pose_task, +) +``` + +**Image Path Resolution:** + +COCO JSON files can specify image paths in two ways: + +1. **Relative paths**: Resolved to the `images/` folder + - `"file_name": "img0000.png"` → `/path/to/COCOProject/images/img0000.png` + - `"file_name": "subfolder/img0000.png"` → `/path/to/COCOProject/images/subfolder/img0000.png` + +2. **Absolute paths**: Used directly without resolution + - `"file_name": "/data/disk2/images/img0000.png"` → `/data/disk2/images/img0000.png` + +This allows you to keep images on different disks or reuse images across projects without duplication. + +## PoseDataset + +The [`PoseDataset`][deeplabcut.pose_estimation_pytorch.data.PoseDataset] class extends `torch.utils.data.Dataset` and converts raw images and keypoints into tensors for training and evaluation. + +- Loads images and annotations +- Applies data augmentation transforms +- Generates training targets using the model's target generator +- Handles multi-animal and single-animal data +- Supports dynamic cropping for top-down models diff --git a/deeplabcut/pose_estimation_pytorch/docs/devguides.md b/deeplabcut/pose_estimation_pytorch/docs/devguides.md index 1f287f76aa..a91de9d617 100644 --- a/deeplabcut/pose_estimation_pytorch/docs/devguides.md +++ b/deeplabcut/pose_estimation_pytorch/docs/devguides.md @@ -1 +1,181 @@ # Developer Guides + +This section provides comprehensive guides for developers working with the DeepLabCut PyTorch backend. Whether you're training models, running inference, or building custom architectures, these guides will help you understand and extend the framework. + +## Guide Overview + +### Model Architecture + +The [Model Architecture](models.md) guide explains the modular design of DeepLabCut models. Learn about: + +- **Backbones**: Feature extraction networks (ResNet, HRNet, CSPNext) +- **Necks**: Optional intermediate processing layers +- **Heads**: Task-specific output layers for pose estimation +- **Predictors**: Converting model outputs to keypoint predictions +- **Target Generators**: Creating training targets from annotations +- **Available Models**: Pre-configured architectures for various tasks + +This guide is essential for understanding how components work together to form complete pose estimation systems. + +### Configuration Files + +The [Configuration Files](configuration.md) guide covers model and training configuration. Topics include: + +- Creating configuration files with [`make_pytorch_pose_config`][deeplabcut.pose_estimation_pytorch.config.make_pytorch_pose_config] +- YAML configuration structure and parameters +- Model architecture specification +- Training hyperparameters (optimizer, scheduler, batch size) +- Data augmentation settings +- Reading and writing configurations +- Using configuration templates + +Understanding configuration is crucial for customizing model behavior and training settings. + +### Data Preparation + +The [Data Preparation](dataprep.md) guide explains data loading and augmentation. Learn about: + +- [`DLCLoader`][deeplabcut.pose_estimation_pytorch.data.DLCLoader] for DeepLabCut projects +- [`COCOLoader`][deeplabcut.pose_estimation_pytorch.data.COCOLoader] for COCO-format datasets +- [`PoseDataset`][deeplabcut.pose_estimation_pytorch.data.PoseDataset] for PyTorch data loading +- Data augmentation transforms +- Train/test splitting +- Custom collate functions for multi-animal scenarios +- Snapshot management + +Proper data preparation is key to training robust models. + +### Training Models + +The [Training Models](training.md) guide covers the complete training workflow. Topics include: + +- High-level training APIs: [`train`][deeplabcut.pose_estimation_pytorch.apis.train] and [`train_network`][deeplabcut.pose_estimation_pytorch.apis.train_network] +- Training on DeepLabCut projects and COCO datasets +- [`PoseTrainingRunner`][deeplabcut.pose_estimation_pytorch.runners.PoseTrainingRunner] for custom training loops +- Optimizers and learning rate schedulers +- Checkpointing and resuming training +- Logging with Weights & Biases +- Training top-down models (detector and pose estimation) +- Distributed training across multiple GPUs +- Performance optimization tips + +This guide helps you train models efficiently and monitor progress effectively. + +### Inference and Analysis + +The [Inference & Analysis](inference.md) guide explains how to run predictions. Learn about: + +- High-level APIs: [`analyze_videos`][deeplabcut.pose_estimation_pytorch.apis.analyze_videos] and [`analyze_images`][deeplabcut.pose_estimation_pytorch.apis.analyze_images] +- Low-level [`video_inference`][deeplabcut.pose_estimation_pytorch.apis.video_inference] API +- [`PoseInferenceRunner`][deeplabcut.pose_estimation_pytorch.runners.PoseInferenceRunner] for pose estimation +- [`DetectorInferenceRunner`][deeplabcut.pose_estimation_pytorch.runners.DetectorInferenceRunner] for object detection +- [`VideoIterator`][deeplabcut.pose_estimation_pytorch.apis.VideoIterator] for efficient video processing +- Running inference outside DeepLabCut projects +- Using custom bounding boxes with top-down models +- Model evaluation and metrics +- Visualization and labeled video creation +- Batch processing and performance optimization + +Master inference to efficiently analyze your data and evaluate model performance. + +### Adding Custom Models + +The [Adding Custom Models](custom_models.md) guide shows how to extend the framework. Topics include: + +- The model registry system (BACKBONES, NECKS, HEADS, PREDICTORS, TARGET_GENERATORS) +- Creating custom backbones for feature extraction +- Implementing custom heads for new output types +- Building custom predictors for keypoint extraction +- Designing custom target generators for training +- Creating custom necks for feature processing +- Using custom components in configuration files +- Best practices for extensibility + +This guide empowers you to implement novel architectures and techniques. + +## Core Concepts + +### Modular Design + +DeepLabCut PyTorch uses a modular architecture where models are composed of interchangeable components. + +### Registry System + +Components are registered using decorators, making them available for instantiation from configuration files: + +```python +from deeplabcut.pose_estimation_pytorch.models.backbones import BACKBONES, BaseBackbone + +@BACKBONES.register_module +class MyBackbone(BaseBackbone): + def __init__(self, ...): + super().__init__(stride=32) + # Implementation + + def forward(self, x): + # Forward pass + return x +``` + +This pattern is used for all model components (backbones, necks, heads, predictors, target generators). + +### Configuration-Driven + +Models and training are controlled through YAML configuration files: + +```yaml +model: + backbone: + type: "HRNet" + variant: "w32" + head: + type: "HeatmapHead" + num_bodyparts: 17 + +train: + optimizer: + type: "AdamW" + lr: 0.0001 + epochs: 200 +``` + +This approach separates architecture definition from code, making experimentation easier. + +### Task-Based Design + +Different pose estimation approaches are handled through the [`Task`][deeplabcut.pose_estimation_pytorch.task.Task] enum: + +- `BOTTOM_UP`: Detect all keypoints then group into individuals +- `TOP_DOWN`: Detect individuals then estimate pose for each +- `COND_TOP_DOWN`: Conditional top-down approach + +The same APIs work across tasks, with the framework handling task-specific logic internally. + +## API Patterns + +### High-Level vs Low-Level APIs + +DeepLabCut provides both high-level and low-level APIs: + +- **High-Level**: Simple functions for common tasks (training, video analysis) +- **Low-Level**: Detailed control through runners and components + +Start with high-level APIs and move to low-level when you need more control. + +### Loaders + +Data loaders abstract dataset access: + +- Use [`DLCLoader`][deeplabcut.pose_estimation_pytorch.data.DLCLoader] for standard DeepLabCut projects +- Use [`COCOLoader`][deeplabcut.pose_estimation_pytorch.data.COCOLoader] for COCO-format data +- Both provide consistent interfaces for dataset creation + +### Runners + +Runners encapsulate training and inference logic: + +- [`PoseTrainingRunner`][deeplabcut.pose_estimation_pytorch.runners.PoseTrainingRunner]: Manages training loops +- [`PoseInferenceRunner`][deeplabcut.pose_estimation_pytorch.runners.PoseInferenceRunner]: Handles pose estimation +- [`DetectorInferenceRunner`][deeplabcut.pose_estimation_pytorch.runners.DetectorInferenceRunner]: Handles object detection + +Runners can be created from configuration or instantiated directly for custom workflows. \ No newline at end of file diff --git a/deeplabcut/pose_estimation_pytorch/docs/examples.md b/deeplabcut/pose_estimation_pytorch/docs/examples.md index a0fc203fe8..a5d38a0be7 100644 --- a/deeplabcut/pose_estimation_pytorch/docs/examples.md +++ b/deeplabcut/pose_estimation_pytorch/docs/examples.md @@ -1 +1,135 @@ # Code Examples + +This guide provides practical examples for common DeepLabCut PyTorch workflows. + +## Training a Model on a COCO Dataset + +```python +from pathlib import Path + +import deeplabcut.pose_estimation_pytorch as dlc_torch + +# Specify project paths +project_root = Path("/path/to/my/COCOProject") +train_json_filename = "train.json" +test_json_filename = "test.json" + +loader = dlc_torch.COCOLoader( + project_root=project_root, + model_config_path="/path/to/my/project/experiments/pytorch_config.yaml", + train_json_filename=train_json_filename, + test_json_filename=test_json_filename, +) +dlc_torch.train( + loader=loader, + run_config=loader.model_cfg, + task=dlc_torch.Task(loader.model_cfg["method"]), + device="cuda:2", + logger_config=dict( + type="WandbLogger", + project_name="MyWandbProject", + tags=["model=hrnet_w32"], + ), + snapshot_path=None, +) +``` + +## Running Video Analysis outside a DeepLabCut Project + +DeepLabCut provides high-level APIs (via the GUI or the python package) to analyze your data. The usage of this API assumes the existence of a DLC project (with `config.yaml` file, etc.). + +Sometimes it might be more convenient to just run a model on your data via a low-level API. We also use this API under the hood, in particular for the Model Zoo. Check out the example below: + +```python +from deeplabcut.core.config import read_config_as_dict +from pathlib import Path + +import deeplabcut.pose_estimation_pytorch as dlc_torch + +train_dir = Path("/Users/Jaylen/my-dlc-models/train") +pytorch_config_path = train_dir / "pytorch_config.yaml" +snapshot_path = train_dir / "snapshot-100.pt" + +# for top-down models, otherwise None +detector_snapshot_path = train_dir / "detector-snapshot-100.pt" + +# video and inference parameters +video_path = Path("/Users/Jaylen/my-dlc-models/videos/test-video.mp4") +max_num_animals = 5 +batch_size = 16 +detector_batch_size = 8 + +# read model configuration +model_cfg = read_config_as_dict(pytorch_config_path) +pose_task = dlc_torch.Task(model_cfg["method"]) +pose_runner = dlc_torch.get_pose_inference_runner( + model_config=model_cfg, + snapshot_path=snapshot_path, + max_individuals=max_num_animals, + batch_size=batch_size, +) + +detector_runner = None +if pose_task == dlc_torch.Task.TOP_DOWN: + detector_runner = dlc_torch.get_detector_inference_runner( + model_config=model_cfg, + snapshot_path=detector_snapshot_path, + max_individuals=max_num_animals, + batch_size=detector_batch_size, + ) + +predictions = dlc_torch.video_inference( + video=video_path, + pose_runner=pose_runner, + detector_runner=detector_runner, +) +``` + +## Running Top-Down Video Analysis with Existing Bounding Boxes + +When `deeplabcut.pose_estimation_pytorch.apis.videos.video_inference` is called with a top-down model, it is assumed that a detector snapshot is given as well to obtain bounding boxes with which to run pose estimation. It's possible that you've already obtained bounding boxes for your video (with another object detector or through some other means), and you want to reuse those bounding boxes instead of running an object detector again. + +You can easily do so by writing a bit of custom code, as shown in the example below: + +```python +from deeplabcut.core.config import read_config_as_dict +from pathlib import Path + +import numpy as np +import deeplabcut.pose_estimation_pytorch as dlc_torch +from tqdm import tqdm + +# create an iterator for your video +video = dlc_torch.VideoIterator("/Users/Jayson/my-cool-video.mp4") + +# dummy bboxes - you can load yours from a file or in another way +# the bboxes should be in `xywh` format, i.e. (x_top_left, y_top_left, width, height) +bounding_boxes = [ + dict( # frame 0 bounding boxes + bboxes=np.array([[12, 37, 120, 78]]), + ), + dict( # frame 1 bounding boxes + bboxes=np.array([[17, 45, 128, 73], [532, 34, 117, 87]]), + ), + # ... + dict( # frame N bboxes -> must be equal to the number of frames in the video! + bboxes=np.array([[17, 45, 128, 73], [532, 34, 117, 87]]), + ), +] +video.set_context(bounding_boxes) +max_individuals = np.max([len(context["bboxes"]) for context in bounding_boxes]) + +# run inference! +model_cfg = read_config_as_dict("/Users/Jayson/pytorch_config.yaml") +pose_runner = dlc_torch.get_pose_inference_runner( + model_config=model_cfg, + snapshot_path=Path("/Users/Jayson/model-snapshot.pt"), + max_individuals=max_individuals, + batch_size=32, +) + +# your predictions will be a list, containing the predictions made for each frame +# as a dict (with keys for "bodyparts" but also "bboxes")! +predictions = pose_runner.inference(images=tqdm(video)) +``` + diff --git a/deeplabcut/pose_estimation_pytorch/docs/index.md b/deeplabcut/pose_estimation_pytorch/docs/index.md index 5ecf6bdf38..7c0f4d7d39 100644 --- a/deeplabcut/pose_estimation_pytorch/docs/index.md +++ b/deeplabcut/pose_estimation_pytorch/docs/index.md @@ -16,4 +16,61 @@ This documentation is designed for maintainers, developers, and expert users who ## Overview -The [`deeplabcut.pose_estimation_pytorch`][] package provides a complete framework for training and deploying deep learning models for pose estimation. The API is designed to be modular, flexible, and extensible, allowing developers to easily add new models, customize training pipelines, and integrate with existing workflows. \ No newline at end of file +The [`deeplabcut.pose_estimation_pytorch`][] package provides a complete framework for training and deploying deep learning models for pose estimation. The API is designed to be modular, flexible, and extensible, allowing developers to easily add new models, customize training pipelines, and integrate with existing workflows. + +## Core Components + +The PyTorch DeepLabCut codebase is organized into four main components: + +### High-Level APIs + +The [`deeplabcut.pose_estimation_pytorch.apis`][] module contains high-level methods for training, evaluation, and inference. These methods work seamlessly with DeepLabCut projects but can also be used independently for maximum flexibility. + +Key functions include: + +- [`train`][deeplabcut.pose_estimation_pytorch.apis.train] - Train pose estimation models +- [`evaluate`][deeplabcut.pose_estimation_pytorch.apis.evaluate] - Evaluate model performance +- [`analyze_videos`][deeplabcut.pose_estimation_pytorch.apis.analyze_videos] - Run inference on video files +- [`analyze_images`][deeplabcut.pose_estimation_pytorch.apis.analyze_images] - Run inference on image files +- [`video_inference`][deeplabcut.pose_estimation_pytorch.apis.video_inference] - Low-level video inference API + +### Models + +The [`deeplabcut.pose_estimation_pytorch.models`][] package provides state-of-the-art pose estimation architectures including DLCRNet, HRNet, DEKR, BUCTD, and RTMPose. Models are built from modular components: + +- **Backbones**: Feature extraction networks like ResNet and HRNet (see [`deeplabcut.pose_estimation_pytorch.models.backbones`][]) +- **Necks**: Optional intermediate layers between backbone and head (see [`deeplabcut.pose_estimation_pytorch.models.necks`][]) +- **Heads**: Task-specific output layers for pose prediction (see [`deeplabcut.pose_estimation_pytorch.models.heads`][]) +- **Predictors**: Convert model outputs to keypoint locations (see [`deeplabcut.pose_estimation_pytorch.models.predictors`][]) +- **Target Generators**: Create training targets from annotations (see [`deeplabcut.pose_estimation_pytorch.models.target_generators`][]) + +Object detection models for top-down pose estimation are available in [`deeplabcut.pose_estimation_pytorch.models.detectors`][]. + +You can check available models programmatically: + +```python +import deeplabcut.pose_estimation_pytorch as dlc_torch + +# List available pose estimation models +print(dlc_torch.available_models()) + +# List available object detection models +print(dlc_torch.available_detectors()) +``` + +### Data Loading + +The [`deeplabcut.pose_estimation_pytorch.data`][] package handles dataset creation, train/test splitting, and data augmentation. Two main data loaders are provided: + +- [`DLCLoader`][deeplabcut.pose_estimation_pytorch.data.DLCLoader] - Load data from DeepLabCut projects +- [`COCOLoader`][deeplabcut.pose_estimation_pytorch.data.COCOLoader] - Load data in COCO format + +The [`PoseDataset`][deeplabcut.pose_estimation_pytorch.data.PoseDataset] class extends `torch.utils.data.Dataset` to provide tensor-based datasets for training and evaluation. + +### Training and Inference Runners + +The [`deeplabcut.pose_estimation_pytorch.runners`][] module provides classes for model training and inference: + +- [`PoseTrainingRunner`][deeplabcut.pose_estimation_pytorch.runners.PoseTrainingRunner] - Handles the training loop +- [`PoseInferenceRunner`][deeplabcut.pose_estimation_pytorch.runners.PoseInferenceRunner] - Runs pose estimation inference +- [`DetectorInferenceRunner`][deeplabcut.pose_estimation_pytorch.runners.DetectorInferenceRunner] - Runs object detection for top-down models diff --git a/deeplabcut/pose_estimation_pytorch/docs/inference.md b/deeplabcut/pose_estimation_pytorch/docs/inference.md index 48d9ecc8bf..91a0e9fe15 100644 --- a/deeplabcut/pose_estimation_pytorch/docs/inference.md +++ b/deeplabcut/pose_estimation_pytorch/docs/inference.md @@ -1 +1,104 @@ # Inference and Video Analysis + +The [`deeplabcut.pose_estimation_pytorch`][] package provides APIs for running inference on images and videos. + +## Running Video Analysis outside a DeepLabCut Project + +DeepLabCut provides high-level APIs (via the GUI or the python package) to analyze your data. The usage of this API assumes the existence of a DLC project (with `config.yaml` file, etc.). + +Sometimes it might be more convenient to just run a model on your data via a low-level API. We also use this API under the hood, in particular for the Model Zoo. Check out the example below: + +```python +from deeplabcut.core.config import read_config_as_dict +from pathlib import Path + +import deeplabcut.pose_estimation_pytorch as dlc_torch + +train_dir = Path("/Users/Jaylen/my-dlc-models/train") +pytorch_config_path = train_dir / "pytorch_config.yaml" +snapshot_path = train_dir / "snapshot-100.pt" + +# for top-down models, otherwise None +detector_snapshot_path = train_dir / "detector-snapshot-100.pt" + +# video and inference parameters +video_path = Path("/Users/Jaylen/my-dlc-models/videos/test-video.mp4") +max_num_animals = 5 +batch_size = 16 +detector_batch_size = 8 + +# read model configuration +model_cfg = read_config_as_dict(pytorch_config_path) +pose_task = dlc_torch.Task(model_cfg["method"]) +pose_runner = dlc_torch.get_pose_inference_runner( + model_config=model_cfg, + snapshot_path=snapshot_path, + max_individuals=max_num_animals, + batch_size=batch_size, +) + +detector_runner = None +if pose_task == dlc_torch.Task.TOP_DOWN: + detector_runner = dlc_torch.get_detector_inference_runner( + model_config=model_cfg, + snapshot_path=detector_snapshot_path, + max_individuals=max_num_animals, + batch_size=detector_batch_size, + ) + +predictions = dlc_torch.video_inference( + video=video_path, + pose_runner=pose_runner, + detector_runner=detector_runner, +) +``` + +## Running Top-Down Video Analysis with Existing Bounding Boxes + +When `deeplabcut.pose_estimation_pytorch.apis.videos.video_inference` is called with a top-down model, it is assumed that a detector snapshot is given as well to obtain bounding boxes with which to run pose estimation. It's possible that you've already obtained bounding boxes for your video (with another object detector or through some other means), and you want to reuse those bounding boxes instead of running an object detector again. + +You can easily do so by writing a bit of custom code, as shown in the example below: + + + +```python +from deeplabcut.core.config import read_config_as_dict +from pathlib import Path + +import numpy as np +import deeplabcut.pose_estimation_pytorch as dlc_torch +from tqdm import tqdm + +# create an iterator for your video +video = dlc_torch.VideoIterator("/Users/Jayson/my-cool-video.mp4") + +# dummy bboxes - you can load yours from a file or in another way +# the bboxes should be in `xywh` format, i.e. (x_top_left, y_top_left, width, height) +bounding_boxes = [ + dict( # frame 0 bounding boxes + bboxes=np.array([[12, 37, 120, 78]]), + ), + dict( # frame 1 bounding boxes + bboxes=np.array([[17, 45, 128, 73], [532, 34, 117, 87]]), + ), + # ... + dict( # frame N bboxes -> must be equal to the number of frames in the video! + bboxes=np.array([[17, 45, 128, 73], [532, 34, 117, 87]]), + ), +] +video.set_context(bounding_boxes) +max_individuals = np.max([len(context["bboxes"]) for context in bounding_boxes]) + +# run inference! +model_cfg = read_config_as_dict("/Users/Jayson/pytorch_config.yaml") +pose_runner = dlc_torch.get_pose_inference_runner( + model_config=model_cfg, + snapshot_path=Path("/Users/Jayson/model-snapshot.pt"), + max_individuals=max_individuals, + batch_size=32, +) + +# your predictions will be a list, containing the predictions made for each frame +# as a dict (with keys for "bodyparts" but also "bboxes")! +predictions = pose_runner.inference(images=tqdm(video)) +``` diff --git a/deeplabcut/pose_estimation_pytorch/docs/models.md b/deeplabcut/pose_estimation_pytorch/docs/models.md index dc34891ec2..4987095769 100644 --- a/deeplabcut/pose_estimation_pytorch/docs/models.md +++ b/deeplabcut/pose_estimation_pytorch/docs/models.md @@ -1 +1,89 @@ # Model Architecture + +The [`deeplabcut.pose_estimation_pytorch.models`][] package provides a modular framework for building pose estimation models. Models are composed of several key components that can be mixed and matched to create custom architectures. + +## Model Components + +DeepLabCut models are built from the following modular components: + +### Backbones + +Backbones are feature extraction networks that process input images and produce multi-scale feature representations. The [`deeplabcut.pose_estimation_pytorch.models.backbones`][] module provides several state-of-the-art backbone architectures. + +**Available Backbones:** + +- **ResNet** (ResNet-50, ResNet-101): Deep residual networks +- **HRNet** (HRNet-W18, HRNet-W32, HRNet-W48): High-resolution networks that maintain high-resolution representations throughout +- **CSPNext** (CSPNext-S, CSPNext-M, CSPNext-X): Cross Stage Partial networks for efficient feature extraction + +All backbones inherit from [`BaseBackbone`][deeplabcut.pose_estimation_pytorch.models.backbones.BaseBackbone] and must define a stride property indicating the downsampling factor. + +**Example:** +```python +from deeplabcut.pose_estimation_pytorch.models.backbones import BACKBONES + +# Build a backbone from configuration +backbone_config = dict(type="HRNet", variant="w32") +backbone = BACKBONES.build(backbone_config) +``` + +### Necks + +Necks are optional intermediate layers between the backbone and head. They typically perform feature aggregation, refinement, or transformation. The [`deeplabcut.pose_estimation_pytorch.models.necks`][] module provides various neck architectures. + +All necks inherit from [`BaseNeck`][deeplabcut.pose_estimation_pytorch.models.necks.BaseNeck]. + +### Heads + +Heads are task-specific output layers that produce the final predictions. The [`deeplabcut.pose_estimation_pytorch.models.heads`][] module contains various head architectures for different pose estimation approaches. + +Each head contains: +- A **predictor** to convert model outputs to keypoint locations +- A **target generator** to create training targets from annotations +- A **criterion** to compute the loss +- An **aggregator** to combine multiple losses + +All heads inherit from [`BaseHead`][deeplabcut.pose_estimation_pytorch.models.heads.BaseHead] and output a dictionary mapping output names to tensors. + +**Example:** +```python +from deeplabcut.pose_estimation_pytorch.models.heads import HEADS + +head_config = dict( + type="HeatmapHead", + predictor=dict(type="HeatmapPredictor", location_refinement=True, locref_std=7.2801), + target_generator=dict( + type="HeatmapGaussianGenerator", + num_heatmaps=17, + pos_dist_thresh=17, + generate_locref=True, + ), + criterion=dict(type="WeightedMSECriterion"), + aggregator=None, + heatmap_config=dict( + num_input_channels=256, + num_output_channels=17, + num_deconv_layers=1, + ), +) +head = HEADS.build(head_config) +``` + +### Predictors + +Predictors transform model outputs into final keypoint predictions. The [`deeplabcut.pose_estimation_pytorch.models.predictors`][] module provides various prediction strategies. + +**Available Predictors:** + +- [`HeatmapPredictor`][deeplabcut.pose_estimation_pytorch.models.predictors.HeatmapPredictor]: Extract keypoints from heatmaps +- [`DEKRPredictor`][deeplabcut.pose_estimation_pytorch.models.predictors.DEKRPredictor]: DEKR-style predictions with offsets +- [`SimCCPredictor`][deeplabcut.pose_estimation_pytorch.models.predictors.SimCCPredictor]: SimCC coordinate classification +- [`PartAffinityFieldPredictor`][deeplabcut.pose_estimation_pytorch.models.predictors.PartAffinityFieldPredictor]: Bottom-up PAF-based assembly + +All predictors inherit from [`BasePredictor`][deeplabcut.pose_estimation_pytorch.models.predictors.BasePredictor]. + +### Target Generators + +Target generators create training targets from ground truth annotations. The [`deeplabcut.pose_estimation_pytorch.models.target_generators`][] module provides generators for different output types. + +All target generators inherit from [`BaseGenerator`][deeplabcut.pose_estimation_pytorch.models.target_generators.BaseGenerator]. \ No newline at end of file diff --git a/deeplabcut/pose_estimation_pytorch/docs/quickstart.md b/deeplabcut/pose_estimation_pytorch/docs/quickstart.md index 05cf8c1fd0..a4e4a784c2 100644 --- a/deeplabcut/pose_estimation_pytorch/docs/quickstart.md +++ b/deeplabcut/pose_estimation_pytorch/docs/quickstart.md @@ -1 +1,69 @@ # Quick Start + +## Standard API (High-Level) + +The high-level API remains unchanged. PyTorch models work seamlessly with existing DeepLabCut workflows: + +```python +import deeplabcut + +config = "/path/to/project/config.yaml" + +# Standard workflow works with both engines +deeplabcut.create_training_dataset(config) +deeplabcut.train_network(config, shuffle=1) +deeplabcut.evaluate_network(config, shuffle=[1]) +deeplabcut.analyze_videos(config, videos=["/path/to/video.mp4"]) +``` + +See the [standard guide](https://deeplabcut.github.io/DeepLabCut/docs/standardDeepLabCut_UserGuide) for single-animal projects and [multi-animal guide](https://deeplabcut.github.io/DeepLabCut/docs/maDLC_UserGuide) for multi-animal tracking. + +## Low-Level PyTorch API + +For advanced usage, directly interact with the PyTorch backend: + +```python +import deeplabcut.pose_estimation_pytorch as dlc_torch + +# Create a data loader +loader = dlc_torch.DLCLoader( + config="/path/to/project/config.yaml", + trainset_index=0, + shuffle=1, +) + +# Train with custom configuration +dlc_torch.train( + loader=loader, + run_config=loader.model_cfg, + task=loader.pose_task, + device="cuda", + max_epochs=200, + batch_size=8, +) + +# Run inference +predictions = dlc_torch.analyze_videos( + config="/path/to/project/config.yaml", + videos=["/path/to/video.mp4"], + shuffle=1, +) +``` + +## Listing Available Models + +Query supported architectures: + +```python +from deeplabcut.pose_estimation_pytorch import available_models, available_detectors + +# List all pose estimation models +models = available_models() +print(models) +# Output: ['dlcrnet_ms5', 'hrnet_w18', 'hrnet_w32', 'hrnet_w48', 'dekr_w32', ...] + +# List all object detectors (for top-down models) +detectors = available_detectors() +print(detectors) +# Output: ['fasterrcnn_resnet50_fpn', 'fasterrcnn_mobilenet_v3_large_fpn', 'yolox_s', ...] +``` \ No newline at end of file diff --git a/deeplabcut/pose_estimation_pytorch/docs/training.md b/deeplabcut/pose_estimation_pytorch/docs/training.md index 7a3bf0b704..d47f527c95 100644 --- a/deeplabcut/pose_estimation_pytorch/docs/training.md +++ b/deeplabcut/pose_estimation_pytorch/docs/training.md @@ -1 +1,53 @@ # Training Models + +The [`deeplabcut.pose_estimation_pytorch`][] package provides tools for training pose estimation models. + +## Training on a DeepLabCut Project + +For standard DeepLabCut projects, use [`train_network`][deeplabcut.pose_estimation_pytorch.apis.train_network]: + +```python +import deeplabcut + +deeplabcut.train_network( + config="/path/to/project/config.yaml", + shuffle=1, + trainingsetindex=0, + max_epochs=200, + displayiters=100, + saveiters=10000, +) +``` + +## Training on COCO Datasets + +You can train directly on COCO-format datasets: + +```python +from pathlib import Path +import deeplabcut.pose_estimation_pytorch as dlc_torch + +# Specify project paths +project_root = Path("/path/to/my/COCOProject") +train_json_filename = "train.json" +test_json_filename = "test.json" + +loader = dlc_torch.COCOLoader( + project_root=project_root, + model_config_path="/path/to/my/project/experiments/pytorch_config.yaml", + train_json_filename=train_json_filename, + test_json_filename=test_json_filename, +) +dlc_torch.train( + loader=loader, + run_config=loader.model_cfg, + task=dlc_torch.Task(loader.model_cfg["method"]), + device="cuda:2", + logger_config=dict( + type="WandbLogger", + project_name="MyWandbProject", + tags=["model=hrnet_w32"], + ), + snapshot_path=None, +) +``` From 4d3623e4992a132260948b4894bd8d961f2116d3 Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter Date: Tue, 19 May 2026 14:48:12 +0200 Subject: [PATCH 04/14] dev docs: fix CI build dev docs --- .github/workflows/publish-book.yml | 54 ++++++++++++++----- .../docs/theme/.gitkeep | 0 pyproject.toml | 9 ++++ 3 files changed, 51 insertions(+), 12 deletions(-) create mode 100644 deeplabcut/pose_estimation_pytorch/docs/theme/.gitkeep diff --git a/.github/workflows/publish-book.yml b/.github/workflows/publish-book.yml index c0d815266a..ebe5070cf3 100644 --- a/.github/workflows/publish-book.yml +++ b/.github/workflows/publish-book.yml @@ -16,30 +16,60 @@ jobs: upload_artifact: true secrets: inherit + build-dev-docs: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v6 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.10" + + - name: Install system dependencies for mkdocs-material social plugin + run: | + sudo apt-get update -qq + sudo apt-get install -y libcairo2-dev libfreetype6-dev libffi-dev libjpeg-dev libpng-dev libz-dev + + - name: Install developer docs dependencies + run: | + python -m pip install --upgrade pip + python -m pip install ".[dev-docs]" + + - name: Build developer docs + run: | + mkdocs build -v -f deeplabcut/pose_estimation_pytorch/mkdocs.yml --site-dir site + + - name: Upload developer docs artifact + uses: actions/upload-artifact@v6 + with: + name: built-dev-docs + path: site + if-no-files-found: error + retention-days: 1 + deploy: - needs: build + needs: [build, build-dev-docs] runs-on: ubuntu-latest permissions: contents: write steps: - - name: Download built site artifact + - name: Download built Jupyter Book artifact uses: actions/download-artifact@v4 with: name: built-book path: site + - name: Download built developer docs artifact + uses: actions/download-artifact@v4 + with: + name: built-dev-docs + path: site/dev + - name: Deploy via gh-pages branch uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: site - - - name: Build developer docs - run: | - mkdocs build -v -f deeplabcut/pose_estimation_pytorch/mkdocs.yml --site-dir ../../_build/html/dev - - - name: GitHub Pages action - uses: peaceiris/actions-gh-pages@v3.9.3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./_build/html diff --git a/deeplabcut/pose_estimation_pytorch/docs/theme/.gitkeep b/deeplabcut/pose_estimation_pytorch/docs/theme/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pyproject.toml b/pyproject.toml index 17b761a512..127d2956d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,6 +79,15 @@ docs = [ "numpydoc", "sphinxcontrib-mermaid", ] +dev-docs = [ + "mike>=2.1", + "mkdocs>=1.6", + "mkdocs-api-autonav>=0.1", + "mkdocs-autorefs>=1.2", + "mkdocs-jupyter>=0.25", + "mkdocs-material[imaging]>=9.5", + "mkdocstrings[python]>=0.27", +] fmpose3d = [ "fmpose3d>=0.0.8" ] # Use only one of [tf, tf-cu11, tf-cu12, tf-latest]. Do not combine extras. tf = [ From 6e39a209e12b3330bc52fdb5dd0da877dcaa6978 Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter Date: Tue, 19 May 2026 16:12:39 +0200 Subject: [PATCH 05/14] dev docs: move to project root --- .github/workflows/publish-book.yml | 4 ++-- .../docs/assets/favicon.ico | Bin .../docs/assets/javascripts/.gitkeep | 0 .../docs/assets/logo.png | Bin .../docs/assets/stylesheets/.gitkeep | 0 .../docs/configuration.md | 2 +- .../docs/custom_models.md | 2 +- .../docs/dataprep.md | 0 .../docs/devguides.md | 4 ++-- .../docs/examples.md | 1 - .../docs/index.md | 0 .../docs/inference.md | 0 .../docs/installation.md | 1 - .../docs/models.md | 2 +- .../docs/quickstart.md | 2 +- .../docs/theme/.gitkeep | 0 .../docs/tracking.md | 0 .../docs/training.md | 0 .../mkdocs.yml | 15 +++++++++++---- 19 files changed, 19 insertions(+), 14 deletions(-) rename {deeplabcut/pose_estimation_pytorch => dev-docs}/docs/assets/favicon.ico (100%) rename {deeplabcut/pose_estimation_pytorch => dev-docs}/docs/assets/javascripts/.gitkeep (100%) rename {deeplabcut/pose_estimation_pytorch => dev-docs}/docs/assets/logo.png (100%) rename {deeplabcut/pose_estimation_pytorch => dev-docs}/docs/assets/stylesheets/.gitkeep (100%) rename {deeplabcut/pose_estimation_pytorch => dev-docs}/docs/configuration.md (99%) rename {deeplabcut/pose_estimation_pytorch => dev-docs}/docs/custom_models.md (94%) rename {deeplabcut/pose_estimation_pytorch => dev-docs}/docs/dataprep.md (100%) rename {deeplabcut/pose_estimation_pytorch => dev-docs}/docs/devguides.md (99%) rename {deeplabcut/pose_estimation_pytorch => dev-docs}/docs/examples.md (99%) rename {deeplabcut/pose_estimation_pytorch => dev-docs}/docs/index.md (100%) rename {deeplabcut/pose_estimation_pytorch => dev-docs}/docs/inference.md (100%) rename {deeplabcut/pose_estimation_pytorch => dev-docs}/docs/installation.md (93%) rename {deeplabcut/pose_estimation_pytorch => dev-docs}/docs/models.md (99%) rename {deeplabcut/pose_estimation_pytorch => dev-docs}/docs/quickstart.md (99%) rename {deeplabcut/pose_estimation_pytorch => dev-docs}/docs/theme/.gitkeep (100%) rename {deeplabcut/pose_estimation_pytorch => dev-docs}/docs/tracking.md (100%) rename {deeplabcut/pose_estimation_pytorch => dev-docs}/docs/training.md (100%) rename {deeplabcut/pose_estimation_pytorch => dev-docs}/mkdocs.yml (93%) diff --git a/.github/workflows/publish-book.yml b/.github/workflows/publish-book.yml index ebe5070cf3..bf838c73a3 100644 --- a/.github/workflows/publish-book.yml +++ b/.github/workflows/publish-book.yml @@ -40,13 +40,13 @@ jobs: - name: Build developer docs run: | - mkdocs build -v -f deeplabcut/pose_estimation_pytorch/mkdocs.yml --site-dir site + mkdocs build -v -f dev-docs/mkdocs.yml - name: Upload developer docs artifact uses: actions/upload-artifact@v6 with: name: built-dev-docs - path: site + path: _build/dev if-no-files-found: error retention-days: 1 diff --git a/deeplabcut/pose_estimation_pytorch/docs/assets/favicon.ico b/dev-docs/docs/assets/favicon.ico similarity index 100% rename from deeplabcut/pose_estimation_pytorch/docs/assets/favicon.ico rename to dev-docs/docs/assets/favicon.ico diff --git a/deeplabcut/pose_estimation_pytorch/docs/assets/javascripts/.gitkeep b/dev-docs/docs/assets/javascripts/.gitkeep similarity index 100% rename from deeplabcut/pose_estimation_pytorch/docs/assets/javascripts/.gitkeep rename to dev-docs/docs/assets/javascripts/.gitkeep diff --git a/deeplabcut/pose_estimation_pytorch/docs/assets/logo.png b/dev-docs/docs/assets/logo.png similarity index 100% rename from deeplabcut/pose_estimation_pytorch/docs/assets/logo.png rename to dev-docs/docs/assets/logo.png diff --git a/deeplabcut/pose_estimation_pytorch/docs/assets/stylesheets/.gitkeep b/dev-docs/docs/assets/stylesheets/.gitkeep similarity index 100% rename from deeplabcut/pose_estimation_pytorch/docs/assets/stylesheets/.gitkeep rename to dev-docs/docs/assets/stylesheets/.gitkeep diff --git a/deeplabcut/pose_estimation_pytorch/docs/configuration.md b/dev-docs/docs/configuration.md similarity index 99% rename from deeplabcut/pose_estimation_pytorch/docs/configuration.md rename to dev-docs/docs/configuration.md index e384466b77..e03ee3b6c4 100644 --- a/deeplabcut/pose_estimation_pytorch/docs/configuration.md +++ b/dev-docs/docs/configuration.md @@ -92,4 +92,4 @@ resume_training_from: /path/to/model/train/snapshot-010.pt - Inference Configuration: configure inference-specific behavior independently of training: -- Top-Down Detector Configuration: Top-down models require a separate detector configuration: \ No newline at end of file +- Top-Down Detector Configuration: Top-down models require a separate detector configuration: diff --git a/deeplabcut/pose_estimation_pytorch/docs/custom_models.md b/dev-docs/docs/custom_models.md similarity index 94% rename from deeplabcut/pose_estimation_pytorch/docs/custom_models.md rename to dev-docs/docs/custom_models.md index 4acc2cf484..b23a152d20 100644 --- a/deeplabcut/pose_estimation_pytorch/docs/custom_models.md +++ b/dev-docs/docs/custom_models.md @@ -10,4 +10,4 @@ The registry system allows you to register custom components that can be instant - [`NECKS`][deeplabcut.pose_estimation_pytorch.models.necks] - Intermediate processing layers - [`HEADS`][deeplabcut.pose_estimation_pytorch.models.heads] - Task-specific output layers - [`PREDICTORS`][deeplabcut.pose_estimation_pytorch.models.predictors] - Output-to-keypoint converters -- [`TARGET_GENERATORS`][deeplabcut.pose_estimation_pytorch.models.target_generators] - Annotation-to-target converters \ No newline at end of file +- [`TARGET_GENERATORS`][deeplabcut.pose_estimation_pytorch.models.target_generators] - Annotation-to-target converters diff --git a/deeplabcut/pose_estimation_pytorch/docs/dataprep.md b/dev-docs/docs/dataprep.md similarity index 100% rename from deeplabcut/pose_estimation_pytorch/docs/dataprep.md rename to dev-docs/docs/dataprep.md diff --git a/deeplabcut/pose_estimation_pytorch/docs/devguides.md b/dev-docs/docs/devguides.md similarity index 99% rename from deeplabcut/pose_estimation_pytorch/docs/devguides.md rename to dev-docs/docs/devguides.md index a91de9d617..4714191dd8 100644 --- a/deeplabcut/pose_estimation_pytorch/docs/devguides.md +++ b/dev-docs/docs/devguides.md @@ -111,7 +111,7 @@ class MyBackbone(BaseBackbone): def __init__(self, ...): super().__init__(stride=32) # Implementation - + def forward(self, x): # Forward pass return x @@ -178,4 +178,4 @@ Runners encapsulate training and inference logic: - [`PoseInferenceRunner`][deeplabcut.pose_estimation_pytorch.runners.PoseInferenceRunner]: Handles pose estimation - [`DetectorInferenceRunner`][deeplabcut.pose_estimation_pytorch.runners.DetectorInferenceRunner]: Handles object detection -Runners can be created from configuration or instantiated directly for custom workflows. \ No newline at end of file +Runners can be created from configuration or instantiated directly for custom workflows. diff --git a/deeplabcut/pose_estimation_pytorch/docs/examples.md b/dev-docs/docs/examples.md similarity index 99% rename from deeplabcut/pose_estimation_pytorch/docs/examples.md rename to dev-docs/docs/examples.md index a5d38a0be7..2cdc537d99 100644 --- a/deeplabcut/pose_estimation_pytorch/docs/examples.md +++ b/dev-docs/docs/examples.md @@ -132,4 +132,3 @@ pose_runner = dlc_torch.get_pose_inference_runner( # as a dict (with keys for "bodyparts" but also "bboxes")! predictions = pose_runner.inference(images=tqdm(video)) ``` - diff --git a/deeplabcut/pose_estimation_pytorch/docs/index.md b/dev-docs/docs/index.md similarity index 100% rename from deeplabcut/pose_estimation_pytorch/docs/index.md rename to dev-docs/docs/index.md diff --git a/deeplabcut/pose_estimation_pytorch/docs/inference.md b/dev-docs/docs/inference.md similarity index 100% rename from deeplabcut/pose_estimation_pytorch/docs/inference.md rename to dev-docs/docs/inference.md diff --git a/deeplabcut/pose_estimation_pytorch/docs/installation.md b/dev-docs/docs/installation.md similarity index 93% rename from deeplabcut/pose_estimation_pytorch/docs/installation.md rename to dev-docs/docs/installation.md index 4026317a86..25267fe2b7 100644 --- a/deeplabcut/pose_estimation_pytorch/docs/installation.md +++ b/dev-docs/docs/installation.md @@ -1,2 +1 @@ # Installation - diff --git a/deeplabcut/pose_estimation_pytorch/docs/models.md b/dev-docs/docs/models.md similarity index 99% rename from deeplabcut/pose_estimation_pytorch/docs/models.md rename to dev-docs/docs/models.md index 4987095769..50bd0027d4 100644 --- a/deeplabcut/pose_estimation_pytorch/docs/models.md +++ b/dev-docs/docs/models.md @@ -86,4 +86,4 @@ All predictors inherit from [`BasePredictor`][deeplabcut.pose_estimation_pytorch Target generators create training targets from ground truth annotations. The [`deeplabcut.pose_estimation_pytorch.models.target_generators`][] module provides generators for different output types. -All target generators inherit from [`BaseGenerator`][deeplabcut.pose_estimation_pytorch.models.target_generators.BaseGenerator]. \ No newline at end of file +All target generators inherit from [`BaseGenerator`][deeplabcut.pose_estimation_pytorch.models.target_generators.BaseGenerator]. diff --git a/deeplabcut/pose_estimation_pytorch/docs/quickstart.md b/dev-docs/docs/quickstart.md similarity index 99% rename from deeplabcut/pose_estimation_pytorch/docs/quickstart.md rename to dev-docs/docs/quickstart.md index a4e4a784c2..9235e2781b 100644 --- a/deeplabcut/pose_estimation_pytorch/docs/quickstart.md +++ b/dev-docs/docs/quickstart.md @@ -66,4 +66,4 @@ print(models) detectors = available_detectors() print(detectors) # Output: ['fasterrcnn_resnet50_fpn', 'fasterrcnn_mobilenet_v3_large_fpn', 'yolox_s', ...] -``` \ No newline at end of file +``` diff --git a/deeplabcut/pose_estimation_pytorch/docs/theme/.gitkeep b/dev-docs/docs/theme/.gitkeep similarity index 100% rename from deeplabcut/pose_estimation_pytorch/docs/theme/.gitkeep rename to dev-docs/docs/theme/.gitkeep diff --git a/deeplabcut/pose_estimation_pytorch/docs/tracking.md b/dev-docs/docs/tracking.md similarity index 100% rename from deeplabcut/pose_estimation_pytorch/docs/tracking.md rename to dev-docs/docs/tracking.md diff --git a/deeplabcut/pose_estimation_pytorch/docs/training.md b/dev-docs/docs/training.md similarity index 100% rename from deeplabcut/pose_estimation_pytorch/docs/training.md rename to dev-docs/docs/training.md diff --git a/deeplabcut/pose_estimation_pytorch/mkdocs.yml b/dev-docs/mkdocs.yml similarity index 93% rename from deeplabcut/pose_estimation_pytorch/mkdocs.yml rename to dev-docs/mkdocs.yml index d1493a983a..27ba95362c 100644 --- a/deeplabcut/pose_estimation_pytorch/mkdocs.yml +++ b/dev-docs/mkdocs.yml @@ -1,6 +1,7 @@ site_name: DeepLabCut Developer Documentation site_description: Developer documentation for DeepLabCut site_url: https://deeplabcut.github.io/DeepLabCut/dev +site_dir: "../_build/dev" repo_name: DeepLabCut/DeepLabCut repo_url: https://github.com/DeepLabCut/DeepLabCut @@ -8,8 +9,8 @@ repo_url: https://github.com/DeepLabCut/DeepLabCut theme: name: material custom_dir: "docs/theme" - logo: assets/logo.png - favicon: assets/favicon.ico + logo: docs/assets/logo.png + favicon: docs/assets/favicon.ico palette: - media: "(prefers-color-scheme: light)" @@ -50,9 +51,14 @@ plugins: - mike # for versioning - mkdocs-jupyter # adds support for rendering Jupyter Notebooks - api-autonav: # automatically generates API reference navigation based on the file structure - modules: ["../"] + modules: + - "../deeplabcut" module_options: {} - exclude: [] + exclude: + - "deeplabcut.version" + - "deeplabcut.cli" + - "deeplabcut.benchmark" + - "deeplabcut.gui" nav_section_title: "API Reference" api_root_uri: "reference" nav_item_prefix: "" @@ -63,6 +69,7 @@ plugins: default_handler: python handlers: python: + paths: [".."] # repo root, so `import deeplabcut` resolves correctly options: summary: true show_root_heading: true From a08654bfc968a174cf4cbad4748658aab3ff327b Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter Date: Tue, 19 May 2026 16:24:06 +0200 Subject: [PATCH 06/14] dev docs: remove incomplete guides from index --- dev-docs/mkdocs.yml | 8 ++++---- docs/dev.md | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dev-docs/mkdocs.yml b/dev-docs/mkdocs.yml index 27ba95362c..bed8c86783 100644 --- a/dev-docs/mkdocs.yml +++ b/dev-docs/mkdocs.yml @@ -111,17 +111,17 @@ copyright: "Copyright © 2026 The DeepLabCut Team" nav: - Get Started: - Overview: index.md - - Installation: installation.md + # - Installation: installation.md - Quick Start: quickstart.md - Developer Guides: - Overview: devguides.md - Model Architecture: models.md - - Configuration Files: configuration.md + # - Configuration Files: configuration.md - Data Preparation: dataprep.md - Training Models: training.md - Inference & Analysis: inference.md - - Adding Custom Models: custom_models.md - - Multi-animal Tracking: tracking.md + # - Adding Custom Models: custom_models.md + # - Multi-animal Tracking: tracking.md - Examples: - Practical Examples: examples.md - API Reference diff --git a/docs/dev.md b/docs/dev.md index 945f98066e..678a867867 100644 --- a/docs/dev.md +++ b/docs/dev.md @@ -2,4 +2,4 @@ -If you are not redirected automatically, please go to [Developer docs](dev/index/). \ No newline at end of file +If you are not redirected automatically, please go to [Developer docs](../dev/index.html). From 2709a8258c1012ccccb6679a3a0a9dee73368ebf Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter Date: Tue, 19 May 2026 16:25:05 +0200 Subject: [PATCH 07/14] dev docs: update configuration and custom_models sections [WIP] --- dev-docs/docs/configuration.md | 152 +++++++++++++++++++++++--- dev-docs/docs/custom_models.md | 188 +++++++++++++++++++++++++++++++++ 2 files changed, 327 insertions(+), 13 deletions(-) diff --git a/dev-docs/docs/configuration.md b/dev-docs/docs/configuration.md index e03ee3b6c4..c1a656ad69 100644 --- a/dev-docs/docs/configuration.md +++ b/dev-docs/docs/configuration.md @@ -63,33 +63,159 @@ model_cfg = dlc_torch.config.make_pytorch_pose_config( ## Configuration File Components -A complete `pytorch_cfg.yaml` file contains several sections: +A complete `pytorch_cfg.yaml` file contains the following sections. -- Model Architecture: specifies the backbone, neck, and head components: +### Model Architecture -- Training Parameters: Defines the optimization strategy and training loop settings: +Specifies the backbone, optional neck, and head: -- Data Configuration: Specifies data augmentation and preprocessing for training and inference: +```yaml +model: + backbone: + type: HRNet + variant: w32 + neck: null # omit or set to null for no neck + head: + type: HeatmapHead + weight_init: normal + predictor: + type: HeatmapPredictor + location_refinement: true + locref_std: 7.2801 + target_generator: + type: HeatmapGaussianGenerator + num_heatmaps: "num_bodyparts" + pos_dist_thresh: 17 + generate_locref: true + criterion: + heatmap: + type: WeightedMSECriterion + weight: 1.0 + locref: + type: WeightedHuberCriterion + weight: 0.05 +``` -- Data Augmentation Options: transformations are available for the `train` and `inference` configurations. Most augmentations should only be applied during training, not inference. +### Data Configuration -- Training Settings: Training-specific parameters control batch size, epochs, and data loading: +Controls data loading, augmentation, and preprocessing. Augmentations under `train` are applied only during training; `inference` augmentations are applied during evaluation and video analysis: -- Runner Configuration: The runner manages training execution, optimization, and checkpointing: +```yaml +data: + colormode: RGB # RGB or GRAY + bbox_margin: 20 # pixels added around bounding boxes (top-down only) + train: + normalize_images: true + crop_sampling: + width: 448 + height: 448 + max_shift: 0.1 + method: hybrid + affine: + p: 0.5 + rotation: 30 + scaling: [0.5, 1.25] + translation: 0 + gaussian_noise: 12.75 + motion_blur: true + hflip: true + inference: + normalize_images: true +``` -- Optimizers +### Training Settings -- Learning Rate Schedulers: use any scheduler from [`torch.optim.lr_scheduler`](https://pytorch.org/docs/stable/optim.html#how-to-adjust-learning-rate): +Controls the training loop — batch size, number of epochs, data loading, and random seed: -- Logging: training runs are logged locally by default. Optionally integrate with [Weights & Biases](https://wandb.ai/site): +```yaml +train_settings: + batch_size: 8 + epochs: 200 + seed: 42 + dataloader_workers: 4 + dataloader_pin_memory: true + display_iters: 500 +``` +### Runner Configuration -- Resuming Training: resume from a specific checkpoint by specifying its path: +The runner manages the training loop, optimisation, checkpointing, and evaluation. Use any optimizer from [`torch.optim`](https://pytorch.org/docs/stable/optim.html) and any scheduler from [`torch.optim.lr_scheduler`](https://pytorch.org/docs/stable/optim.html#how-to-adjust-learning-rate): + +```yaml +runner: + type: PoseTrainingRunner + gpus: null # null = use device setting; list of ints for multi-GPU + key_metric: "test.mAP" + key_metric_asc: true # true if higher is better + eval_interval: 10 # evaluate every N epochs + + optimizer: + type: AdamW + params: + lr: 0.0001 + weight_decay: 0.01 + + scheduler: + type: LRListScheduler + params: + milestones: [160, 190] + lr_list: [[1e-5], [1e-6]] + + snapshots: + max_snapshots: 5 # keep only the N most recent snapshots + save_epochs: 25 # save a snapshot every N epochs + save_optimizer_state: false + + logger: + type: WandbLogger # omit or set to null for local-only logging + project_name: my-project + tags: ["model=hrnet_w32"] +``` + +### Resuming Training + +Resume from a specific snapshot by setting: ```yaml resume_training_from: /path/to/model/train/snapshot-010.pt ``` -- Inference Configuration: configure inference-specific behavior independently of training: +### Inference Configuration + +Controls inference-specific behaviour, set independently of training augmentations: + +```yaml +method: td # bu = bottom-up, td = top-down, ctd = conditional top-down +device: auto # auto, cpu, cuda, or cuda:N +``` -- Top-Down Detector Configuration: Top-down models require a separate detector configuration: +### Top-Down Detector Configuration + +Top-down models require a separate detector. The detector `pytorch_cfg.yaml` mirrors the pose model structure but uses `DetectorTrainingRunner`: + +```yaml +runner: + type: DetectorTrainingRunner + key_metric: "test.mAP@50:95" + key_metric_asc: true + eval_interval: 10 + optimizer: + type: AdamW + params: + lr: 1e-4 + scheduler: + type: LRListScheduler + params: + milestones: [160] + lr_list: [[1e-5]] + snapshots: + max_snapshots: 5 + save_epochs: 25 + save_optimizer_state: false + +train_settings: + batch_size: 1 + epochs: 250 + dataloader_workers: 0 + display_iters: 500 +``` diff --git a/dev-docs/docs/custom_models.md b/dev-docs/docs/custom_models.md index b23a152d20..916bd0b865 100644 --- a/dev-docs/docs/custom_models.md +++ b/dev-docs/docs/custom_models.md @@ -11,3 +11,191 @@ The registry system allows you to register custom components that can be instant - [`HEADS`][deeplabcut.pose_estimation_pytorch.models.heads] - Task-specific output layers - [`PREDICTORS`][deeplabcut.pose_estimation_pytorch.models.predictors] - Output-to-keypoint converters - [`TARGET_GENERATORS`][deeplabcut.pose_estimation_pytorch.models.target_generators] - Annotation-to-target converters + +## Custom Backbones + +A backbone extracts features from input images. Subclass [`BaseBackbone`][deeplabcut.pose_estimation_pytorch.models.backbones.BaseBackbone] and decorate with `@BACKBONES.register_module`: + +```python +import torch +import torch.nn as nn +from deeplabcut.pose_estimation_pytorch.models.backbones import BACKBONES, BaseBackbone + + +@BACKBONES.register_module +class MyBackbone(BaseBackbone): + def __init__(self, out_channels: int = 256, pretrained: bool = False): + # stride is the total downsampling factor of the network + super().__init__(stride=32) + self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3) + self.conv2 = nn.Conv2d(64, out_channels, kernel_size=3, stride=2, padding=1) + # ... rest of architecture + + def forward(self, x: torch.Tensor) -> torch.Tensor: + x = self.conv1(x) + x = self.conv2(x) + return x +``` + +Use the custom backbone in a configuration file: + +```yaml +model: + backbone: + type: MyBackbone + out_channels: 256 + pretrained: false +``` + +## Custom Necks + +A neck processes the backbone's feature map before it reaches the head. Subclass [`BaseNeck`][deeplabcut.pose_estimation_pytorch.models.necks.BaseNeck] and register with `@NECKS.register_module`: + +```python +import torch +import torch.nn as nn +from deeplabcut.pose_estimation_pytorch.models.necks import NECKS, BaseNeck + + +@NECKS.register_module +class MyNeck(BaseNeck): + def __init__(self, in_channels: int = 256, out_channels: int = 256): + super().__init__() + self.project = nn.Conv2d(in_channels, out_channels, kernel_size=1) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + return self.project(x) +``` + +Reference in configuration: + +```yaml +model: + neck: + type: MyNeck + in_channels: 256 + out_channels: 128 +``` + +## Custom Heads + +A head produces task-specific predictions from the neck's or backbone's output. Subclass [`BaseHead`][deeplabcut.pose_estimation_pytorch.models.heads.BaseHead] and register with `@HEADS.register_module`: + +```python +import torch +import torch.nn as nn +from deeplabcut.pose_estimation_pytorch.models.heads import HEADS, BaseHead +from deeplabcut.pose_estimation_pytorch.models.predictors import BasePredictor +from deeplabcut.pose_estimation_pytorch.models.target_generators import BaseGenerator + + +@HEADS.register_module +class MyHead(BaseHead): + def __init__( + self, + predictor: BasePredictor, + target_generator: BaseGenerator, + criterion: dict, + aggregator=None, + in_channels: int = 256, + num_keypoints: int = 17, + ): + super().__init__( + predictor=predictor, + target_generator=target_generator, + criterion=criterion, + aggregator=aggregator, + ) + self.conv = nn.Conv2d(in_channels, num_keypoints, kernel_size=1) + + def forward(self, x: torch.Tensor) -> dict[str, torch.Tensor]: + heatmap = self.conv(x) + return {"heatmap": heatmap} +``` + +## Custom Predictors + +A predictor converts a head's raw output tensors into keypoint coordinates. Subclass [`BasePredictor`][deeplabcut.pose_estimation_pytorch.models.predictors.BasePredictor] and register with `@PREDICTORS.register_module`: + +```python +import torch +from deeplabcut.pose_estimation_pytorch.models.predictors import PREDICTORS, BasePredictor + + +@PREDICTORS.register_module +class MyPredictor(BasePredictor): + def forward( + self, + stride: float, + outputs: dict[str, torch.Tensor], + ) -> dict[str, torch.Tensor]: + heatmap = outputs["heatmap"] + # Locate the maximum value in each heatmap channel + batch, num_kpts, h, w = heatmap.shape + flat = heatmap.flatten(2) + idx = flat.argmax(dim=-1) + x = (idx % w).float() * stride + y = (idx // w).float() * stride + coords = torch.stack([x, y], dim=-1) # (batch, num_kpts, 2) + scores = flat.max(dim=-1).values + return {"coordinates": coords, "scores": scores} +``` + +## Custom Target Generators + +A target generator converts ground-truth annotations into training target tensors. Subclass [`BaseGenerator`][deeplabcut.pose_estimation_pytorch.models.target_generators.BaseGenerator] and register with `@TARGET_GENERATORS.register_module`: + +```python +import torch +import torch.nn as nn +from deeplabcut.pose_estimation_pytorch.models.target_generators import ( + TARGET_GENERATORS, + BaseGenerator, +) + + +@TARGET_GENERATORS.register_module +class MyTargetGenerator(BaseGenerator): + def __init__(self, num_heatmaps: int, sigma: float = 2.0): + super().__init__() + self.num_heatmaps = num_heatmaps + self.sigma = sigma + + def forward( + self, + annotations: dict, + stride: float, + output_size: tuple[int, int], + ) -> dict[str, torch.Tensor]: + # Build Gaussian heatmaps for each keypoint annotation + # Returns a dict matching the keys produced by the head + ... + return {"heatmap": target_heatmaps, "heatmap_mask": mask} +``` + +## Using Custom Components in Configuration + +Once registered, custom components are referenced by class name in `pytorch_cfg.yaml`. Make sure the module containing your class is imported before the configuration is loaded (e.g. at the top of your training script): + +```python +# my_components.py — import this before calling dlc_torch.train() +from deeplabcut.pose_estimation_pytorch.models.backbones import BACKBONES, BaseBackbone +import torch.nn as nn + + +@BACKBONES.register_module +class MyBackbone(BaseBackbone): + ... +``` + +Then reference it in the configuration: + +```yaml +model: + backbone: + type: MyBackbone # matched by class name in the registry + out_channels: 256 + head: + type: HeatmapHead # built-in head, paired with your backbone + ... +``` From 78e6dde6cd62c53a96080824eec13ce10dca12f1 Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter Date: Wed, 27 May 2026 13:44:01 +0200 Subject: [PATCH 08/14] dev docs: improve API formatting --- dev-docs/mkdocs.yml | 9 +++++++-- pyproject.toml | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/dev-docs/mkdocs.yml b/dev-docs/mkdocs.yml index bed8c86783..50a98e96e5 100644 --- a/dev-docs/mkdocs.yml +++ b/dev-docs/mkdocs.yml @@ -9,8 +9,8 @@ repo_url: https://github.com/DeepLabCut/DeepLabCut theme: name: material custom_dir: "docs/theme" - logo: docs/assets/logo.png - favicon: docs/assets/favicon.ico + logo: assets/logo.png + favicon: assets/favicon.ico palette: - media: "(prefers-color-scheme: light)" @@ -73,9 +73,14 @@ plugins: options: summary: true show_root_heading: true + show_symbol_type_heading: true + parameter_headings: true docstring_options: returns_multiple_items: false docstring_section_style: table + separate_signature: true + line_length: 88 + show_signature_annotations: true inventories: - url: https://docs.python.org/3/objects.inv domains: [py, std] diff --git a/pyproject.toml b/pyproject.toml index 127d2956d0..1aaa108a4e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,6 +80,7 @@ docs = [ "sphinxcontrib-mermaid", ] dev-docs = [ + "black>=24", "mike>=2.1", "mkdocs>=1.6", "mkdocs-api-autonav>=0.1", From ef7c729748705a229775619b6bbf7a2283cae34e Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter Date: Wed, 27 May 2026 13:51:36 +0200 Subject: [PATCH 09/14] dev docs: add mike deploy workflow for versioning --- .github/workflows/publish-book.yml | 42 ++++++++++++++++-------------- dev-docs/mkdocs.yml | 3 ++- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/.github/workflows/publish-book.yml b/.github/workflows/publish-book.yml index bf838c73a3..8c0af45080 100644 --- a/.github/workflows/publish-book.yml +++ b/.github/workflows/publish-book.yml @@ -16,12 +16,14 @@ jobs: upload_artifact: true secrets: inherit - build-dev-docs: + deploy-dev-docs: runs-on: ubuntu-latest permissions: - contents: read + contents: write steps: - uses: actions/checkout@v6 + with: + fetch-depth: 0 # mike needs full history to read/write gh-pages branch - name: Set up Python uses: actions/setup-python@v6 @@ -38,20 +40,25 @@ jobs: python -m pip install --upgrade pip python -m pip install ".[dev-docs]" - - name: Build developer docs + - name: Configure git for mike run: | - mkdocs build -v -f dev-docs/mkdocs.yml + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" - - name: Upload developer docs artifact - uses: actions/upload-artifact@v6 - with: - name: built-dev-docs - path: _build/dev - if-no-files-found: error - retention-days: 1 + - name: Deploy dev-docs with mike + run: | + VERSION=$(python -c "import deeplabcut; v=deeplabcut.__version__; print('.'.join(v.split('.')[:2]))") + mike deploy --push --update-aliases \ + --config-file dev-docs/mkdocs.yml \ + --deploy-prefix dev \ + "$VERSION" latest + mike set-default --push \ + --config-file dev-docs/mkdocs.yml \ + --deploy-prefix dev \ + latest - deploy: - needs: [build, build-dev-docs] + deploy-main-docs: + needs: [build, deploy-dev-docs] runs-on: ubuntu-latest permissions: contents: write @@ -62,14 +69,9 @@ jobs: name: built-book path: site - - name: Download built developer docs artifact - uses: actions/download-artifact@v4 - with: - name: built-dev-docs - path: site/dev - - - name: Deploy via gh-pages branch + - name: Deploy main docs via gh-pages branch uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: site + keep_files: true # preserve /dev/ tree managed by mike diff --git a/dev-docs/mkdocs.yml b/dev-docs/mkdocs.yml index 50a98e96e5..b9d2e26893 100644 --- a/dev-docs/mkdocs.yml +++ b/dev-docs/mkdocs.yml @@ -109,7 +109,8 @@ markdown_extensions: extra: version: - provider: mike # TODO: use mike to manage versions + provider: mike + default: latest copyright: "Copyright © 2026 The DeepLabCut Team" From 4e0b5f0d836a3f69fea98b29d1b94594e62c9ef9 Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter Date: Wed, 27 May 2026 15:25:50 +0200 Subject: [PATCH 10/14] dev docs: add CI dev-docs build step (for validation) --- .github/workflows/build-dev-docs.yml | 37 +++++++++++++++++++ .../{build-book.yml => build-main-docs.yml} | 4 +- .github/workflows/intelligent-testing.yml | 13 ++++++- 3 files changed, 49 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/build-dev-docs.yml rename .github/workflows/{build-book.yml => build-main-docs.yml} (95%) diff --git a/.github/workflows/build-dev-docs.yml b/.github/workflows/build-dev-docs.yml new file mode 100644 index 0000000000..8c31275ff8 --- /dev/null +++ b/.github/workflows/build-dev-docs.yml @@ -0,0 +1,37 @@ +name: Build dev-docs (mkdocs) + +on: + workflow_call: + inputs: + python-version: + description: "Python version used to build the dev docs." + required: false + default: "3.10" + type: string + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v6 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: ${{ inputs.python-version }} + + - name: Install system dependencies for mkdocs-material social plugin + run: | + sudo apt-get update -qq + sudo apt-get install -y libcairo2-dev libfreetype6-dev libffi-dev \ + libjpeg-dev libpng-dev libz-dev + + - name: Install dev-docs dependencies + run: | + python -m pip install --upgrade pip + python -m pip install ".[dev-docs]" + + - name: Build dev docs + run: mkdocs build -v -f dev-docs/mkdocs.yml diff --git a/.github/workflows/build-book.yml b/.github/workflows/build-main-docs.yml similarity index 95% rename from .github/workflows/build-book.yml rename to .github/workflows/build-main-docs.yml index a1c55b9a0e..aedc113f5a 100644 --- a/.github/workflows/build-book.yml +++ b/.github/workflows/build-main-docs.yml @@ -1,4 +1,4 @@ -name: Build (and optionally deploy) Jupyter Book +name: Build main docs (Jupyter Book) on: workflow_call: @@ -18,8 +18,6 @@ on: default: false type: boolean - - jobs: build: runs-on: ubuntu-latest diff --git a/.github/workflows/intelligent-testing.yml b/.github/workflows/intelligent-testing.yml index ae85292c75..e4ba7a6d79 100644 --- a/.github/workflows/intelligent-testing.yml +++ b/.github/workflows/intelligent-testing.yml @@ -76,16 +76,25 @@ jobs: docs: - name: Docs build + name: Main docs build needs: intelligent-test-selection if: needs.intelligent-test-selection.outputs.run_docs == 'true' - uses: ./.github/workflows/build-book.yml + uses: ./.github/workflows/build-main-docs.yml with: python-version: "3.10" build_dir: "./_build/html" upload_artifact: false secrets: inherit + dev-docs: + name: Dev docs build + needs: intelligent-test-selection + if: needs.intelligent-test-selection.outputs.run_docs == 'true' + uses: ./.github/workflows/build-dev-docs.yml + with: + python-version: "3.10" + secrets: inherit + fast-tests: name: Fast lane (targeted pytest + selected functional) From e6ba863defb80f84677d8eb06e6808d13c757a1d Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter Date: Wed, 27 May 2026 15:32:25 +0200 Subject: [PATCH 11/14] dev docs: CI latest dev docs from main, manual dispatch for others --- .../{publish-book.yml => deploy-docs.yml} | 22 ++--- .github/workflows/manage-dev-docs.yml | 98 +++++++++++++++++++ 2 files changed, 109 insertions(+), 11 deletions(-) rename .github/workflows/{publish-book.yml => deploy-docs.yml} (77%) create mode 100644 .github/workflows/manage-dev-docs.yml diff --git a/.github/workflows/publish-book.yml b/.github/workflows/deploy-docs.yml similarity index 77% rename from .github/workflows/publish-book.yml rename to .github/workflows/deploy-docs.yml index 8c0af45080..6c85e637d7 100644 --- a/.github/workflows/publish-book.yml +++ b/.github/workflows/deploy-docs.yml @@ -1,4 +1,4 @@ -name: publish-book +name: Deploy docs (main + dev-docs latest) on: push: @@ -9,14 +9,14 @@ permissions: jobs: build: - uses: ./.github/workflows/build-book.yml + uses: ./.github/workflows/build-main-docs.yml with: python-version: "3.10" build_dir: "./_build/html" upload_artifact: true secrets: inherit - deploy-dev-docs: + deploy-dev-docs-latest: runs-on: ubuntu-latest permissions: contents: write @@ -33,9 +33,10 @@ jobs: - name: Install system dependencies for mkdocs-material social plugin run: | sudo apt-get update -qq - sudo apt-get install -y libcairo2-dev libfreetype6-dev libffi-dev libjpeg-dev libpng-dev libz-dev + sudo apt-get install -y libcairo2-dev libfreetype6-dev libffi-dev \ + libjpeg-dev libpng-dev libz-dev - - name: Install developer docs dependencies + - name: Install dev-docs dependencies run: | python -m pip install --upgrade pip python -m pip install ".[dev-docs]" @@ -45,20 +46,19 @@ jobs: git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - - name: Deploy dev-docs with mike + - name: Deploy latest dev-docs with mike run: | - VERSION=$(python -c "import deeplabcut; v=deeplabcut.__version__; print('.'.join(v.split('.')[:2]))") - mike deploy --push --update-aliases \ + mike deploy --push \ --config-file dev-docs/mkdocs.yml \ --deploy-prefix dev \ - "$VERSION" latest + latest mike set-default --push \ --config-file dev-docs/mkdocs.yml \ --deploy-prefix dev \ latest deploy-main-docs: - needs: [build, deploy-dev-docs] + needs: [ build, deploy-dev-docs-latest ] runs-on: ubuntu-latest permissions: contents: write @@ -69,7 +69,7 @@ jobs: name: built-book path: site - - name: Deploy main docs via gh-pages branch + - name: Deploy main docs to gh-pages uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/manage-dev-docs.yml b/.github/workflows/manage-dev-docs.yml new file mode 100644 index 0000000000..d09d4e8740 --- /dev/null +++ b/.github/workflows/manage-dev-docs.yml @@ -0,0 +1,98 @@ +name: Manage dev-docs versions (manual) + +on: + workflow_dispatch: + inputs: + action: + description: "Action to perform" + required: true + type: choice + options: + - deploy-version + - delete-version + version_label: + description: "Version label to deploy or delete (e.g. 3.0)" + required: true + type: string + git_tag: + description: "Git tag to check out for deploy-version (e.g. v3.0.0rc14). Ignored for delete-version." + required: false + type: string + +permissions: + contents: write + +jobs: + deploy-version: + if: ${{ inputs.action == 'deploy-version' }} + runs-on: ubuntu-latest + steps: + - name: Validate inputs + run: | + if [ -z "${{ inputs.git_tag }}" ]; then + echo "::error::git_tag is required for deploy-version" + exit 1 + fi + + - uses: actions/checkout@v6 + with: + ref: ${{ inputs.git_tag }} + fetch-depth: 0 # mike needs full history to read/write gh-pages branch + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.10" + + - name: Install system dependencies for mkdocs-material social plugin + run: | + sudo apt-get update -qq + sudo apt-get install -y libcairo2-dev libfreetype6-dev libffi-dev \ + libjpeg-dev libpng-dev libz-dev + + - name: Install dev-docs dependencies + run: | + python -m pip install --upgrade pip + python -m pip install ".[dev-docs]" + + - name: Configure git for mike + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Deploy version with mike + run: | + mike deploy --push \ + --config-file dev-docs/mkdocs.yml \ + --deploy-prefix dev \ + "${{ inputs.version_label }}" + + delete-version: + if: ${{ inputs.action == 'delete-version' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 # mike needs full history to read/write gh-pages branch + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.10" + + - name: Install dev-docs dependencies + run: | + python -m pip install --upgrade pip + python -m pip install ".[dev-docs]" + + - name: Configure git for mike + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Delete version with mike + run: | + mike delete --push \ + --config-file dev-docs/mkdocs.yml \ + --deploy-prefix dev \ + "${{ inputs.version_label }}" From e2e19566ff8f2f3a5273149607b143423d7d0304 Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter Date: Wed, 27 May 2026 15:53:14 +0200 Subject: [PATCH 12/14] dev docs: reverse CI order (deploy main docs before dev docs) --- .github/workflows/deploy-docs.yml | 39 ++++++++++++++++--------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 6c85e637d7..2819caf7f1 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -16,7 +16,27 @@ jobs: upload_artifact: true secrets: inherit + deploy-main-docs: + needs: build + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Download built Jupyter Book artifact + uses: actions/download-artifact@v4 + with: + name: built-book + path: site + + - name: Deploy main docs to gh-pages + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: site + keep_files: true # preserve /dev/ versions managed by mike + deploy-dev-docs-latest: + needs: deploy-main-docs runs-on: ubuntu-latest permissions: contents: write @@ -56,22 +76,3 @@ jobs: --config-file dev-docs/mkdocs.yml \ --deploy-prefix dev \ latest - - deploy-main-docs: - needs: [ build, deploy-dev-docs-latest ] - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - name: Download built Jupyter Book artifact - uses: actions/download-artifact@v4 - with: - name: built-book - path: site - - - name: Deploy main docs to gh-pages - uses: peaceiris/actions-gh-pages@v4 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: site - keep_files: true # preserve /dev/ tree managed by mike From aa1c064912b76241cecb07c8922eb1d9afb885d9 Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter Date: Wed, 27 May 2026 16:31:56 +0200 Subject: [PATCH 13/14] dev-docs: only checkout deeplabcut module from version, not entire repo --- .github/workflows/manage-dev-docs.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/manage-dev-docs.yml b/.github/workflows/manage-dev-docs.yml index d09d4e8740..6f16ca7e62 100644 --- a/.github/workflows/manage-dev-docs.yml +++ b/.github/workflows/manage-dev-docs.yml @@ -36,7 +36,6 @@ jobs: - uses: actions/checkout@v6 with: - ref: ${{ inputs.git_tag }} fetch-depth: 0 # mike needs full history to read/write gh-pages branch - name: Set up Python @@ -50,6 +49,9 @@ jobs: sudo apt-get install -y libcairo2-dev libfreetype6-dev libffi-dev \ libjpeg-dev libpng-dev libz-dev + - name: Check out tagged source tree + run: git checkout "${{ inputs.git_tag }}" -- deeplabcut + - name: Install dev-docs dependencies run: | python -m pip install --upgrade pip From eaf049c114827fe07ff89a4d01f17af7f1593807 Mon Sep 17 00:00:00 2001 From: Jaap de Ruyter Date: Wed, 27 May 2026 17:01:59 +0200 Subject: [PATCH 14/14] dev docs: rename 'latest' to 'main' + fix mike deploy prefix --- .github/workflows/deploy-docs.yml | 6 +++--- dev-docs/mkdocs.yml | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 2819caf7f1..5f6b86adcc 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -66,13 +66,13 @@ jobs: git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - - name: Deploy latest dev-docs with mike + - name: Deploy latest dev-docs with mike (main branch) run: | mike deploy --push \ --config-file dev-docs/mkdocs.yml \ --deploy-prefix dev \ - latest + main mike set-default --push \ --config-file dev-docs/mkdocs.yml \ --deploy-prefix dev \ - latest + main diff --git a/dev-docs/mkdocs.yml b/dev-docs/mkdocs.yml index b9d2e26893..8ccdbc678b 100644 --- a/dev-docs/mkdocs.yml +++ b/dev-docs/mkdocs.yml @@ -1,6 +1,6 @@ site_name: DeepLabCut Developer Documentation site_description: Developer documentation for DeepLabCut -site_url: https://deeplabcut.github.io/DeepLabCut/dev +site_url: https://deeplabcut.github.io/DeepLabCut/dev/ site_dir: "../_build/dev" repo_name: DeepLabCut/DeepLabCut @@ -48,7 +48,8 @@ theme: plugins: - autorefs # link to a heading or attribute (id) from any other page in the site - - mike # for versioning + - mike: + deploy_prefix: dev - mkdocs-jupyter # adds support for rendering Jupyter Notebooks - api-autonav: # automatically generates API reference navigation based on the file structure modules: @@ -110,7 +111,7 @@ markdown_extensions: extra: version: provider: mike - default: latest + default: main copyright: "Copyright © 2026 The DeepLabCut Team"