kinemotion 0.67.0__py3-none-any.whl → 0.68.0__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.
Potentially problematic release.
This version of kinemotion might be problematic. Click here for more details.
- kinemotion/cli.py +4 -3
- kinemotion/cmj/api.py +45 -17
- kinemotion/cmj/cli.py +21 -0
- kinemotion/core/__init__.py +9 -2
- kinemotion/core/debug_overlay_utils.py +2 -2
- kinemotion/core/pipeline_utils.py +3 -3
- kinemotion/core/pose.py +462 -1
- kinemotion/core/rtmpose_cpu.py +626 -0
- kinemotion/core/rtmpose_wrapper.py +190 -0
- kinemotion/core/timing.py +4 -2
- kinemotion/dropjump/api.py +40 -16
- kinemotion/dropjump/cli.py +27 -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.67.0.dist-info → kinemotion-0.68.0.dist-info}/METADATA +5 -1
- {kinemotion-0.67.0.dist-info → kinemotion-0.68.0.dist-info}/RECORD +19 -15
- {kinemotion-0.67.0.dist-info → kinemotion-0.68.0.dist-info}/WHEEL +0 -0
- {kinemotion-0.67.0.dist-info → kinemotion-0.68.0.dist-info}/entry_points.txt +0 -0
- {kinemotion-0.67.0.dist-info → kinemotion-0.68.0.dist-info}/licenses/LICENSE +0 -0
kinemotion/cli.py
CHANGED
|
@@ -8,14 +8,15 @@ from .dropjump.cli import dropjump_analyze
|
|
|
8
8
|
|
|
9
9
|
@click.group()
|
|
10
10
|
@click.version_option(package_name="dropjump-analyze")
|
|
11
|
-
def cli() -> None:
|
|
11
|
+
def cli() -> None: # type: ignore[return]
|
|
12
12
|
"""Kinemotion: Video-based kinematic analysis for athletic performance."""
|
|
13
13
|
pass
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
# Register commands from submodules
|
|
17
|
-
|
|
18
|
-
cli.add_command(
|
|
17
|
+
# Type ignore needed because @click.group() transforms cli into a click.Group
|
|
18
|
+
cli.add_command(dropjump_analyze) # type: ignore[attr-defined]
|
|
19
|
+
cli.add_command(cmj_analyze) # type: ignore[attr-defined]
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
if __name__ == "__main__":
|
kinemotion/cmj/api.py
CHANGED
|
@@ -37,7 +37,7 @@ from ..core.pipeline_utils import (
|
|
|
37
37
|
process_all_frames,
|
|
38
38
|
process_videos_bulk_generic,
|
|
39
39
|
)
|
|
40
|
-
from ..core.pose import
|
|
40
|
+
from ..core.pose import MediaPipePoseTracker
|
|
41
41
|
from ..core.quality import QualityAssessment, assess_jump_quality
|
|
42
42
|
from ..core.timing import PerformanceTimer, Timer
|
|
43
43
|
from ..core.validation import ValidationResult
|
|
@@ -114,7 +114,7 @@ def _save_metrics_to_json(
|
|
|
114
114
|
|
|
115
115
|
def _print_timing_summary(start_time: float, timer: Timer, metrics: CMJMetrics) -> None:
|
|
116
116
|
"""Print verbose timing summary and metrics."""
|
|
117
|
-
total_time = time.
|
|
117
|
+
total_time = time.perf_counter() - start_time
|
|
118
118
|
stage_times = convert_timer_to_stage_names(timer.get_metrics())
|
|
119
119
|
|
|
120
120
|
print("\n=== Timing Summary ===")
|
|
@@ -187,7 +187,7 @@ def _create_processing_info(
|
|
|
187
187
|
start_time: float, quality_preset: QualityPreset, timer: Timer
|
|
188
188
|
) -> ProcessingInfo:
|
|
189
189
|
"""Create processing information metadata."""
|
|
190
|
-
processing_time = time.
|
|
190
|
+
processing_time = time.perf_counter() - start_time
|
|
191
191
|
stage_times = convert_timer_to_stage_names(timer.get_metrics())
|
|
192
192
|
|
|
193
193
|
return ProcessingInfo(
|
|
@@ -219,7 +219,8 @@ def _run_pose_tracking(
|
|
|
219
219
|
quality_preset: QualityPreset,
|
|
220
220
|
detection_confidence: float | None,
|
|
221
221
|
tracking_confidence: float | None,
|
|
222
|
-
pose_tracker:
|
|
222
|
+
pose_tracker: "MediaPipePoseTracker | None",
|
|
223
|
+
pose_backend: str | None,
|
|
223
224
|
verbose: bool,
|
|
224
225
|
timer: Timer,
|
|
225
226
|
) -> tuple[list[NDArray[np.uint8]], list, list[int]]:
|
|
@@ -234,15 +235,38 @@ def _run_pose_tracking(
|
|
|
234
235
|
quality_preset, detection_confidence, tracking_confidence
|
|
235
236
|
)
|
|
236
237
|
|
|
237
|
-
if
|
|
238
|
-
|
|
238
|
+
if pose_tracker is None:
|
|
239
|
+
if pose_backend is not None:
|
|
240
|
+
import time
|
|
239
241
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
242
|
+
from ..core import get_tracker_info
|
|
243
|
+
from ..core.pose import PoseTrackerFactory
|
|
244
|
+
|
|
245
|
+
init_start = time.perf_counter()
|
|
246
|
+
tracker = PoseTrackerFactory.create(
|
|
247
|
+
backend=pose_backend,
|
|
248
|
+
min_detection_confidence=det_conf,
|
|
249
|
+
min_tracking_confidence=track_conf,
|
|
250
|
+
timer=timer,
|
|
251
|
+
)
|
|
252
|
+
init_time = time.perf_counter() - init_start
|
|
253
|
+
|
|
254
|
+
if verbose:
|
|
255
|
+
print(f"Using pose backend: {pose_backend}")
|
|
256
|
+
print(f" → {get_tracker_info(tracker)}")
|
|
257
|
+
print(f" → Initialized in {init_time * 1000:.1f} ms")
|
|
258
|
+
else:
|
|
259
|
+
if verbose:
|
|
260
|
+
print("Processing all frames with MediaPipe pose tracking...")
|
|
261
|
+
tracker = MediaPipePoseTracker(
|
|
262
|
+
min_detection_confidence=det_conf,
|
|
263
|
+
min_tracking_confidence=track_conf,
|
|
264
|
+
timer=timer,
|
|
265
|
+
)
|
|
266
|
+
should_close_tracker = True
|
|
267
|
+
else:
|
|
268
|
+
tracker = pose_tracker
|
|
269
|
+
should_close_tracker = False
|
|
246
270
|
|
|
247
271
|
return process_all_frames(video, tracker, verbose, timer, close_tracker=should_close_tracker)
|
|
248
272
|
|
|
@@ -388,6 +412,7 @@ class CMJVideoConfig:
|
|
|
388
412
|
overrides: AnalysisOverrides | None = None
|
|
389
413
|
detection_confidence: float | None = None
|
|
390
414
|
tracking_confidence: float | None = None
|
|
415
|
+
pose_backend: str | None = None
|
|
391
416
|
|
|
392
417
|
|
|
393
418
|
@dataclass
|
|
@@ -409,9 +434,10 @@ def process_cmj_video(
|
|
|
409
434
|
overrides: AnalysisOverrides | None = None,
|
|
410
435
|
detection_confidence: float | None = None,
|
|
411
436
|
tracking_confidence: float | None = None,
|
|
437
|
+
pose_backend: str | None = None,
|
|
412
438
|
verbose: bool = False,
|
|
413
439
|
timer: Timer | None = None,
|
|
414
|
-
pose_tracker:
|
|
440
|
+
pose_tracker: MediaPipePoseTracker | None = None,
|
|
415
441
|
) -> CMJMetrics:
|
|
416
442
|
"""
|
|
417
443
|
Process a single CMJ video and return metrics.
|
|
@@ -442,7 +468,7 @@ def process_cmj_video(
|
|
|
442
468
|
if not Path(video_path).exists():
|
|
443
469
|
raise FileNotFoundError(f"Video file not found: {video_path}")
|
|
444
470
|
|
|
445
|
-
start_time = time.
|
|
471
|
+
start_time = time.perf_counter()
|
|
446
472
|
timer = timer or PerformanceTimer()
|
|
447
473
|
quality_preset = parse_quality_preset(quality)
|
|
448
474
|
|
|
@@ -455,6 +481,7 @@ def process_cmj_video(
|
|
|
455
481
|
detection_confidence,
|
|
456
482
|
tracking_confidence,
|
|
457
483
|
pose_tracker,
|
|
484
|
+
pose_backend,
|
|
458
485
|
verbose,
|
|
459
486
|
timer,
|
|
460
487
|
)
|
|
@@ -529,7 +556,7 @@ def process_cmj_videos_bulk(
|
|
|
529
556
|
|
|
530
557
|
def _process_cmj_video_wrapper(config: CMJVideoConfig) -> CMJVideoResult:
|
|
531
558
|
"""Wrapper function for parallel CMJ processing."""
|
|
532
|
-
start_time = time.
|
|
559
|
+
start_time = time.perf_counter()
|
|
533
560
|
|
|
534
561
|
try:
|
|
535
562
|
metrics = process_cmj_video(
|
|
@@ -540,10 +567,11 @@ def _process_cmj_video_wrapper(config: CMJVideoConfig) -> CMJVideoResult:
|
|
|
540
567
|
overrides=config.overrides,
|
|
541
568
|
detection_confidence=config.detection_confidence,
|
|
542
569
|
tracking_confidence=config.tracking_confidence,
|
|
570
|
+
pose_backend=config.pose_backend,
|
|
543
571
|
verbose=False,
|
|
544
572
|
)
|
|
545
573
|
|
|
546
|
-
processing_time = time.
|
|
574
|
+
processing_time = time.perf_counter() - start_time
|
|
547
575
|
|
|
548
576
|
return CMJVideoResult(
|
|
549
577
|
video_path=config.video_path,
|
|
@@ -553,7 +581,7 @@ def _process_cmj_video_wrapper(config: CMJVideoConfig) -> CMJVideoResult:
|
|
|
553
581
|
)
|
|
554
582
|
|
|
555
583
|
except Exception as e:
|
|
556
|
-
processing_time = time.
|
|
584
|
+
processing_time = time.perf_counter() - start_time
|
|
557
585
|
|
|
558
586
|
return CMJVideoResult(
|
|
559
587
|
video_path=config.video_path,
|
kinemotion/cmj/cli.py
CHANGED
|
@@ -27,6 +27,7 @@ class AnalysisParameters:
|
|
|
27
27
|
visibility_threshold: float | None = None
|
|
28
28
|
detection_confidence: float | None = None
|
|
29
29
|
tracking_confidence: float | None = None
|
|
30
|
+
pose_backend: str | None = None
|
|
30
31
|
|
|
31
32
|
|
|
32
33
|
def _process_batch_videos(
|
|
@@ -77,6 +78,23 @@ def _process_batch_videos(
|
|
|
77
78
|
is_flag=True,
|
|
78
79
|
help="Show auto-selected parameters and analysis details",
|
|
79
80
|
)
|
|
81
|
+
@click.option(
|
|
82
|
+
"--pose-backend",
|
|
83
|
+
type=click.Choice(
|
|
84
|
+
["auto", "mediapipe", "rtmpose-cpu", "rtmpose-cuda", "rtmpose-coreml"],
|
|
85
|
+
case_sensitive=False,
|
|
86
|
+
),
|
|
87
|
+
default="auto",
|
|
88
|
+
help=(
|
|
89
|
+
"Pose tracking backend: "
|
|
90
|
+
"auto (detect best), "
|
|
91
|
+
"mediapipe (baseline), "
|
|
92
|
+
"rtmpose-cpu (optimized CPU), "
|
|
93
|
+
"rtmpose-cuda (NVIDIA GPU), "
|
|
94
|
+
"rtmpose-coreml (Apple Silicon)"
|
|
95
|
+
),
|
|
96
|
+
show_default=True,
|
|
97
|
+
)
|
|
80
98
|
# Batch processing options
|
|
81
99
|
@click.option(
|
|
82
100
|
"--batch",
|
|
@@ -155,6 +173,7 @@ def cmj_analyze( # NOSONAR(S107) - Click CLI requires individual parameters
|
|
|
155
173
|
json_output: str | None,
|
|
156
174
|
quality: str,
|
|
157
175
|
verbose: bool,
|
|
176
|
+
pose_backend: str,
|
|
158
177
|
batch: bool,
|
|
159
178
|
workers: int,
|
|
160
179
|
output_dir: str | None,
|
|
@@ -218,6 +237,7 @@ def cmj_analyze( # NOSONAR(S107) - Click CLI requires individual parameters
|
|
|
218
237
|
visibility_threshold=visibility_threshold,
|
|
219
238
|
detection_confidence=detection_confidence,
|
|
220
239
|
tracking_confidence=tracking_confidence,
|
|
240
|
+
pose_backend=pose_backend,
|
|
221
241
|
)
|
|
222
242
|
|
|
223
243
|
if use_batch:
|
|
@@ -273,6 +293,7 @@ def _process_single(
|
|
|
273
293
|
overrides=overrides,
|
|
274
294
|
detection_confidence=expert_params.detection_confidence,
|
|
275
295
|
tracking_confidence=expert_params.tracking_confidence,
|
|
296
|
+
pose_backend=expert_params.pose_backend,
|
|
276
297
|
verbose=verbose,
|
|
277
298
|
)
|
|
278
299
|
|
kinemotion/core/__init__.py
CHANGED
|
@@ -9,7 +9,12 @@ from .filtering import (
|
|
|
9
9
|
remove_outliers,
|
|
10
10
|
)
|
|
11
11
|
from .model_downloader import get_model_cache_dir, get_model_path
|
|
12
|
-
from .pose import
|
|
12
|
+
from .pose import (
|
|
13
|
+
MediaPipePoseTracker,
|
|
14
|
+
PoseTrackerFactory,
|
|
15
|
+
compute_center_of_mass,
|
|
16
|
+
get_tracker_info,
|
|
17
|
+
)
|
|
13
18
|
from .pose_landmarks import KINEMOTION_LANDMARKS, LANDMARK_INDICES
|
|
14
19
|
from .quality import (
|
|
15
20
|
QualityAssessment,
|
|
@@ -34,8 +39,10 @@ from .video_io import VideoProcessor
|
|
|
34
39
|
|
|
35
40
|
__all__ = [
|
|
36
41
|
# Pose tracking
|
|
37
|
-
"
|
|
42
|
+
"MediaPipePoseTracker",
|
|
43
|
+
"PoseTrackerFactory",
|
|
38
44
|
"compute_center_of_mass",
|
|
45
|
+
"get_tracker_info",
|
|
39
46
|
"LANDMARK_INDICES",
|
|
40
47
|
"KINEMOTION_LANDMARKS",
|
|
41
48
|
"get_model_path",
|
|
@@ -340,9 +340,9 @@ class BaseDebugOverlayRenderer:
|
|
|
340
340
|
)
|
|
341
341
|
|
|
342
342
|
try:
|
|
343
|
-
reencode_start = time.
|
|
343
|
+
reencode_start = time.perf_counter()
|
|
344
344
|
subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
|
|
345
|
-
self.reencode_duration_s = time.
|
|
345
|
+
self.reencode_duration_s = time.perf_counter() - reencode_start
|
|
346
346
|
|
|
347
347
|
_log(
|
|
348
348
|
"info",
|
|
@@ -11,7 +11,7 @@ import numpy as np
|
|
|
11
11
|
from ..cmj.analysis import compute_average_hip_position
|
|
12
12
|
from ..dropjump.analysis import compute_average_foot_position
|
|
13
13
|
from .auto_tuning import AnalysisParameters, QualityPreset, VideoCharacteristics
|
|
14
|
-
from .pose import
|
|
14
|
+
from .pose import MediaPipePoseTracker
|
|
15
15
|
from .smoothing import smooth_landmarks, smooth_landmarks_advanced
|
|
16
16
|
from .timing import NULL_TIMER, Timer
|
|
17
17
|
from .video_io import VideoProcessor
|
|
@@ -142,7 +142,7 @@ def print_verbose_parameters(
|
|
|
142
142
|
|
|
143
143
|
def _process_frames_loop(
|
|
144
144
|
video: VideoProcessor,
|
|
145
|
-
tracker:
|
|
145
|
+
tracker: MediaPipePoseTracker,
|
|
146
146
|
step: int,
|
|
147
147
|
should_resize: bool,
|
|
148
148
|
debug_w: int,
|
|
@@ -180,7 +180,7 @@ def _process_frames_loop(
|
|
|
180
180
|
|
|
181
181
|
def process_all_frames(
|
|
182
182
|
video: VideoProcessor,
|
|
183
|
-
tracker:
|
|
183
|
+
tracker: MediaPipePoseTracker,
|
|
184
184
|
verbose: bool,
|
|
185
185
|
timer: Timer | None = None,
|
|
186
186
|
close_tracker: bool = True,
|