Skip to content

Commit e186132

Browse files
authored
Merge pull request #29 from RmSchaffert/lane_helpers_polyline
Added polyline interpolation (in the new `lane_helpers` sub-package)
2 parents 6d743ac + e99dbff commit e186132

60 files changed

Lines changed: 5781 additions & 118 deletions

File tree

Some content is hidden

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

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,3 @@ __pycache__/
1818
*.whl
1919

2020
*.log
21-

docker/Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ RUN pip install pandas==1.5.3 \
113113
numba==0.59 \
114114
pyquaternion==0.9.9
115115

116-
RUN pip install nuscenes-devkit && \
117-
pip install shapely tqdm pillow networkx fire
116+
RUN pip install nuscenes-devkit==1.2.0 && \
117+
pip install shapely==2.0.7 tqdm==4.67.3 pillow==12.2.0 networkx==3.4.2 fire==0.7.1
118118

119119
RUN pip install pytest pytest-timeout
120120
RUN pip install pynvml

docs/Makefile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ help:
2323

2424
# Generate namespace package documentation before building
2525
generate:
26-
python3 mirror_referenced_dirs.py
2726
python3 generate_new_namespace_package_docs.py
27+
python3 generate_package_docs_assets.py
28+
python3 mirror_referenced_dirs.py
2829
python3 update_docs_index.py
2930

3031
# Sync the root README into the docs tree before building
@@ -41,7 +42,7 @@ clean:
4142
@$(SPHINXBUILD) -M clean "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
4243
rm -rf $(BUILDDIR)/
4344
rm -rf api/generated/
44-
rm -rf ../packages/*/docs/generated/
45+
rm -rf ../packages/*/docs/_generated/
4546

4647
# Auto-build documentation (watches for changes)
4748
livehtml: sync-readme generate
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
#!/usr/bin/env python3
2+
3+
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
import argparse
18+
from dataclasses import dataclass
19+
import importlib.util
20+
from pathlib import Path
21+
import sys
22+
from types import ModuleType
23+
from typing import Callable
24+
25+
26+
@dataclass(frozen=True)
27+
class PackageDocsContext:
28+
project_root: Path
29+
namespace_package: str
30+
package_name: str
31+
package_root: Path
32+
docs_root: Path
33+
generated_dir: Path
34+
35+
36+
HookFunction = Callable[[PackageDocsContext], None]
37+
_GENERATED_ASSET_GITIGNORE = "*\n"
38+
39+
40+
def _load_hook_module(hook_path: Path, package_name: str) -> ModuleType:
41+
# Temporary module name for the imported hook.
42+
module_name = f"_accvlab_docs_assets_{package_name}"
43+
44+
# Import
45+
spec = importlib.util.spec_from_file_location(module_name, hook_path)
46+
if spec is None or spec.loader is None:
47+
raise ImportError(f"Could not create import spec for docs asset hook: {hook_path}")
48+
module = importlib.util.module_from_spec(spec)
49+
spec.loader.exec_module(module)
50+
51+
return module
52+
53+
54+
def _get_hook_function(module: ModuleType, hook_path: Path) -> HookFunction:
55+
hook_function = getattr(module, "generate_docs_assets", None)
56+
if not callable(hook_function):
57+
raise AttributeError(
58+
f"Docs asset hook must define a callable generate_docs_assets(context): {hook_path}"
59+
)
60+
return hook_function
61+
62+
63+
def _prepare_generated_dir(context: PackageDocsContext) -> None:
64+
"""Create the package's generated docs asset directory and keep it untracked."""
65+
context.generated_dir.mkdir(parents=True, exist_ok=True)
66+
(context.generated_dir / ".gitignore").write_text(_GENERATED_ASSET_GITIGNORE, encoding="utf-8")
67+
68+
69+
def _build_context(project_root: Path, namespace_package: str) -> PackageDocsContext:
70+
package_name = namespace_package.split(".")[-1]
71+
package_root = project_root / "packages" / package_name
72+
docs_root = package_root / "docs"
73+
generated_dir = docs_root / "_generated"
74+
ctx = PackageDocsContext(
75+
project_root=project_root,
76+
namespace_package=namespace_package,
77+
package_name=package_name,
78+
package_root=package_root,
79+
docs_root=docs_root,
80+
generated_dir=generated_dir,
81+
)
82+
return ctx
83+
84+
85+
def _generate_assets_for_package(
86+
*,
87+
project_root: Path,
88+
namespace_package: str,
89+
verbose: bool,
90+
) -> bool:
91+
context = _build_context(project_root, namespace_package)
92+
hook_path = context.docs_root / "_on_doc_generation.py"
93+
if not hook_path.exists():
94+
if verbose:
95+
print(f"No docs asset hook for {context.package_name}")
96+
return False
97+
98+
if verbose:
99+
print(f"Running docs asset hook for {context.package_name}: {hook_path}")
100+
module = _load_hook_module(hook_path, context.package_name)
101+
_prepare_generated_dir(context)
102+
hook_function = _get_hook_function(module, hook_path)
103+
hook_function(context)
104+
print(f"Generated docs assets for {context.package_name}")
105+
return True
106+
107+
108+
def _parse_args() -> argparse.Namespace:
109+
parser = argparse.ArgumentParser(
110+
description="Run optional package-local documentation asset generation hooks.",
111+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
112+
)
113+
parser.add_argument(
114+
"-v",
115+
"--verbose",
116+
action="store_true",
117+
help="Enable verbose output.",
118+
)
119+
parser.add_argument(
120+
"--package",
121+
dest="package_names",
122+
action="append",
123+
help="Package name to process, such as lane_helpers. Can be passed more than once.",
124+
)
125+
return parser.parse_args()
126+
127+
128+
def main() -> int:
129+
args = _parse_args()
130+
docs_dir = Path(__file__).resolve().parent
131+
project_root = docs_dir.parent
132+
sys.path.insert(0, str(project_root))
133+
134+
try:
135+
from namespace_packages_config import NAMESPACE_PACKAGES
136+
except ImportError as exc:
137+
print(
138+
f"Error: Could not import NAMESPACE_PACKAGES from namespace_packages_config.py: {exc}",
139+
file=sys.stderr,
140+
)
141+
return 1
142+
143+
package_filter = set(args.package_names or [])
144+
namespace_packages = [
145+
namespace_package
146+
for namespace_package in NAMESPACE_PACKAGES
147+
if not package_filter or namespace_package.split(".")[-1] in package_filter
148+
]
149+
if package_filter and len(namespace_packages) != len(package_filter):
150+
found_package_names = {namespace_package.split(".")[-1] for namespace_package in namespace_packages}
151+
missing_package_names = sorted(package_filter - found_package_names)
152+
print(f"Error: Unknown namespace package(s): {', '.join(missing_package_names)}", file=sys.stderr)
153+
return 1
154+
155+
hook_count = 0
156+
for namespace_package in namespace_packages:
157+
package_name = namespace_package.split(".")[-1]
158+
try:
159+
hook_ran = _generate_assets_for_package(
160+
project_root=project_root,
161+
namespace_package=namespace_package,
162+
verbose=args.verbose,
163+
)
164+
except Exception as exc:
165+
print(f"Error: docs asset generation failed for {package_name}: {exc}", file=sys.stderr)
166+
return 1
167+
if hook_ran:
168+
hook_count += 1
169+
170+
if args.verbose:
171+
print(f"Ran {hook_count} package docs asset hook(s).")
172+
return 0
173+
174+
175+
if __name__ == "__main__":
176+
sys.exit(main())

docs/guides/DEVELOPMENT_GUIDE.md

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ There are two example projects which showcase how a namespace package is structu
4848
- `packages/example_package`: Showcases a package containing PyTorch extensions built using
4949
`CppExtension` and `CUDAExtension` provided by PyTorch as well as an external implementation (see
5050
[External Implementations](#external-implementations) section for more details on external implementations)
51-
as described below.
51+
as described below. It also includes a package-local documentation asset hook that generates a simple plot
52+
from committed CSV data under `evaluation_results/` during the docs build.
5253
- `packages/example_skbuild_package`: Showcases a package using `scikit-build` for C++/CUDA implementation
5354
(see the [Alternative: SKBuild-Based Packages](#alternative-skbuild-based-packages) section for more
5455
details on this approach).
@@ -70,6 +71,8 @@ To add a new namespace package (e.g., `example_package`), you need to create:
7071
| **Setup** | `packages/example_package/setup.py` | Package build configuration |
7172
| **Project Config** | `packages/example_package/pyproject.toml` | Modern Python project configuration and authoritative dependency definition |
7273
| **Documentation include list (optional)** | `packages/example_package/docu_referenced_dirs.txt` | List additional directories referenced by the docs (besides `docs/`). See [Documentation Setup Guide](DOCUMENTATION_SETUP_GUIDE.md) for more details.|
74+
| **Documentation asset hook (optional)** | `packages/example_package/docs/_on_doc_generation.py` | Generate package-owned docs assets such as plots from committed evaluation data. See [Documentation Setup Guide](DOCUMENTATION_SETUP_GUIDE.md#package-local-generated-assets). |
75+
| **Evaluation results (optional)** | `packages/example_package/evaluation_results/` | Package-owned committed inputs for generating docs assets, such as data to plot. |
7376

7477
> **ℹ️ Note**: Apart from the above, further folders/files can be included (and made use of manually or added to the
7578
> documentation) if needed. A typical use case is to include e.g. an `examples` directory which is:
@@ -84,26 +87,29 @@ The following diagram shows the relevant project structure containing the folder
8487

8588
```
8689
accvlab/
87-
├── packages/ # Namespace packages directory
90+
├── packages/ # Namespace packages directory
8891
│ ├── optim_test_tools/...
8992
│ ├── batching_helpers/...
90-
│ └── example_package/ # ← New namespace package
91-
│ ├── accvlab/ # ← Namespace root
92-
│ │ └── example_package/ # ← Implementation for "example_package" package
93+
│ └── example_package/ # ← New namespace package
94+
│ ├── accvlab/ # ← Namespace root
95+
│ │ └── example_package/ # ← Implementation for "example_package" package
9396
│ │ ├── __init__.py
94-
│ │ ├── csrc/ # ← C++/CUDA sources
95-
│ │ └── include/ # ← Headers
96-
│ ├── ext_impl/ # ← Optional: external implementation
97+
│ │ ├── csrc/ # ← C++/CUDA sources
98+
│ │ └── include/ # ← Headers
99+
│ ├── ext_impl/ # ← Optional: external implementation
97100
│ │ ├── build_and_copy.sh
98101
│ │ └── ...
99-
│ ├── tests/ # ← Tests for "example_package" package
100-
│ ├── docs/ # ← Documentation for "example_package" package
101-
│ ├── setup.py # ← Package build configuration
102-
│ ├── pyproject.toml # ← Project configuration (including dependencies)
103-
│ └── docu_referenced_dirs.txt # ← Optional: list additional directories referenced by the docs (besides `docs/`)
104-
├── build_config/ # Shared build utilities
105-
├── docs/ # Main documentation
106-
└── namespace_packages_config.py # ← Namespace package needs to be listed here
102+
│ ├── tests/ # ← Tests for "example_package" package
103+
│ ├── evaluation_results/ # ← Optional committed inputs for generated docs assets
104+
│ ├── docs/ # ← Documentation for "example_package" package
105+
│ │ ├── _on_doc_generation.py # ← Optional docs asset hook
106+
│ │ └── ...
107+
│ ├── setup.py # ← Package build configuration
108+
│ ├── pyproject.toml # ← Project configuration (including dependencies)
109+
│ └── docu_referenced_dirs.txt # ← Optional: list additional directories referenced by the docs (besides `docs/`)
110+
├── build_config/ # Shared build utilities
111+
├── docs/ # Main documentation
112+
└── namespace_packages_config.py # ← Namespace package needs to be listed here
107113
```
108114

109115
Note that inside the package, there is the directory structure `accvlab/example_package`. This is where the
@@ -238,6 +244,11 @@ root = "../.."
238244

239245
Use this pattern for your own namespace package, adapting the dependency names as needed.
240246

247+
Use `[project.optional-dependencies].optional` for dependencies needed by tests, examples, or package-local
248+
documentation asset hooks, but not by the core package at runtime. For example, if a docs hook generates plots
249+
from committed data, put the plotting library in the package's optional dependencies rather than in the base
250+
`[project].dependencies`.
251+
241252
> **ℹ️ Note**: The `accvlab-build-config @ file:../../build_config` build dependency is intentionally a
242253
> local path reference. From a package under `packages/<package_name>/`, it resolves to the repository's `build_config/` package
243254
> so isolated pip builds use the local helper package. See
@@ -317,6 +328,18 @@ Most of the contained packages extend this basic structure considerably to provi
317328
documentation. Please see the [Documentation Setup Guide](DOCUMENTATION_SETUP_GUIDE.md) for more details on
318329
the documentation system and how to set it up.
319330

331+
If your package needs generated docs assets, add `packages/<package_name>/docs/_on_doc_generation.py`. The
332+
documentation build creates `packages/<package_name>/docs/_generated/`, keeps it untracked, and passes that
333+
directory to the hook. Keep user-facing `.rst`/`.md` files static and reference generated assets with relative
334+
paths such as `_generated/<asset_name>.png`. The hook should generate those assets from committed inputs and
335+
fail clearly if required inputs are missing. Store committed plot or evaluation inputs outside the package
336+
`docs/` folder, for example under `packages/<package_name>/evaluation_results/`, so Sphinx does not discover
337+
data tables as standalone documentation pages.
338+
339+
> **⚠️ Important**: Documentation asset hooks must not run evaluations, benchmarks, or other measurement
340+
> workflows. They should only regenerate documentation assets, such as plots, from data that is already
341+
> available in the repository.
342+
320343
#### 8. Test Your Package
321344

322345
```bash
@@ -352,6 +375,10 @@ When adding a new namespace package, ensure you have:
352375
- [ ] **Documentation**: Generated with docs scripts and customized intro
353376
- [ ] **Documentation include list (optional)**: `docu_referenced_dirs.txt` created and populated if extra
354377
folders (e.g. `examples/`) are referenced and are needed to build the documentation
378+
- [ ] **Documentation asset hook (optional)**: `_on_doc_generation.py` added if the package needs generated
379+
documentation assets
380+
- [ ] **Evaluation results (optional)**: `packages/<package_name>/evaluation_results/` contains committed
381+
inputs for generated docs assets if needed
355382
- [ ] **Examples (optional)**: `packages/<package_name>/examples/` created and referenced from docs if used
356383
- [ ] **Dependencies**: Declared runtime and optional dependencies in `pyproject.toml`
357384
- [ ] **External implementation**: (Optional) `packages/<package_name>/ext_impl/` for external builds

0 commit comments

Comments
 (0)