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 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
- cli.add_command(dropjump_analyze)
18
- cli.add_command(cmj_analyze)
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 PoseTracker
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.time() - start_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.time() - start_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: PoseTracker | None,
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 verbose:
238
- print("Processing all frames with MediaPipe pose tracking...")
238
+ if pose_tracker is None:
239
+ if pose_backend is not None:
240
+ import time
239
241
 
240
- tracker = pose_tracker or PoseTracker(
241
- min_detection_confidence=det_conf,
242
- min_tracking_confidence=track_conf,
243
- timer=timer,
244
- )
245
- should_close_tracker = pose_tracker is None
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: PoseTracker | None = None,
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.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.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.time() - start_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.time() - start_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
 
@@ -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 PoseTracker, compute_center_of_mass
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
- "PoseTracker",
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.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.time() - reencode_start
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 PoseTracker
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: PoseTracker,
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: PoseTracker,
183
+ tracker: MediaPipePoseTracker,
184
184
  verbose: bool,
185
185
  timer: Timer | None = None,
186
186
  close_tracker: bool = True,