kinemotion 0.70.1__py3-none-any.whl → 0.71.1__py3-none-any.whl
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.
- kinemotion/__init__.py +4 -1
- kinemotion/cmj/analysis.py +79 -30
- kinemotion/cmj/api.py +16 -39
- kinemotion/cmj/cli.py +0 -21
- kinemotion/cmj/debug_overlay.py +154 -286
- kinemotion/cmj/joint_angles.py +96 -31
- kinemotion/cmj/metrics_validator.py +30 -51
- kinemotion/cmj/validation_bounds.py +1 -18
- kinemotion/core/__init__.py +0 -2
- kinemotion/core/auto_tuning.py +91 -99
- kinemotion/core/debug_overlay_utils.py +142 -15
- kinemotion/core/experimental.py +55 -51
- kinemotion/core/filtering.py +15 -11
- kinemotion/core/overlay_constants.py +61 -0
- kinemotion/core/pose.py +67 -499
- kinemotion/core/smoothing.py +65 -51
- kinemotion/core/types.py +15 -0
- kinemotion/core/validation.py +6 -7
- kinemotion/core/video_io.py +14 -9
- kinemotion/dropjump/__init__.py +2 -2
- kinemotion/dropjump/analysis.py +67 -44
- kinemotion/dropjump/api.py +12 -44
- kinemotion/dropjump/cli.py +63 -105
- kinemotion/dropjump/debug_overlay.py +124 -65
- kinemotion/dropjump/validation_bounds.py +1 -1
- kinemotion/models/rtmpose-s_simcc-body7_pt-body7-halpe26_700e-256x192-7f134165_20230605.onnx +0 -0
- kinemotion/models/yolox_tiny_8xb8-300e_humanart-6f3252f9.onnx +0 -0
- {kinemotion-0.70.1.dist-info → kinemotion-0.71.1.dist-info}/METADATA +1 -5
- kinemotion-0.71.1.dist-info/RECORD +50 -0
- kinemotion/core/rtmpose_cpu.py +0 -626
- kinemotion/core/rtmpose_wrapper.py +0 -190
- kinemotion-0.70.1.dist-info/RECORD +0 -51
- {kinemotion-0.70.1.dist-info → kinemotion-0.71.1.dist-info}/WHEEL +0 -0
- {kinemotion-0.70.1.dist-info → kinemotion-0.71.1.dist-info}/entry_points.txt +0 -0
- {kinemotion-0.70.1.dist-info → kinemotion-0.71.1.dist-info}/licenses/LICENSE +0 -0
kinemotion/core/pose.py
CHANGED
|
@@ -18,6 +18,8 @@ Configuration strategies for matching Solution API behavior:
|
|
|
18
18
|
|
|
19
19
|
from __future__ import annotations
|
|
20
20
|
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
21
23
|
import cv2
|
|
22
24
|
import mediapipe as mp
|
|
23
25
|
import numpy as np
|
|
@@ -159,474 +161,42 @@ class MediaPipePoseTracker:
|
|
|
159
161
|
|
|
160
162
|
|
|
161
163
|
class PoseTrackerFactory:
|
|
162
|
-
"""Factory for creating pose trackers
|
|
164
|
+
"""Factory for creating pose trackers.
|
|
163
165
|
|
|
164
|
-
|
|
165
|
-
- RTMPose CUDA: NVIDIA GPU acceleration (fastest, 133 FPS)
|
|
166
|
-
- RTMPose CoreML: Apple Silicon acceleration (42 FPS)
|
|
167
|
-
- RTMPose CPU: Optimized CPU implementation (40-68 FPS)
|
|
168
|
-
- MediaPipe: Fallback baseline (48 FPS)
|
|
166
|
+
Currently supports MediaPipe as the only backend.
|
|
169
167
|
|
|
170
168
|
Usage:
|
|
171
|
-
# Auto-detect best backend
|
|
172
169
|
tracker = PoseTrackerFactory.create()
|
|
173
|
-
|
|
174
|
-
# Force specific backend
|
|
175
|
-
tracker = PoseTrackerFactory.create(backend='rtmpose-cuda')
|
|
176
|
-
|
|
177
|
-
# Check available backends
|
|
178
|
-
available = PoseTrackerFactory.get_available_backends()
|
|
179
170
|
"""
|
|
180
171
|
|
|
181
|
-
# Backend class mappings
|
|
182
|
-
_BACKENDS: dict[str, type] = {}
|
|
183
|
-
|
|
184
172
|
@classmethod
|
|
185
173
|
def create(
|
|
186
174
|
cls,
|
|
187
|
-
backend: str = "
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
"""Create a pose tracker with the specified backend.
|
|
192
|
-
|
|
193
|
-
Args:
|
|
194
|
-
backend: Backend selection:
|
|
195
|
-
- 'auto': Auto-detect best available backend
|
|
196
|
-
- 'mediapipe': MediaPipe Tasks API (baseline)
|
|
197
|
-
- 'rtmpose-cpu': RTMPose optimized CPU
|
|
198
|
-
- 'rtmpose-cuda': RTMPose with CUDA (NVIDIA GPU)
|
|
199
|
-
- 'rtmpose-coreml': RTMPose with CoreML (Apple Silicon)
|
|
200
|
-
mode: RTMPose performance mode ('lightweight', 'balanced', 'performance')
|
|
201
|
-
Only used for RTMPose backends
|
|
202
|
-
**kwargs: Additional arguments passed to tracker constructor
|
|
203
|
-
|
|
204
|
-
Returns:
|
|
205
|
-
Configured pose tracker instance
|
|
206
|
-
|
|
207
|
-
Raises:
|
|
208
|
-
ValueError: If backend is not available or recognized
|
|
209
|
-
"""
|
|
210
|
-
# Auto-detect backend
|
|
211
|
-
if backend == "auto":
|
|
212
|
-
backend = cls._detect_best_backend()
|
|
213
|
-
backend = cls._check_backend_available(backend)
|
|
214
|
-
|
|
215
|
-
# Check environment variable override
|
|
216
|
-
import os
|
|
217
|
-
|
|
218
|
-
env_backend = os.environ.get("POSE_TRACKER_BACKEND")
|
|
219
|
-
if env_backend:
|
|
220
|
-
backend = cls._normalize_backend_name(env_backend)
|
|
221
|
-
|
|
222
|
-
# Verify backend is available
|
|
223
|
-
backend = cls._check_backend_available(backend)
|
|
224
|
-
|
|
225
|
-
# Get tracker class
|
|
226
|
-
tracker_class = cls._get_tracker_class(backend)
|
|
227
|
-
|
|
228
|
-
# Create tracker with appropriate arguments
|
|
229
|
-
return cls._create_tracker(tracker_class, backend, mode, kwargs)
|
|
230
|
-
|
|
231
|
-
@classmethod
|
|
232
|
-
def _detect_best_backend(cls) -> str:
|
|
233
|
-
"""Detect the best available backend.
|
|
234
|
-
|
|
235
|
-
Priority order:
|
|
236
|
-
1. CUDA (NVIDIA GPU) - fastest
|
|
237
|
-
2. CoreML (Apple Silicon) - good performance
|
|
238
|
-
3. RTMPose CPU - optimized CPU
|
|
239
|
-
4. MediaPipe - baseline fallback
|
|
240
|
-
|
|
241
|
-
Returns:
|
|
242
|
-
Backend name string
|
|
243
|
-
"""
|
|
244
|
-
# Check for CUDA (NVIDIA GPU)
|
|
245
|
-
try:
|
|
246
|
-
import torch
|
|
247
|
-
|
|
248
|
-
if torch.cuda.is_available():
|
|
249
|
-
return "rtmpose-cuda"
|
|
250
|
-
except ImportError:
|
|
251
|
-
pass
|
|
252
|
-
|
|
253
|
-
# Check for CoreML (Apple Silicon)
|
|
254
|
-
import sys
|
|
255
|
-
|
|
256
|
-
if sys.platform == "darwin":
|
|
257
|
-
return "rtmpose-coreml"
|
|
258
|
-
|
|
259
|
-
# Check for RTMPose CPU
|
|
260
|
-
try:
|
|
261
|
-
from kinemotion.core.rtmpose_cpu import (
|
|
262
|
-
OptimizedCPUTracker as _RTMPoseCPU, # type: ignore
|
|
263
|
-
)
|
|
264
|
-
|
|
265
|
-
_ = _RTMPoseCPU # Mark as intentionally used for availability check
|
|
266
|
-
|
|
267
|
-
return "rtmpose-cpu"
|
|
268
|
-
except ImportError:
|
|
269
|
-
pass
|
|
270
|
-
|
|
271
|
-
# Fallback to MediaPipe
|
|
272
|
-
return "mediapipe"
|
|
273
|
-
|
|
274
|
-
@classmethod
|
|
275
|
-
def _check_backend_available(cls, backend: str) -> str:
|
|
276
|
-
"""Check if a backend is available and return a fallback if not.
|
|
175
|
+
backend: str = "mediapipe",
|
|
176
|
+
**kwargs: Any,
|
|
177
|
+
) -> MediaPipePoseTracker:
|
|
178
|
+
"""Create a MediaPipe pose tracker.
|
|
277
179
|
|
|
278
180
|
Args:
|
|
279
|
-
backend:
|
|
181
|
+
backend: Backend selection (only 'mediapipe' supported)
|
|
182
|
+
**kwargs: Arguments passed to MediaPipePoseTracker
|
|
280
183
|
|
|
281
184
|
Returns:
|
|
282
|
-
|
|
185
|
+
Configured MediaPipePoseTracker instance
|
|
283
186
|
|
|
284
187
|
Raises:
|
|
285
|
-
ValueError: If
|
|
286
|
-
"""
|
|
287
|
-
normalized = cls._normalize_backend_name(backend)
|
|
288
|
-
|
|
289
|
-
# Check if specific backend can be imported
|
|
290
|
-
if normalized == "rtmpose-cuda":
|
|
291
|
-
try:
|
|
292
|
-
import torch # noqa: F401
|
|
293
|
-
|
|
294
|
-
if not torch.cuda.is_available():
|
|
295
|
-
# CUDA not available, fall back to CPU
|
|
296
|
-
return cls._check_backend_available("rtmpose-cpu")
|
|
297
|
-
# CUDA is available, use rtmpose-cuda
|
|
298
|
-
return normalized
|
|
299
|
-
except ImportError:
|
|
300
|
-
return cls._check_backend_available("rtmpose-cpu")
|
|
301
|
-
|
|
302
|
-
if normalized == "rtmpose-coreml":
|
|
303
|
-
import sys
|
|
304
|
-
|
|
305
|
-
if sys.platform != "darwin":
|
|
306
|
-
# Not macOS, fall back to CPU
|
|
307
|
-
return cls._check_backend_available("rtmpose-cpu")
|
|
308
|
-
|
|
309
|
-
# On macOS, verify CoreML wrapper is available
|
|
310
|
-
try:
|
|
311
|
-
from kinemotion.core.rtmpose_wrapper import RTMPoseWrapper
|
|
312
|
-
|
|
313
|
-
_ = RTMPoseWrapper # Mark as intentionally used
|
|
314
|
-
return normalized
|
|
315
|
-
except ImportError:
|
|
316
|
-
# CoreML wrapper not available, fall back to CPU
|
|
317
|
-
return cls._check_backend_available("rtmpose-cpu")
|
|
318
|
-
|
|
319
|
-
if normalized == "rtmpose-cpu":
|
|
320
|
-
try:
|
|
321
|
-
from kinemotion.core.rtmpose_cpu import (
|
|
322
|
-
OptimizedCPUTracker as _RTMPoseCPU,
|
|
323
|
-
) # type: ignore
|
|
324
|
-
|
|
325
|
-
_ = _RTMPoseCPU # Mark as intentionally used for availability check
|
|
326
|
-
|
|
327
|
-
return normalized
|
|
328
|
-
except ImportError:
|
|
329
|
-
# RTMPose not available, fall back to MediaPipe
|
|
330
|
-
return "mediapipe"
|
|
331
|
-
|
|
332
|
-
if normalized == "mediapipe":
|
|
333
|
-
try:
|
|
334
|
-
import mediapipe as _mp # noqa: F401
|
|
335
|
-
|
|
336
|
-
_ = _mp # Mark as intentionally used for availability check
|
|
337
|
-
return normalized
|
|
338
|
-
except ImportError as err:
|
|
339
|
-
raise ValueError(
|
|
340
|
-
"No pose tracking backend available. Please install mediapipe or rtmlib."
|
|
341
|
-
) from err
|
|
342
|
-
|
|
343
|
-
raise ValueError(f"Unknown backend: {backend}")
|
|
344
|
-
|
|
345
|
-
@classmethod
|
|
346
|
-
def _normalize_backend_name(cls, backend: str) -> str:
|
|
347
|
-
"""Normalize backend name to canonical form.
|
|
348
|
-
|
|
349
|
-
Args:
|
|
350
|
-
backend: User-provided backend name
|
|
351
|
-
|
|
352
|
-
Returns:
|
|
353
|
-
Canonical backend name
|
|
354
|
-
"""
|
|
355
|
-
# Normalize various aliases to canonical names
|
|
356
|
-
aliases = {
|
|
357
|
-
"mp": "mediapipe",
|
|
358
|
-
"mediapipe": "mediapipe",
|
|
359
|
-
"rtmpose": "rtmpose-cpu",
|
|
360
|
-
"rtmpose-cpu": "rtmpose-cpu",
|
|
361
|
-
"rtmpose_cpu": "rtmpose-cpu",
|
|
362
|
-
"cpu": "rtmpose-cpu",
|
|
363
|
-
"cuda": "rtmpose-cuda",
|
|
364
|
-
"rtmpose-cuda": "rtmpose-cuda",
|
|
365
|
-
"rtmpose_cuda": "rtmpose-cuda",
|
|
366
|
-
"gpu": "rtmpose-cuda",
|
|
367
|
-
"mps": "rtmpose-coreml",
|
|
368
|
-
"coreml": "rtmpose-coreml",
|
|
369
|
-
"rtmpose-coreml": "rtmpose-coreml",
|
|
370
|
-
"rtmpose_coreml": "rtmpose-coreml",
|
|
371
|
-
}
|
|
372
|
-
return aliases.get(backend.lower(), backend)
|
|
373
|
-
|
|
374
|
-
@classmethod
|
|
375
|
-
def _get_tracker_class(cls, backend: str):
|
|
376
|
-
"""Get the tracker class for a backend.
|
|
377
|
-
|
|
378
|
-
Args:
|
|
379
|
-
backend: Canonical backend name
|
|
380
|
-
|
|
381
|
-
Returns:
|
|
382
|
-
Tracker class
|
|
383
|
-
|
|
384
|
-
Raises:
|
|
385
|
-
ValueError: If backend is not recognized
|
|
386
|
-
"""
|
|
387
|
-
if backend == "mediapipe":
|
|
388
|
-
return MediaPipePoseTracker
|
|
389
|
-
|
|
390
|
-
if backend == "rtmpose-cpu":
|
|
391
|
-
try:
|
|
392
|
-
from kinemotion.core.rtmpose_cpu import OptimizedCPUTracker
|
|
393
|
-
|
|
394
|
-
return OptimizedCPUTracker
|
|
395
|
-
except ImportError as e:
|
|
396
|
-
raise ValueError(f"RTMPose CPU backend requested but not available: {e}") from e
|
|
397
|
-
|
|
398
|
-
if backend in ("rtmpose-cuda", "rtmpose-coreml"):
|
|
399
|
-
try:
|
|
400
|
-
from kinemotion.core.rtmpose_wrapper import RTMPoseWrapper
|
|
401
|
-
|
|
402
|
-
return RTMPoseWrapper
|
|
403
|
-
except ImportError as e:
|
|
404
|
-
raise ValueError(
|
|
405
|
-
f"RTMPose wrapper backend requested but not available: {e}"
|
|
406
|
-
) from e
|
|
407
|
-
|
|
408
|
-
raise ValueError(f"Unknown backend: {backend}")
|
|
409
|
-
|
|
410
|
-
@classmethod
|
|
411
|
-
def _create_tracker(
|
|
412
|
-
cls,
|
|
413
|
-
tracker_class: type,
|
|
414
|
-
backend: str,
|
|
415
|
-
mode: str,
|
|
416
|
-
kwargs: dict[str, object],
|
|
417
|
-
) -> object:
|
|
418
|
-
"""Create a tracker instance with appropriate arguments.
|
|
419
|
-
|
|
420
|
-
Args:
|
|
421
|
-
tracker_class: Tracker class to instantiate
|
|
422
|
-
backend: Backend name (for parameter mapping)
|
|
423
|
-
mode: RTMPose mode (only used for RTMPose backends)
|
|
424
|
-
kwargs: Additional arguments from user
|
|
425
|
-
|
|
426
|
-
Returns:
|
|
427
|
-
Tracker instance
|
|
428
|
-
"""
|
|
429
|
-
# MediaPipe-specific arguments
|
|
430
|
-
if backend == "mediapipe":
|
|
431
|
-
# Remove RTMPose-specific arguments
|
|
432
|
-
rttmpose_keys = {"mode", "backend", "device", "pose_input_size"}
|
|
433
|
-
filtered_kwargs = {k: v for k, v in kwargs.items() if k not in rttmpose_keys}
|
|
434
|
-
return tracker_class(**filtered_kwargs)
|
|
435
|
-
|
|
436
|
-
# OptimizedCPUTracker (CPU-only, doesn't accept device parameter)
|
|
437
|
-
if backend == "rtmpose-cpu":
|
|
438
|
-
# Remove RTMPoseWrapper-specific and MediaPipe-specific arguments
|
|
439
|
-
unsupported_keys = {
|
|
440
|
-
"backend",
|
|
441
|
-
"device",
|
|
442
|
-
"min_detection_confidence",
|
|
443
|
-
"min_tracking_confidence",
|
|
444
|
-
}
|
|
445
|
-
filtered_kwargs = {k: v for k, v in kwargs.items() if k not in unsupported_keys}
|
|
446
|
-
filtered_kwargs.setdefault("mode", mode)
|
|
447
|
-
return tracker_class(**filtered_kwargs)
|
|
448
|
-
|
|
449
|
-
# RTMPoseWrapper (CUDA/CoreML, requires device parameter)
|
|
450
|
-
# Remove MediaPipe-specific arguments
|
|
451
|
-
mediapipe_keys = {"min_detection_confidence", "min_tracking_confidence"}
|
|
452
|
-
filtered_kwargs = {k: v for k, v in kwargs.items() if k not in mediapipe_keys}
|
|
453
|
-
|
|
454
|
-
device = backend.split("-")[-1] # Extract 'cuda', 'cpu', 'coreml'
|
|
455
|
-
if device == "coreml":
|
|
456
|
-
device = "mps" # RTMLib uses 'mps' for Apple Silicon
|
|
457
|
-
|
|
458
|
-
filtered_kwargs.setdefault("device", device)
|
|
459
|
-
filtered_kwargs.setdefault("mode", mode)
|
|
460
|
-
|
|
461
|
-
return tracker_class(**filtered_kwargs)
|
|
462
|
-
|
|
463
|
-
@classmethod
|
|
464
|
-
def get_available_backends(cls) -> list[str]:
|
|
465
|
-
"""Get list of available backends on current system.
|
|
466
|
-
|
|
467
|
-
Returns:
|
|
468
|
-
List of available backend names
|
|
188
|
+
ValueError: If backend is not 'mediapipe'
|
|
469
189
|
"""
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
import mediapipe as _mp # noqa: F401
|
|
475
|
-
|
|
476
|
-
_ = _mp # Mark as intentionally used for availability check
|
|
477
|
-
available.append("mediapipe")
|
|
478
|
-
except ImportError:
|
|
479
|
-
pass
|
|
480
|
-
|
|
481
|
-
# Check RTMPose CPU
|
|
482
|
-
try:
|
|
483
|
-
from kinemotion.core.rtmpose_cpu import (
|
|
484
|
-
OptimizedCPUTracker as _RTMPoseCPU,
|
|
485
|
-
) # type: ignore
|
|
486
|
-
|
|
487
|
-
_ = _RTMPoseCPU # Mark as intentionally used for availability check
|
|
190
|
+
# Normalize and validate backend
|
|
191
|
+
normalized = backend.lower()
|
|
192
|
+
if normalized not in ("mediapipe", "mp", "auto"):
|
|
193
|
+
raise ValueError(f"Unknown backend: {backend}. Only 'mediapipe' is supported.")
|
|
488
194
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
195
|
+
# Filter out any legacy kwargs that don't apply to MediaPipe
|
|
196
|
+
legacy_keys = {"mode", "backend", "device", "pose_input_size"}
|
|
197
|
+
filtered_kwargs = {k: v for k, v in kwargs.items() if k not in legacy_keys}
|
|
492
198
|
|
|
493
|
-
|
|
494
|
-
try:
|
|
495
|
-
import torch
|
|
496
|
-
|
|
497
|
-
if torch.cuda.is_available():
|
|
498
|
-
from kinemotion.core.rtmpose_wrapper import (
|
|
499
|
-
RTMPoseWrapper as _RTMPoseWrapper,
|
|
500
|
-
) # type: ignore
|
|
501
|
-
|
|
502
|
-
_ = _RTMPoseWrapper # Mark as intentionally used for availability check
|
|
503
|
-
|
|
504
|
-
available.append("rtmpose-cuda")
|
|
505
|
-
except ImportError:
|
|
506
|
-
pass
|
|
507
|
-
|
|
508
|
-
# Check CoreML (Apple Silicon)
|
|
509
|
-
import sys
|
|
510
|
-
|
|
511
|
-
if sys.platform == "darwin":
|
|
512
|
-
try:
|
|
513
|
-
from kinemotion.core.rtmpose_wrapper import (
|
|
514
|
-
RTMPoseWrapper as _RTMPoseWrapperMPS,
|
|
515
|
-
) # type: ignore
|
|
516
|
-
|
|
517
|
-
_ = _RTMPoseWrapperMPS # Mark as intentionally used for availability check
|
|
518
|
-
|
|
519
|
-
available.append("rtmpose-coreml")
|
|
520
|
-
except ImportError:
|
|
521
|
-
pass
|
|
522
|
-
|
|
523
|
-
return available
|
|
524
|
-
|
|
525
|
-
@classmethod
|
|
526
|
-
def get_backend_info(cls, backend: str) -> dict[str, str]:
|
|
527
|
-
"""Get information about a backend.
|
|
528
|
-
|
|
529
|
-
Args:
|
|
530
|
-
backend: Backend name
|
|
531
|
-
|
|
532
|
-
Returns:
|
|
533
|
-
Dictionary with backend information
|
|
534
|
-
"""
|
|
535
|
-
info = {
|
|
536
|
-
"mediapipe": {
|
|
537
|
-
"name": "MediaPipe",
|
|
538
|
-
"description": "Baseline pose tracking using MediaPipe Tasks API",
|
|
539
|
-
"performance": "~48 FPS",
|
|
540
|
-
"accuracy": "Baseline (reference)",
|
|
541
|
-
"requirements": "mediapipe package",
|
|
542
|
-
},
|
|
543
|
-
"rtmpose-cpu": {
|
|
544
|
-
"name": "RTMPose CPU",
|
|
545
|
-
"description": "Optimized CPU implementation with ONNX Runtime",
|
|
546
|
-
"performance": "~40-68 FPS (134% of MediaPipe)",
|
|
547
|
-
"accuracy": "9-12px mean difference (1-5% metric accuracy)",
|
|
548
|
-
"requirements": "rtmlib package",
|
|
549
|
-
},
|
|
550
|
-
"rtmpose-cuda": {
|
|
551
|
-
"name": "RTMPose CUDA",
|
|
552
|
-
"description": "NVIDIA GPU acceleration with CUDA",
|
|
553
|
-
"performance": "~133 FPS (271% of MediaPipe)",
|
|
554
|
-
"accuracy": "9-12px mean difference (1-5% metric accuracy)",
|
|
555
|
-
"requirements": "rtmlib + CUDA-capable GPU",
|
|
556
|
-
},
|
|
557
|
-
"rtmpose-coreml": {
|
|
558
|
-
"name": "RTMPose CoreML",
|
|
559
|
-
"description": "Apple Silicon acceleration with CoreML",
|
|
560
|
-
"performance": "~42 FPS (94% of MediaPipe)",
|
|
561
|
-
"accuracy": "9-12px mean difference (1-5% metric accuracy)",
|
|
562
|
-
"requirements": "rtmlib + Apple Silicon",
|
|
563
|
-
},
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
normalized = cls._normalize_backend_name(backend)
|
|
567
|
-
return info.get(normalized, {})
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
def get_tracker_info(tracker: object) -> str:
|
|
571
|
-
"""Get detailed information about a pose tracker instance.
|
|
572
|
-
|
|
573
|
-
Args:
|
|
574
|
-
tracker: Pose tracker instance
|
|
575
|
-
|
|
576
|
-
Returns:
|
|
577
|
-
Formatted string with tracker details
|
|
578
|
-
"""
|
|
579
|
-
tracker_class = type(tracker).__name__
|
|
580
|
-
module = type(tracker).__module__
|
|
581
|
-
|
|
582
|
-
info = f"{tracker_class} (from {module})"
|
|
583
|
-
|
|
584
|
-
# Add backend-specific details
|
|
585
|
-
if tracker_class == "MediaPipePoseTracker":
|
|
586
|
-
info += " [MediaPipe Tasks API]"
|
|
587
|
-
elif tracker_class == "OptimizedCPUTracker":
|
|
588
|
-
# Check if ONNX Runtime has CUDA
|
|
589
|
-
try:
|
|
590
|
-
import onnxruntime as ort
|
|
591
|
-
|
|
592
|
-
providers = ort.get_available_providers()
|
|
593
|
-
if "CUDAExecutionProvider" in providers:
|
|
594
|
-
# Check what providers the session is actually using
|
|
595
|
-
det_session = getattr(tracker, "det_session", None)
|
|
596
|
-
if det_session is not None:
|
|
597
|
-
active_providers = det_session.get_providers()
|
|
598
|
-
if "CUDAExecutionProvider" in active_providers:
|
|
599
|
-
info += " [ONNX Runtime: CUDA]"
|
|
600
|
-
else:
|
|
601
|
-
info += " [ONNX Runtime: CPU]"
|
|
602
|
-
else:
|
|
603
|
-
info += " [ONNX Runtime]"
|
|
604
|
-
else:
|
|
605
|
-
info += " [ONNX Runtime: CPU]"
|
|
606
|
-
except ImportError:
|
|
607
|
-
info += " [ONNX Runtime]"
|
|
608
|
-
elif tracker_class == "RTMPoseWrapper":
|
|
609
|
-
device = getattr(tracker, "device", None)
|
|
610
|
-
if device:
|
|
611
|
-
if device == "cuda":
|
|
612
|
-
try:
|
|
613
|
-
import torch
|
|
614
|
-
|
|
615
|
-
if torch.cuda.is_available():
|
|
616
|
-
device_name = torch.cuda.get_device_name(0)
|
|
617
|
-
info += f" [PyTorch CUDA: {device_name}]"
|
|
618
|
-
else:
|
|
619
|
-
info += " [PyTorch: CPU fallback]"
|
|
620
|
-
except ImportError:
|
|
621
|
-
info += " [PyTorch CUDA]"
|
|
622
|
-
elif device == "mps":
|
|
623
|
-
info += " [PyTorch: Apple Silicon GPU]"
|
|
624
|
-
else:
|
|
625
|
-
info += f" [PyTorch: {device}]"
|
|
626
|
-
else:
|
|
627
|
-
info += " [PyTorch]"
|
|
628
|
-
|
|
629
|
-
return info
|
|
199
|
+
return MediaPipePoseTracker(**filtered_kwargs)
|
|
630
200
|
|
|
631
201
|
|
|
632
202
|
def _extract_landmarks_from_results(
|
|
@@ -654,28 +224,6 @@ def _extract_landmarks_from_results(
|
|
|
654
224
|
return landmarks
|
|
655
225
|
|
|
656
226
|
|
|
657
|
-
# Legacy compatibility aliases for Solution API enum values
|
|
658
|
-
class _LegacyPoseLandmark:
|
|
659
|
-
"""Compatibility shim for Solution API enum values."""
|
|
660
|
-
|
|
661
|
-
LEFT_ANKLE = 27
|
|
662
|
-
RIGHT_ANKLE = 28
|
|
663
|
-
LEFT_HEEL = 29
|
|
664
|
-
RIGHT_HEEL = 30
|
|
665
|
-
LEFT_FOOT_INDEX = 31
|
|
666
|
-
RIGHT_FOOT_INDEX = 32
|
|
667
|
-
LEFT_HIP = 23
|
|
668
|
-
RIGHT_HIP = 24
|
|
669
|
-
LEFT_SHOULDER = 11
|
|
670
|
-
RIGHT_SHOULDER = 12
|
|
671
|
-
NOSE = 0
|
|
672
|
-
LEFT_KNEE = 25
|
|
673
|
-
RIGHT_KNEE = 26
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
PoseLandmark = _LegacyPoseLandmark
|
|
677
|
-
|
|
678
|
-
|
|
679
227
|
def compute_center_of_mass(
|
|
680
228
|
landmarks: dict[str, tuple[float, float, float]],
|
|
681
229
|
visibility_threshold: float = 0.5,
|
|
@@ -754,6 +302,37 @@ def compute_center_of_mass(
|
|
|
754
302
|
return (com_x, com_y, com_visibility)
|
|
755
303
|
|
|
756
304
|
|
|
305
|
+
def _compute_mean_landmark_position(
|
|
306
|
+
landmark_keys: list[str],
|
|
307
|
+
landmarks: dict[str, tuple[float, float, float]],
|
|
308
|
+
vis_threshold: float,
|
|
309
|
+
) -> tuple[float, float, float] | None:
|
|
310
|
+
"""Compute mean position and visibility from multiple landmarks.
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
landmark_keys: List of landmark key names to average
|
|
314
|
+
landmarks: Dictionary of landmark positions
|
|
315
|
+
vis_threshold: Minimum visibility threshold
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
(x, y, visibility) tuple if any landmarks are visible, else None
|
|
319
|
+
"""
|
|
320
|
+
positions = [
|
|
321
|
+
(x, y, vis)
|
|
322
|
+
for key in landmark_keys
|
|
323
|
+
if key in landmarks
|
|
324
|
+
for x, y, vis in [landmarks[key]]
|
|
325
|
+
if vis > vis_threshold
|
|
326
|
+
]
|
|
327
|
+
if not positions:
|
|
328
|
+
return None
|
|
329
|
+
|
|
330
|
+
x = float(np.mean([p[0] for p in positions]))
|
|
331
|
+
y = float(np.mean([p[1] for p in positions]))
|
|
332
|
+
vis = float(np.mean([p[2] for p in positions]))
|
|
333
|
+
return (x, y, vis)
|
|
334
|
+
|
|
335
|
+
|
|
757
336
|
def _add_head_segment(
|
|
758
337
|
segments: list,
|
|
759
338
|
weights: list,
|
|
@@ -779,20 +358,17 @@ def _add_trunk_segment(
|
|
|
779
358
|
) -> None:
|
|
780
359
|
"""Add trunk segment (50% body mass) if visible."""
|
|
781
360
|
trunk_keys = ["left_shoulder", "right_shoulder", "left_hip", "right_hip"]
|
|
782
|
-
trunk_pos =
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
segments.append((trunk_x, trunk_y))
|
|
794
|
-
weights.append(0.50)
|
|
795
|
-
visibilities.append(trunk_vis)
|
|
361
|
+
trunk_pos = _compute_mean_landmark_position(trunk_keys, landmarks, vis_threshold)
|
|
362
|
+
|
|
363
|
+
if trunk_pos is not None:
|
|
364
|
+
# Require at least 2 visible landmarks for valid trunk
|
|
365
|
+
visible_count = sum(
|
|
366
|
+
1 for key in trunk_keys if key in landmarks and landmarks[key][2] > vis_threshold
|
|
367
|
+
)
|
|
368
|
+
if visible_count >= 2:
|
|
369
|
+
segments.append((trunk_pos[0], trunk_pos[1]))
|
|
370
|
+
weights.append(0.50)
|
|
371
|
+
visibilities.append(trunk_pos[2])
|
|
796
372
|
|
|
797
373
|
|
|
798
374
|
def _add_limb_segment(
|
|
@@ -832,17 +408,9 @@ def _add_foot_segment(
|
|
|
832
408
|
) -> None:
|
|
833
409
|
"""Add foot segment (1.5% body mass per foot) if visible."""
|
|
834
410
|
foot_keys = [f"{side}_ankle", f"{side}_heel", f"{side}_foot_index"]
|
|
835
|
-
foot_pos =
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
for x, y, vis in [landmarks[key]]
|
|
840
|
-
if vis > vis_threshold
|
|
841
|
-
]
|
|
842
|
-
if foot_pos:
|
|
843
|
-
foot_x = float(np.mean([p[0] for p in foot_pos]))
|
|
844
|
-
foot_y = float(np.mean([p[1] for p in foot_pos]))
|
|
845
|
-
foot_vis = float(np.mean([p[2] for p in foot_pos]))
|
|
846
|
-
segments.append((foot_x, foot_y))
|
|
411
|
+
foot_pos = _compute_mean_landmark_position(foot_keys, landmarks, vis_threshold)
|
|
412
|
+
|
|
413
|
+
if foot_pos is not None:
|
|
414
|
+
segments.append((foot_pos[0], foot_pos[1]))
|
|
847
415
|
weights.append(0.015)
|
|
848
|
-
visibilities.append(
|
|
416
|
+
visibilities.append(foot_pos[2])
|