keypoints2body 0.1.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 (43) hide show
  1. keypoints2body-0.1.0/PKG-INFO +75 -0
  2. keypoints2body-0.1.0/README.md +49 -0
  3. keypoints2body-0.1.0/keypoints2body/__init__.py +28 -0
  4. keypoints2body-0.1.0/keypoints2body/api/__init__.py +8 -0
  5. keypoints2body-0.1.0/keypoints2body/api/frame.py +111 -0
  6. keypoints2body-0.1.0/keypoints2body/api/model_factory.py +31 -0
  7. keypoints2body-0.1.0/keypoints2body/api/sequence.py +193 -0
  8. keypoints2body-0.1.0/keypoints2body/cli/__init__.py +1 -0
  9. keypoints2body-0.1.0/keypoints2body/cli/eval.py +292 -0
  10. keypoints2body-0.1.0/keypoints2body/cli/fit_frame.py +31 -0
  11. keypoints2body-0.1.0/keypoints2body/cli/fit_seq.py +29 -0
  12. keypoints2body-0.1.0/keypoints2body/core/__init__.py +9 -0
  13. keypoints2body-0.1.0/keypoints2body/core/config.py +50 -0
  14. keypoints2body-0.1.0/keypoints2body/core/constants.py +63 -0
  15. keypoints2body-0.1.0/keypoints2body/core/engine.py +151 -0
  16. keypoints2body-0.1.0/keypoints2body/core/estimators/__init__.py +5 -0
  17. keypoints2body-0.1.0/keypoints2body/core/estimators/base.py +19 -0
  18. keypoints2body-0.1.0/keypoints2body/core/estimators/factory.py +36 -0
  19. keypoints2body-0.1.0/keypoints2body/core/estimators/optimization.py +56 -0
  20. keypoints2body-0.1.0/keypoints2body/core/fitters/__init__.py +4 -0
  21. keypoints2body-0.1.0/keypoints2body/core/fitters/camera_space.py +274 -0
  22. keypoints2body-0.1.0/keypoints2body/core/fitters/world_space.py +208 -0
  23. keypoints2body-0.1.0/keypoints2body/core/joints/__init__.py +17 -0
  24. keypoints2body-0.1.0/keypoints2body/core/joints/adapters.py +213 -0
  25. keypoints2body-0.1.0/keypoints2body/core/losses.py +93 -0
  26. keypoints2body-0.1.0/keypoints2body/core/prior.py +225 -0
  27. keypoints2body-0.1.0/keypoints2body/core/shape.py +115 -0
  28. keypoints2body-0.1.0/keypoints2body/io/__init__.py +3 -0
  29. keypoints2body-0.1.0/keypoints2body/io/motion.py +138 -0
  30. keypoints2body-0.1.0/keypoints2body/models/__init__.py +15 -0
  31. keypoints2body-0.1.0/keypoints2body/models/smpl_data.py +102 -0
  32. keypoints2body-0.1.0/keypoints2body.egg-info/PKG-INFO +75 -0
  33. keypoints2body-0.1.0/keypoints2body.egg-info/SOURCES.txt +41 -0
  34. keypoints2body-0.1.0/keypoints2body.egg-info/dependency_links.txt +1 -0
  35. keypoints2body-0.1.0/keypoints2body.egg-info/entry_points.txt +4 -0
  36. keypoints2body-0.1.0/keypoints2body.egg-info/requires.txt +19 -0
  37. keypoints2body-0.1.0/keypoints2body.egg-info/top_level.txt +1 -0
  38. keypoints2body-0.1.0/pyproject.toml +44 -0
  39. keypoints2body-0.1.0/setup.cfg +4 -0
  40. keypoints2body-0.1.0/tests/test_adapters.py +38 -0
  41. keypoints2body-0.1.0/tests/test_api_surface.py +6 -0
  42. keypoints2body-0.1.0/tests/test_integration_smoke.py +20 -0
  43. keypoints2body-0.1.0/tests/test_models.py +35 -0
@@ -0,0 +1,75 @@
1
+ Metadata-Version: 2.4
2
+ Name: keypoints2body
3
+ Version: 0.1.0
4
+ Summary: SMPL/SMPLH/SMPLX optimization library for single frames and sequences
5
+ Author: keypoints2body contributors
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: Programming Language :: Python :: 3.10
8
+ Classifier: Operating System :: OS Independent
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: numpy
12
+ Requires-Dist: torch
13
+ Requires-Dist: smplx
14
+ Requires-Dist: h5py
15
+ Requires-Dist: tqdm
16
+ Provides-Extra: dev
17
+ Requires-Dist: ruff; extra == "dev"
18
+ Requires-Dist: pytest; extra == "dev"
19
+ Requires-Dist: mypy; extra == "dev"
20
+ Provides-Extra: docs
21
+ Requires-Dist: sphinx>=7; extra == "docs"
22
+ Requires-Dist: furo>=2024.8.6; extra == "docs"
23
+ Requires-Dist: sphinxcontrib-mermaid>=0.9; extra == "docs"
24
+ Provides-Extra: eval
25
+ Provides-Extra: render
26
+
27
+ ![keypoints2body header](docs/assets/header.svg)
28
+
29
+ # keypoints2body
30
+
31
+ `keypoints2body` is a Python library for optimizing SMPL-family body model parameters
32
+ from 3D joints for both single frames and motion sequences.
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ pip install -e .
38
+ ```
39
+
40
+ ## Quick usage
41
+
42
+ ```python
43
+ import numpy as np
44
+ from keypoints2body import optimize_params_frame
45
+
46
+ joints = np.zeros((22, 3), dtype=np.float32)
47
+ result = optimize_params_frame(joints, joint_layout="AMASS")
48
+ ```
49
+
50
+ ## Documentation
51
+
52
+ Full documentation lives under [`docs/`](docs/) and is intended for Sphinx.
53
+
54
+ Suggested starting points:
55
+
56
+ - [Getting started](docs/getting_started.rst)
57
+ - [Library usage](docs/usage.rst)
58
+ - [CLI reference](docs/cli.rst)
59
+ - [Architecture](docs/architecture.rst)
60
+ - [API reference](docs/api.rst)
61
+ - [Contributor guide](docs/contributing.rst)
62
+
63
+ ## CLI
64
+
65
+ ```bash
66
+ keypoints2body-fit-frame --help
67
+ keypoints2body-fit-seq --help
68
+ keypoints2body-eval --help
69
+ ```
70
+
71
+ Project script:
72
+
73
+ ```bash
74
+ python smpl_fit.py --help
75
+ ```
@@ -0,0 +1,49 @@
1
+ ![keypoints2body header](docs/assets/header.svg)
2
+
3
+ # keypoints2body
4
+
5
+ `keypoints2body` is a Python library for optimizing SMPL-family body model parameters
6
+ from 3D joints for both single frames and motion sequences.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ pip install -e .
12
+ ```
13
+
14
+ ## Quick usage
15
+
16
+ ```python
17
+ import numpy as np
18
+ from keypoints2body import optimize_params_frame
19
+
20
+ joints = np.zeros((22, 3), dtype=np.float32)
21
+ result = optimize_params_frame(joints, joint_layout="AMASS")
22
+ ```
23
+
24
+ ## Documentation
25
+
26
+ Full documentation lives under [`docs/`](docs/) and is intended for Sphinx.
27
+
28
+ Suggested starting points:
29
+
30
+ - [Getting started](docs/getting_started.rst)
31
+ - [Library usage](docs/usage.rst)
32
+ - [CLI reference](docs/cli.rst)
33
+ - [Architecture](docs/architecture.rst)
34
+ - [API reference](docs/api.rst)
35
+ - [Contributor guide](docs/contributing.rst)
36
+
37
+ ## CLI
38
+
39
+ ```bash
40
+ keypoints2body-fit-frame --help
41
+ keypoints2body-fit-seq --help
42
+ keypoints2body-eval --help
43
+ ```
44
+
45
+ Project script:
46
+
47
+ ```bash
48
+ python smpl_fit.py --help
49
+ ```
@@ -0,0 +1,28 @@
1
+ from importlib.metadata import PackageNotFoundError, version
2
+
3
+ from .api.frame import optimize_params_frame
4
+ from .api.sequence import optimize_params_sequence, optimize_shape_sequence
5
+ from .models.smpl_data import (
6
+ BodyModelFitResult,
7
+ BodyModelParams,
8
+ SMPLData,
9
+ SMPLHData,
10
+ SMPLXData,
11
+ )
12
+
13
+ try:
14
+ __version__ = version("keypoints2body")
15
+ except PackageNotFoundError:
16
+ __version__ = "0.0.0"
17
+
18
+ __all__ = [
19
+ "__version__",
20
+ "optimize_params_frame",
21
+ "optimize_params_sequence",
22
+ "optimize_shape_sequence",
23
+ "BodyModelFitResult",
24
+ "BodyModelParams",
25
+ "SMPLData",
26
+ "SMPLHData",
27
+ "SMPLXData",
28
+ ]
@@ -0,0 +1,8 @@
1
+ from .frame import optimize_params_frame
2
+ from .sequence import optimize_params_sequence, optimize_shape_sequence
3
+
4
+ __all__ = [
5
+ "optimize_params_frame",
6
+ "optimize_params_sequence",
7
+ "optimize_shape_sequence",
8
+ ]
@@ -0,0 +1,111 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Optional
4
+
5
+ import torch
6
+
7
+ from ..core.config import BodyModelConfig, FrameOptimizeConfig
8
+ from ..core.engine import OptimizeEngine, default_init_params, load_mean_pose_shape
9
+ from ..core.joints.adapters import adapt_layout_and_conf, normalize_joints_frame
10
+ from ..models.smpl_data import BodyModelFitResult, BodyModelParams, SMPLData
11
+ from .model_factory import load_body_model
12
+
13
+ DEFAULT_MEAN_FILE = "./data/models/neutral_smpl_mean_params.h5"
14
+
15
+
16
+ def optimize_params_frame(
17
+ joints,
18
+ *,
19
+ prev_params: Optional[BodyModelParams] = None,
20
+ body_model: str = "smpl",
21
+ joint_layout: Optional[str] = None,
22
+ model=None,
23
+ config: Optional[FrameOptimizeConfig | dict] = None,
24
+ device=None,
25
+ ) -> BodyModelFitResult:
26
+ """Optimize body parameters for a single frame of 3D joints.
27
+
28
+ Args:
29
+ joints: Frame keypoints with shape ``(K,3)`` or ``(K,4)``.
30
+ prev_params: Optional warm-start parameters from a previous fit.
31
+ body_model: Body model type (for example ``"smpl"``).
32
+ joint_layout: Optional explicit layout label for adapter selection.
33
+ model: Optional preloaded body model instance.
34
+ config: Optional frame config or dict equivalent.
35
+ device: Optional torch device specifier.
36
+
37
+ Returns:
38
+ Typed fitting result containing optimized parameters, joints, vertices, and loss.
39
+ """
40
+ device = torch.device(device) if device is not None else torch.device("cpu")
41
+
42
+ if isinstance(config, dict):
43
+ frame_cfg = FrameOptimizeConfig(**config)
44
+ elif isinstance(config, FrameOptimizeConfig):
45
+ frame_cfg = config
46
+ else:
47
+ frame_cfg = FrameOptimizeConfig()
48
+
49
+ if frame_cfg.input_type != "joints3d":
50
+ raise NotImplementedError(
51
+ f"input_type='{frame_cfg.input_type}' is not implemented in this release. "
52
+ "Current APIs support only joints3d."
53
+ )
54
+
55
+ j3d, conf_3d = normalize_joints_frame(joints)
56
+ j_np = j3d.squeeze(0).cpu().numpy()[None, ...]
57
+ c_np = conf_3d.cpu().numpy()[None, ...]
58
+ j_np, c_np, out_layout = adapt_layout_and_conf(j_np, c_np, joint_layout)
59
+ j3d = torch.as_tensor(j_np, dtype=torch.float32, device=device)
60
+ conf_3d = torch.as_tensor(c_np[0], dtype=torch.float32, device=device)
61
+
62
+ if out_layout not in ("SMPL24", "AMASS"):
63
+ raise ValueError(f"Unsupported output layout after adaptation: {out_layout}")
64
+ frame_cfg.joints_category = out_layout
65
+
66
+ if model is None:
67
+ body_cfg = BodyModelConfig(model_type=body_model)
68
+ model = load_body_model(body_cfg, device)
69
+
70
+ engine = OptimizeEngine(model=model, frame_config=frame_cfg, device=device)
71
+
72
+ if prev_params is None:
73
+ init_mean_pose, init_mean_shape = load_mean_pose_shape(
74
+ DEFAULT_MEAN_FILE, device
75
+ )
76
+ init_params = default_init_params(
77
+ init_mean_pose,
78
+ init_mean_shape,
79
+ j3d,
80
+ model,
81
+ joints_category=frame_cfg.joints_category,
82
+ coordinate_mode=frame_cfg.coordinate_mode,
83
+ )
84
+ else:
85
+ init_params = prev_params.to(device)
86
+ if frame_cfg.coordinate_mode == "world" and init_params.transl is None:
87
+ pose = init_params.pose
88
+ if not isinstance(pose, torch.Tensor):
89
+ pose = torch.as_tensor(pose, dtype=torch.float32, device=device)
90
+ betas = init_params.betas
91
+ if not isinstance(betas, torch.Tensor):
92
+ betas = torch.as_tensor(betas, dtype=torch.float32, device=device)
93
+ transl = default_init_params(
94
+ pose,
95
+ betas,
96
+ j3d,
97
+ model,
98
+ frame_cfg.joints_category,
99
+ frame_cfg.coordinate_mode,
100
+ ).transl
101
+ init_params = SMPLData(
102
+ betas=betas,
103
+ global_orient=pose[:, :3],
104
+ body_pose=pose[:, 3:],
105
+ transl=transl,
106
+ metadata=dict(getattr(init_params, "metadata", {})),
107
+ )
108
+
109
+ return engine.fit_frame(
110
+ init_params=init_params, j3d=j3d, conf_3d=conf_3d, seq_ind=0
111
+ )
@@ -0,0 +1,31 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ import smplx
6
+ import torch
7
+
8
+ from ..core.config import BodyModelConfig
9
+
10
+
11
+ def load_body_model(
12
+ config: BodyModelConfig,
13
+ device: torch.device,
14
+ ):
15
+ """Load a body model module with ``smplx.create``.
16
+
17
+ Args:
18
+ config: Model-loading configuration.
19
+ device: Torch device where the model should live.
20
+
21
+ Returns:
22
+ A body model module returned by ``smplx.create`` moved to ``device``.
23
+ """
24
+ model_dir = Path(config.model_dir).expanduser()
25
+ return smplx.create(
26
+ str(model_dir),
27
+ model_type=config.model_type,
28
+ gender=config.gender,
29
+ ext=config.ext,
30
+ batch_size=config.batch_size,
31
+ ).to(device)
@@ -0,0 +1,193 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Optional
4
+
5
+ import torch
6
+
7
+ from ..core.config import BodyModelConfig, FrameOptimizeConfig, SequenceOptimizeConfig
8
+ from ..core.engine import (
9
+ OptimizeEngine,
10
+ default_init_params,
11
+ load_mean_pose_shape,
12
+ optimize_shape_pass,
13
+ )
14
+ from ..core.joints.adapters import adapt_layout_and_conf, normalize_joints_sequence
15
+ from ..models.smpl_data import BodyModelFitResult, BodyModelParams, SMPLData
16
+ from .model_factory import load_body_model
17
+
18
+ DEFAULT_MEAN_FILE = "./data/models/neutral_smpl_mean_params.h5"
19
+
20
+
21
+ def optimize_params_sequence(
22
+ joints_seq,
23
+ *,
24
+ init_params: Optional[BodyModelParams] = None,
25
+ body_model: str = "smpl",
26
+ joint_layout: Optional[str] = None,
27
+ model=None,
28
+ config: Optional[SequenceOptimizeConfig | dict] = None,
29
+ device=None,
30
+ ) -> list[BodyModelFitResult]:
31
+ """Optimize body parameters for a full motion sequence.
32
+
33
+ Args:
34
+ joints_seq: Sequence keypoints with shape ``(T,K,3)`` or ``(T,K,4)``.
35
+ init_params: Optional initial parameters for frame 0.
36
+ body_model: Body model type (for example ``"smpl"``).
37
+ joint_layout: Optional explicit layout label for adapter selection.
38
+ model: Optional preloaded body model instance.
39
+ config: Optional sequence config or dict equivalent.
40
+ device: Optional torch device specifier.
41
+
42
+ Returns:
43
+ Per-frame optimization results in temporal order.
44
+ """
45
+ device = torch.device(device) if device is not None else torch.device("cpu")
46
+
47
+ if isinstance(config, dict):
48
+ frame_cfg = FrameOptimizeConfig(**config.get("frame", {}))
49
+ seq_cfg = SequenceOptimizeConfig(
50
+ frame=frame_cfg,
51
+ num_shape_iters=config.get("num_shape_iters", 40),
52
+ num_shape_frames=config.get("num_shape_frames", 50),
53
+ use_shape_optimization=config.get("use_shape_optimization", True),
54
+ use_previous_frame_init=config.get("use_previous_frame_init", True),
55
+ fix_foot=config.get("fix_foot", False),
56
+ limit_frames=config.get("limit_frames", None),
57
+ )
58
+ elif isinstance(config, SequenceOptimizeConfig):
59
+ seq_cfg = config
60
+ else:
61
+ seq_cfg = SequenceOptimizeConfig()
62
+
63
+ if seq_cfg.frame.input_type != "joints3d":
64
+ raise NotImplementedError(
65
+ f"input_type='{seq_cfg.frame.input_type}' is not implemented in this release. "
66
+ "Current APIs support only joints3d."
67
+ )
68
+
69
+ xyz, conf = normalize_joints_sequence(joints_seq)
70
+ xyz_np, conf_np, out_layout = adapt_layout_and_conf(
71
+ xyz.cpu().numpy(), conf.cpu().numpy(), joint_layout
72
+ )
73
+ xyz = torch.as_tensor(xyz_np, dtype=torch.float32, device=device)
74
+ conf = torch.as_tensor(conf_np, dtype=torch.float32, device=device)
75
+
76
+ if out_layout not in ("SMPL24", "AMASS"):
77
+ raise ValueError(f"Unsupported output layout after adaptation: {out_layout}")
78
+ seq_cfg.frame.joints_category = out_layout
79
+
80
+ if seq_cfg.limit_frames is not None and seq_cfg.limit_frames > 0:
81
+ xyz = xyz[: seq_cfg.limit_frames]
82
+ conf = conf[: seq_cfg.limit_frames]
83
+
84
+ if seq_cfg.fix_foot and xyz.shape[1] > 11:
85
+ conf[:, 7] = 1.5
86
+ conf[:, 8] = 1.5
87
+ conf[:, 10] = 1.5
88
+ conf[:, 11] = 1.5
89
+
90
+ if model is None:
91
+ body_cfg = BodyModelConfig(model_type=body_model)
92
+ model = load_body_model(body_cfg, device)
93
+
94
+ init_mean_pose, init_mean_shape = load_mean_pose_shape(DEFAULT_MEAN_FILE, device)
95
+ betas_opt = optimize_shape_pass(
96
+ model=model,
97
+ seq_config=seq_cfg,
98
+ init_mean_shape=init_mean_shape,
99
+ init_mean_pose=init_mean_pose,
100
+ data_tensor=xyz,
101
+ confidence_input=conf[0],
102
+ device=device,
103
+ )
104
+
105
+ engine = OptimizeEngine(model=model, frame_config=seq_cfg.frame, device=device)
106
+ results: list[BodyModelFitResult] = []
107
+
108
+ if init_params is None:
109
+ prev = default_init_params(
110
+ init_mean_pose,
111
+ betas_opt,
112
+ xyz[0:1],
113
+ model,
114
+ joints_category=seq_cfg.frame.joints_category,
115
+ coordinate_mode=seq_cfg.frame.coordinate_mode,
116
+ )
117
+ else:
118
+ prev = init_params.to(device)
119
+
120
+ for idx in range(xyz.shape[0]):
121
+ frame = xyz[idx : idx + 1]
122
+ frame_conf = conf[idx]
123
+
124
+ if seq_cfg.frame.coordinate_mode == "world" and prev.transl is None:
125
+ pose = (
126
+ prev.pose
127
+ if isinstance(prev.pose, torch.Tensor)
128
+ else torch.as_tensor(prev.pose, dtype=torch.float32, device=device)
129
+ )
130
+ betas = (
131
+ prev.betas
132
+ if isinstance(prev.betas, torch.Tensor)
133
+ else torch.as_tensor(prev.betas, dtype=torch.float32, device=device)
134
+ )
135
+ prev = SMPLData(
136
+ betas=betas,
137
+ global_orient=pose[:, :3],
138
+ body_pose=pose[:, 3:],
139
+ transl=default_init_params(
140
+ pose,
141
+ betas,
142
+ frame,
143
+ model,
144
+ seq_cfg.frame.joints_category,
145
+ seq_cfg.frame.coordinate_mode,
146
+ ).transl,
147
+ metadata=dict(getattr(prev, "metadata", {})),
148
+ )
149
+
150
+ res = engine.fit_frame(
151
+ init_params=prev, j3d=frame, conf_3d=frame_conf, seq_ind=idx
152
+ )
153
+ results.append(res)
154
+ if seq_cfg.use_previous_frame_init:
155
+ prev = res.params
156
+
157
+ return results
158
+
159
+
160
+ def optimize_shape_sequence(
161
+ joints_seq,
162
+ *,
163
+ body_model: str = "smpl",
164
+ joint_layout: Optional[str] = None,
165
+ model=None,
166
+ config: Optional[SequenceOptimizeConfig | dict] = None,
167
+ device=None,
168
+ ) -> BodyModelParams:
169
+ """Run sequence optimization and return the final frame parameters.
170
+
171
+ Args:
172
+ joints_seq: Sequence keypoints input.
173
+ body_model: Body model type.
174
+ joint_layout: Optional explicit layout label.
175
+ model: Optional preloaded body model instance.
176
+ config: Optional sequence config.
177
+ device: Optional torch device specifier.
178
+
179
+ Returns:
180
+ Parameter object from the last optimized frame.
181
+ """
182
+ results = optimize_params_sequence(
183
+ joints_seq,
184
+ init_params=None,
185
+ body_model=body_model,
186
+ joint_layout=joint_layout,
187
+ model=model,
188
+ config=config,
189
+ device=device,
190
+ )
191
+ if not results:
192
+ raise ValueError("No frames were optimized")
193
+ return results[-1].params
@@ -0,0 +1 @@
1
+ __all__ = []