smplfitter 0.3.2.dev0__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/.gitignore +11 -0
  2. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/.readthedocs.yaml +1 -0
  3. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/PKG-INFO +15 -24
  4. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/benchmark/bench_bodymodel.py +9 -6
  5. smplfitter-0.4.0/benchmark/benchmark_smpl.png +0 -0
  6. smplfitter-0.4.0/benchmark/benchmark_vs_smplx_smpl.png +0 -0
  7. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/benchmark/plot_results.py +4 -0
  8. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/benchmark/run_benchmark.py +18 -12
  9. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/conf.py +44 -9
  10. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/howto.rst +57 -17
  11. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/index.rst +21 -6
  12. smplfitter-0.4.0/docs/smpl_to_smplx.rst +83 -0
  13. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/pyproject.toml +8 -2
  14. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/__init__.py +3 -2
  15. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/_version.py +3 -3
  16. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/common.py +250 -14
  17. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/decimation/make_post_lbs_joint_regressors.py +62 -34
  18. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/download.py +3 -1
  19. smplfitter-0.4.0/src/smplfitter/jax/__init__.py +36 -0
  20. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/jax/bodyconverter.py +1 -9
  21. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/jax/bodymodel.py +26 -1
  22. smplfitter-0.4.0/src/smplfitter/nb/__init__.py +36 -0
  23. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/nb/bodyconverter.py +1 -9
  24. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/nb/bodyfitter.py +255 -129
  25. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/nb/bodymodel.py +222 -87
  26. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/nb/precompile.py +6 -4
  27. smplfitter-0.4.0/src/smplfitter/nb/rotation.py +169 -0
  28. smplfitter-0.4.0/src/smplfitter/np/__init__.py +35 -0
  29. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/np/bodyconverter.py +1 -9
  30. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/np/bodymodel.py +37 -6
  31. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/pt/__init__.py +17 -51
  32. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/pt/bodyconverter.py +2 -14
  33. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/pt/bodyfitter.py +1 -2
  34. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/pt/bodyflipper.py +3 -3
  35. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/pt/bodymodel.py +57 -8
  36. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/pt/handreplacer.py +1 -1
  37. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/tf/__init__.py +18 -7
  38. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/tf/bodyconverter.py +1 -9
  39. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/tf/bodymodel.py +33 -8
  40. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter.egg-info/PKG-INFO +15 -24
  41. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter.egg-info/requires.txt +14 -0
  42. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/tests/np/test_bodymodel.py +1 -1
  43. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/tests/pt/test_flipper.py +2 -0
  44. smplfitter-0.3.2.dev0/benchmark/benchmark_smpl.png +0 -0
  45. smplfitter-0.3.2.dev0/benchmark/benchmark_vs_smplx_smpl.png +0 -0
  46. smplfitter-0.3.2.dev0/docs/smpl_to_smplx.rst +0 -133
  47. smplfitter-0.3.2.dev0/src/smplfitter/jax/__init__.py +0 -25
  48. smplfitter-0.3.2.dev0/src/smplfitter/nb/__init__.py +0 -25
  49. smplfitter-0.3.2.dev0/src/smplfitter/nb/rotation.py +0 -120
  50. smplfitter-0.3.2.dev0/src/smplfitter/np/__init__.py +0 -24
  51. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/.editorconfig +0 -0
  52. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/.github/dependabot.yml +0 -0
  53. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/.github/workflows/ci.yml +0 -0
  54. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/.github/workflows/python-publish.yml +0 -0
  55. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/.pre-commit-config.yaml +0 -0
  56. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/LICENSE +0 -0
  57. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/Makefile +0 -0
  58. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/README.md +0 -0
  59. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/benchmark/README.md +0 -0
  60. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/benchmark/bench_converter.py +0 -0
  61. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/benchmark/bench_rotvec.py +0 -0
  62. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/benchmark/benchmark.png +0 -0
  63. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/benchmark/benchmark_smplx.png +0 -0
  64. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/benchmark/benchmark_vs_smplx.png +0 -0
  65. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/benchmark/benchmark_vs_smplx_smplx.png +0 -0
  66. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/Makefile +0 -0
  67. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/_static/images/example.gif +0 -0
  68. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/_static/images/image.png +0 -0
  69. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/_static/images/image_example.jpg +0 -0
  70. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/_static/images/on_image.png +0 -0
  71. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/_static/images/on_image_fit.png +0 -0
  72. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/_static/images/overlay_after_rot1.png +0 -0
  73. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/_static/images/overlay_after_rot1_shape.png +0 -0
  74. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/_static/images/overlay_after_rot2.png +0 -0
  75. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/_static/images/overlay_after_rot2_shape.png +0 -0
  76. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/_static/images/overlay_after_rot3.png +0 -0
  77. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/_static/images/parts_overlay1.png +0 -0
  78. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/_static/images/parts_overlay1_rot.png +0 -0
  79. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/_static/images/parts_overlay2.png +0 -0
  80. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/_static/images/parts_overlay2_rot.png +0 -0
  81. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/_static/images/pose3d.png +0 -0
  82. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/_static/images/pose3d_parts_color.png +0 -0
  83. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/_static/images/pose3d_parts_color_blown.png +0 -0
  84. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/_static/images/tmean_parts.png +0 -0
  85. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/_static/images/tmean_parts_blown.png +0 -0
  86. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/_static/styles/my_theme.css +0 -0
  87. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/_templates/autoapi/index.rst +0 -0
  88. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/_templates/autoapi/python/attribute.rst +0 -0
  89. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/_templates/autoapi/python/class.rst +0 -0
  90. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/_templates/autoapi/python/data.rst +0 -0
  91. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/_templates/autoapi/python/exception.rst +0 -0
  92. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/_templates/autoapi/python/function.rst +0 -0
  93. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/_templates/autoapi/python/method.rst +0 -0
  94. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/_templates/autoapi/python/module.rst +0 -0
  95. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/_templates/autoapi/python/package.rst +0 -0
  96. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/_templates/autoapi/python/property.rst +0 -0
  97. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/_templates/copyright.html +0 -0
  98. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/abbrev_long.bib +0 -0
  99. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/make.bat +0 -0
  100. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/docs/references.bib +0 -0
  101. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/setup.cfg +0 -0
  102. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/decimation/__init__.py +0 -0
  103. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/decimation/decimate_body_models.py +0 -0
  104. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/decimation/make_post_lbs_joint_regressors_tf.py +0 -0
  105. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/jax/bodyfitter.py +0 -0
  106. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/jax/lstsq.py +0 -0
  107. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/jax/rotation.py +0 -0
  108. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/nb/lstsq.py +0 -0
  109. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/nb/util.py +0 -0
  110. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/np/bodyfitter.py +0 -0
  111. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/np/lstsq.py +0 -0
  112. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/np/rotation.py +0 -0
  113. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/np/util.py +0 -0
  114. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/pt/bodyfitter_opt.py +0 -0
  115. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/pt/bodyflipper_opt.py +0 -0
  116. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/pt/lstsq.py +0 -0
  117. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/pt/rotation.py +0 -0
  118. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/tf/bodyfitter.py +0 -0
  119. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/tf/lstsq.py +0 -0
  120. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/tf/rotation.py +0 -0
  121. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter/tf/util.py +0 -0
  122. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter.egg-info/SOURCES.txt +0 -0
  123. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter.egg-info/dependency_links.txt +0 -0
  124. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/src/smplfitter.egg-info/top_level.txt +0 -0
  125. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/tests/conftest.py +0 -0
  126. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/tests/np/__init__.py +0 -0
  127. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/tests/pt/__init__.py +0 -0
  128. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/tests/pt/test_bodymodel.py +0 -0
  129. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/tests/pt/test_converter.py +0 -0
  130. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/tests/test_converter_common.py +0 -0
  131. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/tests/test_fitter_common.py +0 -0
  132. {smplfitter-0.3.2.dev0 → smplfitter-0.4.0}/tests/test_forward.py +0 -0
@@ -129,3 +129,14 @@ dmypy.json
129
129
  .pyre/
130
130
 
131
131
  docs/_build/
132
+ _version.py
133
+
134
+ # Experimental backends (not yet public)
135
+ src/smplfitter/cy/
136
+ src/smplfitter/cuda/
137
+ src/smplfitter/cuda2/
138
+ setup_cy.py
139
+ setup_cuda.py
140
+ setup_cuda2.py
141
+ test_cuda2_vs_pt.py
142
+ tests/test_cuda/
@@ -5,6 +5,7 @@ build:
5
5
  tools:
6
6
  python: "3.10"
7
7
  commands:
8
+ - python -m pip install torch --index-url https://download.pytorch.org/whl/cpu
8
9
  - python -m pip install ".[docs]"
9
10
  - python -m sphinx -E -b html docs $READTHEDOCS_OUTPUT/html
10
11
 
@@ -1,30 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: smplfitter
3
- Version: 0.3.2.dev0
3
+ Version: 0.4.0
4
4
  Summary: Fast inverse kinematics and shape fitting for SMPL-family models in NumPy, PyTorch, TensorFlow, JAX, and Numba
5
5
  Author-email: István Sárándi <istvan.sarandi@uni-tuebingen.de>
6
- License: MIT License
7
-
8
- Copyright (c) 2024 István Sárándi
9
-
10
- Permission is hereby granted, free of charge, to any person obtaining a copy
11
- of this software and associated documentation files (the "Software"), to deal
12
- in the Software without restriction, including without limitation the rights
13
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
- copies of the Software, and to permit persons to whom the Software is
15
- furnished to do so, subject to the following conditions:
16
-
17
- The above copyright notice and this permission notice shall be included in all
18
- copies or substantial portions of the Software.
19
-
20
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
- SOFTWARE.
27
-
6
+ License-Expression: MIT
28
7
  Project-URL: Homepage, https://github.com/isarandi/smplfitter
29
8
  Project-URL: Repository, https://github.com/isarandi/smplfitter
30
9
  Project-URL: Documentation, https://smplfitter.readthedocs.io/
@@ -34,7 +13,6 @@ Project-URL: Author, https://istvansarandi.com
34
13
  Keywords: smpl,smplx,smplh,3d-human,body-model,ik,inverse-kinematics,parametric-model,pose-estimation,differentiable,gpu,tensorflow,pytorch,jax,numba,numpy,batch-processing
35
14
  Classifier: Development Status :: 4 - Beta
36
15
  Classifier: Intended Audience :: Science/Research
37
- Classifier: License :: OSI Approved :: MIT License
38
16
  Classifier: Programming Language :: Python
39
17
  Classifier: Programming Language :: Python :: 3
40
18
  Classifier: Programming Language :: Python :: 3.8
@@ -52,6 +30,7 @@ License-File: LICENSE
52
30
  Requires-Dist: numpy
53
31
  Requires-Dist: scipy
54
32
  Requires-Dist: chumpy
33
+ Requires-Dist: platformdirs
55
34
  Provides-Extra: tensorflow
56
35
  Requires-Dist: tensorflow; extra == "tensorflow"
57
36
  Provides-Extra: pytorch
@@ -61,6 +40,13 @@ Requires-Dist: jax; extra == "jax"
61
40
  Requires-Dist: jaxlib; extra == "jax"
62
41
  Provides-Extra: numba
63
42
  Requires-Dist: numba; extra == "numba"
43
+ Provides-Extra: viz
44
+ Requires-Dist: moderngl; extra == "viz"
45
+ Requires-Dist: glfw; extra == "viz"
46
+ Requires-Dist: poseviz; extra == "viz"
47
+ Requires-Dist: framepump; extra == "viz"
48
+ Requires-Dist: torch; extra == "viz"
49
+ Requires-Dist: deltacamera; extra == "viz"
64
50
  Provides-Extra: test
65
51
  Requires-Dist: pytest>=7.0; extra == "test"
66
52
  Requires-Dist: pytest-cov; extra == "test"
@@ -77,6 +63,11 @@ Requires-Dist: sphinx-codeautolink; extra == "docs"
77
63
  Requires-Dist: pydata-sphinx-theme; extra == "docs"
78
64
  Requires-Dist: setuptools-scm; extra == "docs"
79
65
  Requires-Dist: toml; extra == "docs"
66
+ Requires-Dist: torch; extra == "docs"
67
+ Requires-Dist: tensorflow; extra == "docs"
68
+ Requires-Dist: jax; extra == "docs"
69
+ Requires-Dist: jaxlib; extra == "docs"
70
+ Requires-Dist: numba; extra == "docs"
80
71
  Provides-Extra: dev
81
72
  Requires-Dist: smplfitter[docs,lint,pytorch,test]; extra == "dev"
82
73
  Dynamic: license-file
@@ -4,18 +4,21 @@ import numpy as np
4
4
  import time
5
5
 
6
6
 
7
- def benchmark(func, inputs, n_warmup=3, n_iter=50):
8
- """Run benchmark and return mean time in ms."""
7
+ def benchmark(func, inputs, n_warmup=20, n_iter=50):
8
+ """Run benchmark and return median time in ms."""
9
9
  # Warmup
10
10
  for _ in range(n_warmup):
11
11
  func(**inputs)
12
12
 
13
- # Timed runs
14
- start = time.perf_counter()
13
+ # Timed runs (individual)
14
+ times = []
15
15
  for _ in range(n_iter):
16
+ start = time.perf_counter()
16
17
  func(**inputs)
17
- elapsed = time.perf_counter() - start
18
- return elapsed / n_iter * 1000 # ms
18
+ elapsed = time.perf_counter() - start
19
+ times.append(elapsed * 1000)
20
+ times.sort()
21
+ return times[n_iter // 2] # median
19
22
 
20
23
 
21
24
  def main():
@@ -23,6 +23,10 @@ def query_throughput(conn, backend, model, mode):
23
23
  STYLE = {
24
24
  'NumPy': dict(color='C0', linestyle='--', marker='o'),
25
25
  'Numba': dict(color='C1', linestyle='--', marker='s'),
26
+ 'PT eager CPU': dict(color='C2', linestyle='--', marker='o'),
27
+ 'PT eager GPU': dict(color='C2', linestyle='-', marker='o'),
28
+ 'PT script CPU': dict(color='C6', linestyle='--', marker='s'),
29
+ 'PT script GPU': dict(color='C6', linestyle='-', marker='s'),
26
30
  'PT compile CPU': dict(color='C2', linestyle='--', marker='^'),
27
31
  'PT compile GPU': dict(color='C2', linestyle='-', marker='^'),
28
32
  'TF function CPU': dict(color='C3', linestyle='--', marker='D'),
@@ -98,23 +98,24 @@ def make_backends(model_name='smpl', return_vertices=True, which=None):
98
98
  try:
99
99
  import torch
100
100
 
101
- backends.append(
102
- PyTorchBackend(
103
- model_name=model_name,
104
- gpu=False,
105
- compile_mode='compile',
106
- return_vertices=return_vertices,
107
- )
108
- )
109
- if torch.cuda.is_available():
101
+ for compile_mode in [None, 'script', 'compile']:
110
102
  backends.append(
111
103
  PyTorchBackend(
112
104
  model_name=model_name,
113
- gpu=True,
114
- compile_mode='compile',
105
+ gpu=False,
106
+ compile_mode=compile_mode,
115
107
  return_vertices=return_vertices,
116
108
  )
117
109
  )
110
+ if torch.cuda.is_available():
111
+ backends.append(
112
+ PyTorchBackend(
113
+ model_name=model_name,
114
+ gpu=True,
115
+ compile_mode=compile_mode,
116
+ return_vertices=return_vertices,
117
+ )
118
+ )
118
119
  except ImportError:
119
120
  pass
120
121
 
@@ -304,10 +305,15 @@ class PyTorchBackend(Backend):
304
305
  model = model.cuda()
305
306
  if compile_mode == 'compile':
306
307
  model = torch.compile(model)
308
+ elif compile_mode == 'script':
309
+ model = torch.jit.script(model)
307
310
  self.model = model
308
311
 
309
312
  suffix = ' GPU' if gpu else ' CPU'
310
- self.name = ('PT compile' if compile_mode == 'compile' else 'PyTorch') + suffix
313
+ mode_name = {'compile': 'PT compile', 'script': 'PT script', None: 'PT eager'}[
314
+ compile_mode
315
+ ]
316
+ self.name = mode_name + suffix
311
317
 
312
318
  def prepare(self, pose, shape, trans):
313
319
  inputs = dict(
@@ -139,17 +139,25 @@ python_display_short_literal_types = True
139
139
 
140
140
 
141
141
  def autodoc_skip_member(app, what, name, obj, skip, options):
142
- """Skip members (functions, classes, modules) without docstrings."""
142
+ """Skip members without docstrings or from undocumented modules."""
143
143
  if not getattr(obj, 'docstring', None):
144
144
  return True
145
145
  elif what in ('class', 'function', 'attribute'):
146
- # Check if the module of the class has a docstring
147
146
  module_name = '.'.join(name.split('.')[:-1])
148
147
  try:
149
- module = importlib.import_module(module_name)
150
- return not getattr(module, '__doc__', None)
148
+ mod = importlib.import_module(module_name)
149
+ if not getattr(mod, '__doc__', None):
150
+ return True # Module has no docstring, skip its members
151
151
  except ModuleNotFoundError:
152
+ # Import failed (e.g. attribute path like Class.attr, or missing dep).
153
+ # Defer to AutoAPI default.
152
154
  return None
155
+ # For private names, defer to AutoAPI default (which skips them).
156
+ # For public names, force-include (overrides the imported-members default).
157
+ short_name = name.split('.')[-1]
158
+ if short_name.startswith('_') and not short_name.startswith('__'):
159
+ return None
160
+ return False
153
161
  return skip
154
162
 
155
163
 
@@ -157,14 +165,25 @@ def linkcode_resolve(domain, info):
157
165
  if domain != 'py':
158
166
  return None
159
167
 
168
+ fullname = info['fullname']
160
169
  try:
161
- obj = eval(info['fullname'], module.__dict__)
162
- file, start, end = get_line_numbers(obj)
163
- relpath = os.path.relpath(file, os.path.dirname(module.__file__))
164
- return f'{repo_url}/blob/v{release}/src/{main_module_name}/{relpath}#L{start}-L{end}'
165
- except Exception:
170
+ file, start, end = get_line_numbers(eval(fullname))
171
+ except AttributeError:
172
+ # Instance attribute (dataclass field or self.x = ... in __init__)
173
+ parts = fullname.rsplit('.', 1)
174
+ if len(parts) != 2:
175
+ return None
176
+ try:
177
+ file, start, end = get_attr_line_numbers(eval(parts[0]), parts[1])
178
+ except Exception:
179
+ return None
180
+ except Exception as e:
181
+ print(f'linkcode_resolve failed: {info} — {e}')
166
182
  return None
167
183
 
184
+ relpath = os.path.relpath(file, os.path.dirname(module.__file__))
185
+ return f'{repo_url}/blob/v{release}/src/{main_module_name}/{relpath}#L{start}-L{end}'
186
+
168
187
 
169
188
  def get_line_numbers(obj):
170
189
  if isinstance(obj, property):
@@ -196,6 +215,22 @@ def get_enum_member_line_numbers(obj):
196
215
  raise ValueError(f'Enum member {obj.name} not found in {class_}')
197
216
 
198
217
 
218
+ def get_attr_line_numbers(class_, attr_name):
219
+ with module_restored(class_):
220
+ source_lines, start_line = inspect.getsourcelines(class_)
221
+ for i, line in enumerate(source_lines):
222
+ stripped = line.strip()
223
+ if (
224
+ stripped.startswith(f'{attr_name}:')
225
+ or stripped.startswith(f'{attr_name} :')
226
+ or f'self.{attr_name} =' in stripped
227
+ or f'self.{attr_name}=' in stripped
228
+ ):
229
+ return inspect.getsourcefile(class_), start_line + i, start_line + i
230
+ else:
231
+ raise ValueError(f'Attribute {attr_name} not found in {class_}')
232
+
233
+
199
234
  def get_member_line_numbers(obj: types.MemberDescriptorType):
200
235
  class_ = obj.__objclass__
201
236
  with module_restored(class_):
@@ -20,23 +20,34 @@ https://smpl.is.tue.mpg.de/ first), then download SMPL, SMPL-X, SMPL+H, and
20
20
  MANO model files.
21
21
 
22
22
  By default, files are saved to the location given by the ``SMPLFITTER_BODY_MODELS``
23
- environment variable, or ``$DATA_ROOT/body_models``, or
24
- ``~/.local/share/smplfitter/body_models``. You can also pass a path directly:
23
+ environment variable, or ``$DATA_ROOT/body_models``, or the platform default data
24
+ directory (e.g. ``~/.local/share/smplfitter/body_models`` on Linux).
25
+ You can also pass a path directly:
25
26
 
26
27
  .. code-block:: bash
27
28
 
28
29
  python -m smplfitter.download /path/to/body_models
29
30
 
30
- To tell SMPLFitter where to find the models at runtime:
31
+ At runtime, SMPLFitter checks the same locations automatically (in order):
32
+
33
+ 1. ``$SMPLFITTER_BODY_MODELS/``
34
+ 2. ``$DATA_ROOT/body_models/``
35
+ 3. ``./body_models/``
36
+ 4. The platform default data directory (via ``platformdirs``)
37
+
38
+ If you downloaded to a custom location, set an environment variable or pass the path
39
+ directly:
31
40
 
32
41
  .. code-block:: bash
33
42
 
34
- # Option 1: package-specific variable (recommended)
43
+ # Option 1: package-specific variable
35
44
  export SMPLFITTER_BODY_MODELS=/path/to/body_models
36
45
 
37
46
  # Option 2: generic data root
38
47
  export DATA_ROOT=/path/to/data # looks for $DATA_ROOT/body_models/
39
48
 
49
+ .. code-block:: python
50
+
40
51
  # Option 3: pass directly in code
41
52
  body_model = BodyModel('smpl', 'neutral', model_root='/path/to/body_models/smpl')
42
53
 
@@ -309,26 +320,55 @@ The converter also supports:
309
320
  * ``known_output_shape_betas``: If shape is already known, only fit pose
310
321
 
311
322
 
312
- Use the Convenience Functions
313
- -----------------------------
323
+ Rotate and Translate Body Parameters
324
+ -------------------------------------
314
325
 
315
- For one-off fitting without manually creating model and fitter objects:
326
+ Rotating a parametric body is nontrivial because the global orientation rotates around
327
+ the pelvis joint, not the coordinate origin. Use
328
+ :meth:`~smplfitter.pt.BodyModel.rototranslate` to apply a rotation and translation
329
+ to body parameters correctly:
316
330
 
317
331
  .. code-block:: python
318
332
 
319
- from smplfitter.pt import fit
333
+ import torch
334
+ from smplfitter.pt import BodyModel
320
335
 
321
- result = fit(
322
- verts=vertices,
323
- joints=joints,
324
- body_model_name='smpl',
325
- gender='neutral',
326
- num_betas=10,
327
- num_iter=3,
336
+ body_model = BodyModel('smpl', 'neutral', num_betas=10).cuda()
337
+
338
+ # Apply a rotation R and translation t such that:
339
+ # new_mesh = R @ old_mesh + t
340
+ R = torch.eye(3).cuda() # (3, 3) rotation matrix
341
+ t = torch.zeros(3).cuda() # (3,) translation vector
342
+
343
+ new_pose, new_trans = body_model.rototranslate(
344
+ R=R, t=t,
345
+ pose_rotvecs=pose, # (num_joints * 3,)
346
+ shape_betas=betas, # (num_betas,)
347
+ trans=trans, # (3,)
328
348
  )
329
349
 
330
- For repeated fitting (e.g., in a training loop), use a cached and JIT-compiled
331
- fitter:
350
+ Set ``post_translate=False`` to subtract ``t`` *before* rotation instead of adding it
351
+ after (useful for camera-centric transformations).
352
+
353
+
354
+ Cache Body Models
355
+ -----------------
356
+
357
+ If you create the same :class:`~smplfitter.pt.BodyModel` in multiple places (e.g.,
358
+ in a training loop, across files, or when using both a fitter and a converter),
359
+ use :func:`~smplfitter.pt.get_cached_body_model` to avoid loading the model files
360
+ repeatedly:
361
+
362
+ .. code-block:: python
363
+
364
+ from smplfitter.pt import get_cached_body_model
365
+
366
+ # First call loads from disk; subsequent calls return the cached instance
367
+ body_model = get_cached_body_model('smpl', 'neutral')
368
+
369
+ The cached instance is shared, so do not modify it in place.
370
+
371
+ For repeated fitting with a cached and JIT-compiled fitter:
332
372
 
333
373
  .. code-block:: python
334
374
 
@@ -12,7 +12,9 @@ Example use cases:
12
12
  * You extracted nonparametric vertex and joint estimates from an RGB image, e.g. using `Neural Localizer Fields (NLF) <https://virtualhumans.mpi-inf.mpg.de/nlf>`_, and want to express this estimate in parametric form, for example to feed it to another model that expects body model parameters.
13
13
  * You want to convert between body models. For example, you have SMPL parameters from some dataset but need SMPL-X parameters as input to some pretrained model (or vice versa).
14
14
 
15
- We provide the implementation in **PyTorch, TensorFlow and NumPy**.
15
+ We provide the implementation in **PyTorch, TensorFlow, NumPy, JAX, and Numba**.
16
+ Import from the backend submodule you need: ``from smplfitter.pt import ...`` for PyTorch,
17
+ ``from smplfitter.np import ...`` for NumPy, etc.
16
18
 
17
19
  The algorithm is **fast**, optimized for **batch** processing, can run on the **GPU** and is **differentiable**. There are no learnable parameters here, nor sensitivity to initialization. Just solving equation systems.
18
20
 
@@ -29,7 +31,19 @@ Installation
29
31
  Download Body Model Files
30
32
  --------------------------
31
33
 
32
- You need to download the body model data files from the corresponding websites for this code to work. You only need the ones that you plan to use. There should be a ``DATA_ROOT`` environment variable under which a ``body_models`` directory should look like this:
34
+ You need to download the body model data files from the corresponding websites for this code to work. You only need the ones that you plan to use.
35
+
36
+ Set one of the following environment variables to tell SMPLFitter where to find them:
37
+
38
+ .. code-block:: bash
39
+
40
+ # Option 1: package-specific variable (recommended)
41
+ export SMPLFITTER_BODY_MODELS=/path/to/body_models
42
+
43
+ # Option 2: generic data root
44
+ export DATA_ROOT=/path/to/data # looks for $DATA_ROOT/body_models/
45
+
46
+ The ``body_models`` directory should look like this:
33
47
 
34
48
  .. code-block:: text
35
49
 
@@ -69,15 +83,15 @@ Basic Fitting
69
83
  import torch
70
84
  from smplfitter.pt import BodyModel, BodyFitter
71
85
 
72
- body_model = BodyModel('smpl', 'neutral').cuda()
73
- fitter = BodyFitter(body_model, num_betas=10).cuda()
86
+ body_model = BodyModel('smpl', 'neutral', num_betas=10).cuda()
87
+ fitter = BodyFitter(body_model).cuda()
74
88
  fitter = torch.jit.script(fitter)
75
89
 
76
90
  batch_size = 30
77
91
  vertices = torch.rand((batch_size, 6890, 3)).cuda()
78
92
  joints = torch.rand((batch_size, 24, 3)).cuda()
79
93
 
80
- fit_res = fitter.fit(vertices, joints, n_iter=3, beta_regularizer=1)
94
+ fit_res = fitter.fit(target_vertices=vertices, target_joints=joints, num_iter=3, beta_regularizer=1)
81
95
  fit_res['pose_rotvecs'], fit_res['shape_betas'], fit_res['trans']
82
96
 
83
97
  Body Model Conversion (Transfer)
@@ -86,7 +100,7 @@ Body Model Conversion (Transfer)
86
100
  .. code-block:: python
87
101
 
88
102
  import torch
89
- from smplfitter.pt import BodyConverter
103
+ from smplfitter.pt import BodyModel, BodyConverter
90
104
 
91
105
  bm_in = BodyModel('smpl', 'neutral')
92
106
  bm_out = BodyModel('smplx', 'neutral')
@@ -123,6 +137,7 @@ This algorithm was developed for and described in the following paper:
123
137
  :caption: Contents
124
138
 
125
139
  howto
140
+ smpl_to_smplx
126
141
  API Reference <api/smplfitter/index>
127
142
 
128
143
  Indices and tables
@@ -0,0 +1,83 @@
1
+ SMPL-to-SMPL-X Conversion Benchmark
2
+ ====================================
3
+
4
+ :class:`~smplfitter.pt.BodyConverter` was benchmarked against the official
5
+ `SMPL-X transfer tool <https://github.com/vchoutas/smplx/tree/main/transfer_model>`_
6
+ on 33 sample meshes from the official SMPL-X repository (the same evaluation setup as
7
+ Table 10 in the `NLF paper <https://arxiv.org/abs/2407.07532>`_).
8
+
9
+ Error is measured as per-vertex Euclidean distance (mm) between the ground-truth SMPL-X
10
+ mesh and the converted mesh.
11
+
12
+ Official SMPL-X Converter
13
+ -------------------------
14
+
15
+ .. list-table::
16
+ :header-rows: 1
17
+ :widths: 20 30 20
18
+
19
+ * - Iterations
20
+ - Time
21
+ - Error (mm)
22
+ * - 25
23
+ - 3 min 18 s
24
+ - 14.0
25
+ * - 50
26
+ - 16 min 35 s
27
+ - 6.2
28
+ * - 100
29
+ - 33 min
30
+ - 5.0
31
+
32
+ SMPLFitter (PyTorch, GPU)
33
+ -------------------------
34
+
35
+ .. list-table::
36
+ :header-rows: 1
37
+ :widths: 20 30 20
38
+
39
+ * - Iterations
40
+ - Time (33 meshes)
41
+ - Error (mm)
42
+ * - 1
43
+ - 43 ms
44
+ - 8.3
45
+ * - 2
46
+ - 75 ms
47
+ - 8.0
48
+ * - 3
49
+ - 110 ms
50
+ - 8.0
51
+ * - 5
52
+ - 190 ms
53
+ - 8.1
54
+
55
+ SMPLFitter (TensorFlow, GPU)
56
+ -----------------------------
57
+
58
+ .. list-table::
59
+ :header-rows: 1
60
+ :widths: 20 30 20
61
+
62
+ * - Iterations
63
+ - Time (33 meshes)
64
+ - Error (mm)
65
+ * - 1
66
+ - 25 ms
67
+ - 8.3
68
+ * - 2
69
+ - 35 ms
70
+ - 8.0
71
+ * - 3
72
+ - 50 ms
73
+ - 8.0
74
+ * - 5
75
+ - 78 ms
76
+ - 8.1
77
+
78
+ Reproducing
79
+ -----------
80
+
81
+ .. code-block:: bash
82
+
83
+ python benchmark/bench_converter.py --backends pt tf
@@ -8,7 +8,7 @@ dynamic = ["version"]
8
8
  description = "Fast inverse kinematics and shape fitting for SMPL-family models in NumPy, PyTorch, TensorFlow, JAX, and Numba"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
11
- license = { file = "LICENSE" }
11
+ license = "MIT"
12
12
 
13
13
  authors = [
14
14
  { name = "István Sárándi", email = "istvan.sarandi@uni-tuebingen.de" }
@@ -37,7 +37,6 @@ keywords = [
37
37
  classifiers = [
38
38
  "Development Status :: 4 - Beta",
39
39
  "Intended Audience :: Science/Research",
40
- "License :: OSI Approved :: MIT License",
41
40
  "Programming Language :: Python",
42
41
  "Programming Language :: Python :: 3",
43
42
  "Programming Language :: Python :: 3.8",
@@ -55,6 +54,7 @@ dependencies = [
55
54
  "numpy",
56
55
  "scipy",
57
56
  "chumpy",
57
+ "platformdirs",
58
58
  ]
59
59
 
60
60
  [project.optional-dependencies]
@@ -62,6 +62,7 @@ tensorflow = ["tensorflow"]
62
62
  pytorch = ["torch"]
63
63
  jax = ["jax", "jaxlib"]
64
64
  numba = ["numba"]
65
+ viz = ["moderngl", "glfw", "poseviz", "framepump", "torch", "deltacamera"]
65
66
  test = ["pytest>=7.0", "pytest-cov"]
66
67
  lint = ["ruff", "pre-commit"]
67
68
  docs = [
@@ -74,6 +75,11 @@ docs = [
74
75
  "pydata-sphinx-theme",
75
76
  "setuptools-scm",
76
77
  "toml",
78
+ "torch",
79
+ "tensorflow",
80
+ "jax",
81
+ "jaxlib",
82
+ "numba",
77
83
  ]
78
84
  dev = ["smplfitter[test,lint,docs,pytorch]"]
79
85
 
@@ -10,11 +10,12 @@ Main submodules:
10
10
 
11
11
  from __future__ import annotations
12
12
 
13
- from .common import ModelData, initialize
13
+ from .common import _set_module_for_docs
14
14
 
15
15
  try:
16
16
  from ._version import version as __version__
17
17
  except ImportError:
18
18
  __version__ = '0.0.0'
19
19
 
20
- __all__ = ['ModelData', 'initialize', '__version__']
20
+ __all__ = ['__version__']
21
+ _set_module_for_docs(__name__, globals(), __all__)
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.3.2.dev0'
32
- __version_tuple__ = version_tuple = (0, 3, 2, 'dev0')
31
+ __version__ = version = '0.4.0'
32
+ __version_tuple__ = version_tuple = (0, 4, 0)
33
33
 
34
- __commit_id__ = commit_id = 'gbb6c267c5'
34
+ __commit_id__ = commit_id = 'g2fe42baa8'