kinemotion 0.30.0__py3-none-any.whl → 0.31.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.

Potentially problematic release.


This version of kinemotion might be problematic. Click here for more details.

kinemotion/api.py CHANGED
@@ -62,7 +62,8 @@ def _parse_quality_preset(quality: str) -> QualityPreset:
62
62
  return QualityPreset(quality.lower())
63
63
  except ValueError as e:
64
64
  raise ValueError(
65
- f"Invalid quality preset: {quality}. Must be 'fast', 'balanced', or 'accurate'"
65
+ f"Invalid quality preset: {quality}. "
66
+ "Must be 'fast', 'balanced', or 'accurate'"
66
67
  ) from e
67
68
 
68
69
 
@@ -501,7 +502,8 @@ def process_dropjump_video(
501
502
  if verbose:
502
503
  print("Assessing tracking quality...")
503
504
 
504
- # Detect outliers for quality scoring (doesn't affect results, just for assessment)
505
+ # Detect outliers for quality scoring (doesn't affect results, just
506
+ # for assessment)
505
507
  _, outlier_mask = reject_outliers(
506
508
  vertical_positions,
507
509
  use_ransac=True,
@@ -626,9 +628,11 @@ def process_dropjump_videos_bulk(
626
628
  Process multiple drop jump videos in parallel using ProcessPoolExecutor.
627
629
 
628
630
  Args:
629
- configs: List of DropJumpVideoConfig objects specifying video paths and parameters
631
+ configs: List of DropJumpVideoConfig objects specifying video paths
632
+ and parameters
630
633
  max_workers: Maximum number of parallel workers (default: 4)
631
- progress_callback: Optional callback function called after each video completes.
634
+ progress_callback: Optional callback function called after each video
635
+ completes.
632
636
  Receives DropJumpVideoResult object.
633
637
 
634
638
  Returns:
@@ -953,7 +957,8 @@ def process_cmj_video(
953
957
  if verbose:
954
958
  print("Assessing tracking quality...")
955
959
 
956
- # Detect outliers for quality scoring (doesn't affect results, just for assessment)
960
+ # Detect outliers for quality scoring (doesn't affect results, just
961
+ # for assessment)
957
962
  _, outlier_mask = reject_outliers(
958
963
  vertical_positions,
959
964
  use_ransac=True,
@@ -1047,7 +1052,7 @@ def process_cmj_video(
1047
1052
 
1048
1053
  if verbose:
1049
1054
  print(f"\nJump height: {metrics.jump_height:.3f}m")
1050
- print(f"Flight time: {metrics.flight_time*1000:.1f}ms")
1055
+ print(f"Flight time: {metrics.flight_time * 1000:.1f}ms")
1051
1056
  print(f"Countermovement depth: {metrics.countermovement_depth:.3f}m")
1052
1057
 
1053
1058
  # Validate metrics against physiological bounds
@@ -1126,7 +1131,8 @@ def process_cmj_videos_bulk(
1126
1131
 
1127
1132
  def _process_cmj_video_wrapper(config: CMJVideoConfig) -> CMJVideoResult:
1128
1133
  """
1129
- Wrapper function for parallel CMJ processing. Must be picklable (top-level function).
1134
+ Wrapper function for parallel CMJ processing. Must be picklable
1135
+ (top-level function).
1130
1136
 
1131
1137
  Args:
1132
1138
  config: CMJVideoConfig object with processing parameters
@@ -62,7 +62,8 @@ def find_standing_phase(
62
62
  """
63
63
  Find the end of standing phase (start of countermovement).
64
64
 
65
- Looks for a period of low velocity (standing) followed by consistent downward motion.
65
+ Looks for a period of low velocity (standing) followed by consistent
66
+ downward motion.
66
67
 
67
68
  Args:
68
69
  positions: Array of vertical positions (normalized 0-1)
@@ -109,11 +110,13 @@ def find_countermovement_start(
109
110
  """
110
111
  Find the start of countermovement (eccentric phase).
111
112
 
112
- Detects when velocity becomes consistently positive (downward motion in normalized coords).
113
+ Detects when velocity becomes consistently positive (downward motion in
114
+ normalized coords).
113
115
 
114
116
  Args:
115
117
  velocities: Array of SIGNED vertical velocities
116
- countermovement_threshold: Velocity threshold for detecting downward motion (POSITIVE)
118
+ countermovement_threshold: Velocity threshold for detecting downward
119
+ motion (POSITIVE)
117
120
  min_eccentric_frames: Minimum consecutive frames of downward motion
118
121
  standing_start: Optional frame where standing phase ended
119
122
 
kinemotion/cmj/cli.py CHANGED
@@ -177,7 +177,8 @@ def _process_batch_videos(
177
177
  default=None,
178
178
  help="[EXPERT] Override pose tracking confidence",
179
179
  )
180
- def cmj_analyze( # NOSONAR(S107) - Click CLI requires individual parameters for each option
180
+ def cmj_analyze( # NOSONAR(S107) - Click CLI requires individual parameters
181
+ # for each option
181
182
  video_path: tuple[str, ...],
182
183
  output: str | None,
183
184
  json_output: str | None,
@@ -197,10 +198,12 @@ def cmj_analyze( # NOSONAR(S107) - Click CLI requires individual parameters for
197
198
  tracking_confidence: float | None,
198
199
  ) -> None:
199
200
  """
200
- Analyze counter movement jump (CMJ) video(s) to estimate jump performance metrics.
201
+ Analyze counter movement jump (CMJ) video(s) to estimate jump performance
202
+ metrics.
201
203
 
202
- Uses intelligent auto-tuning to select optimal parameters based on video characteristics.
203
- Parameters are automatically adjusted for frame rate, tracking quality, and analysis preset.
204
+ Uses intelligent auto-tuning to select optimal parameters based on video
205
+ characteristics. Parameters are automatically adjusted for frame rate,
206
+ tracking quality, and analysis preset.
204
207
 
205
208
  VIDEO_PATH: Path(s) to video file(s). Supports glob patterns in batch mode.
206
209
 
@@ -341,11 +344,13 @@ def _output_results(metrics: Any, json_output: str | None) -> None:
341
344
  f"Total movement time: {metrics.total_movement_time * 1000:.1f} ms", err=True
342
345
  )
343
346
  click.echo(
344
- f"Peak eccentric velocity: {abs(metrics.peak_eccentric_velocity):.3f} m/s (downward)",
347
+ f"Peak eccentric velocity: {abs(metrics.peak_eccentric_velocity):.3f} "
348
+ "m/s (downward)",
345
349
  err=True,
346
350
  )
347
351
  click.echo(
348
- f"Peak concentric velocity: {metrics.peak_concentric_velocity:.3f} m/s (upward)",
352
+ f"Peak concentric velocity: {metrics.peak_concentric_velocity:.3f} "
353
+ "m/s (upward)",
349
354
  err=True,
350
355
  )
351
356
  if metrics.transition_time is not None:
@@ -370,10 +370,10 @@ class CMJDebugOverlayRenderer(BaseDebugOverlayRenderer):
370
370
 
371
371
  metrics_text = [
372
372
  f"Jump Height: {metrics.jump_height:.3f}m",
373
- f"Flight Time: {metrics.flight_time*1000:.0f}ms",
373
+ f"Flight Time: {metrics.flight_time * 1000:.0f}ms",
374
374
  f"CM Depth: {metrics.countermovement_depth:.3f}m",
375
- f"Ecc Duration: {metrics.eccentric_duration*1000:.0f}ms",
376
- f"Con Duration: {metrics.concentric_duration*1000:.0f}ms",
375
+ f"Ecc Duration: {metrics.eccentric_duration * 1000:.0f}ms",
376
+ f"Con Duration: {metrics.concentric_duration * 1000:.0f}ms",
377
377
  ]
378
378
 
379
379
  # Draw background
@@ -48,12 +48,17 @@ class CMJMetrics:
48
48
  Attributes:
49
49
  jump_height: Maximum jump height in meters
50
50
  flight_time: Time spent in the air in milliseconds
51
- countermovement_depth: Vertical distance traveled during eccentric phase in meters
52
- eccentric_duration: Time from countermovement start to lowest point in milliseconds
51
+ countermovement_depth: Vertical distance traveled during eccentric
52
+ phase in meters
53
+ eccentric_duration: Time from countermovement start to lowest point in
54
+ milliseconds
53
55
  concentric_duration: Time from lowest point to takeoff in milliseconds
54
- total_movement_time: Total time from countermovement start to takeoff in milliseconds
55
- peak_eccentric_velocity: Maximum downward velocity during countermovement in m/s
56
- peak_concentric_velocity: Maximum upward velocity during propulsion in m/s
56
+ total_movement_time: Total time from countermovement start to takeoff
57
+ in milliseconds
58
+ peak_eccentric_velocity: Maximum downward velocity during
59
+ countermovement in m/s
60
+ peak_concentric_velocity: Maximum upward velocity during propulsion in
61
+ m/s
57
62
  transition_time: Duration at lowest point (amortization phase) in milliseconds
58
63
  standing_start_frame: Frame where standing phase ends (countermovement begins)
59
64
  lowest_point_frame: Frame at lowest point of countermovement
@@ -120,8 +120,10 @@ def print_auto_tuned_params(
120
120
  video: Video processor
121
121
  quality_preset: Quality preset
122
122
  params: Auto-tuned parameters
123
- characteristics: Optional video characteristics (for tracking quality display)
124
- extra_params: Optional extra parameters to display (e.g., countermovement_threshold)
123
+ characteristics: Optional video characteristics (for tracking quality
124
+ display)
125
+ extra_params: Optional extra parameters to display (e.g.,
126
+ countermovement_threshold)
125
127
  """
126
128
  click.echo("\n" + "=" * 60, err=True)
127
129
  click.echo("AUTO-TUNED PARAMETERS", err=True)
@@ -236,7 +236,8 @@ class CMJMetricsValidator:
236
236
  elif bounds.contains(flight_time, profile):
237
237
  result.add_info(
238
238
  "flight_time",
239
- f"Flight time {flight_time:.3f}s within expected range for {profile.value}",
239
+ f"Flight time {flight_time:.3f}s within expected range for "
240
+ f"{profile.value}",
240
241
  value=flight_time,
241
242
  )
242
243
  else:
@@ -278,7 +279,8 @@ class CMJMetricsValidator:
278
279
  elif bounds.contains(jump_height, profile):
279
280
  result.add_info(
280
281
  "jump_height",
281
- f"Jump height {jump_height:.3f}m within expected range for {profile.value}",
282
+ f"Jump height {jump_height:.3f}m within expected range for "
283
+ f"{profile.value}",
282
284
  value=jump_height,
283
285
  )
284
286
  else:
@@ -319,7 +321,8 @@ class CMJMetricsValidator:
319
321
  elif bounds.contains(depth, profile):
320
322
  result.add_info(
321
323
  "countermovement_depth",
322
- f"Countermovement depth {depth:.3f}m within expected range for {profile.value}",
324
+ f"Countermovement depth {depth:.3f}m within expected range for "
325
+ f"{profile.value}",
323
326
  value=depth,
324
327
  )
325
328
  else:
@@ -353,14 +356,16 @@ class CMJMetricsValidator:
353
356
  else:
354
357
  result.add_error(
355
358
  "concentric_duration",
356
- f"Concentric duration {duration:.3f}s likely includes standing phase",
359
+ f"Concentric duration {duration:.3f}s likely includes "
360
+ "standing phase",
357
361
  value=duration,
358
362
  bounds=(bounds.absolute_min, bounds.absolute_max),
359
363
  )
360
364
  elif bounds.contains(duration, profile):
361
365
  result.add_info(
362
366
  "concentric_duration",
363
- f"Concentric duration {duration:.3f}s within expected range for {profile.value}",
367
+ f"Concentric duration {duration:.3f}s within expected range for "
368
+ f"{profile.value}",
364
369
  value=duration,
365
370
  )
366
371
  else:
@@ -393,7 +398,8 @@ class CMJMetricsValidator:
393
398
  elif bounds.contains(duration, profile):
394
399
  result.add_info(
395
400
  "eccentric_duration",
396
- f"Eccentric duration {duration:.3f}s within expected range for {profile.value}",
401
+ f"Eccentric duration {duration:.3f}s within expected range for "
402
+ f"{profile.value}",
397
403
  value=duration,
398
404
  )
399
405
  else:
@@ -424,7 +430,8 @@ class CMJMetricsValidator:
424
430
  elif bounds.contains(ecc_vel, profile):
425
431
  result.add_info(
426
432
  "peak_eccentric_velocity",
427
- f"Peak eccentric velocity {ecc_vel:.2f} m/s within range for {profile.value}",
433
+ f"Peak eccentric velocity {ecc_vel:.2f} m/s within range "
434
+ f"for {profile.value}",
428
435
  value=ecc_vel,
429
436
  )
430
437
  else:
@@ -445,21 +452,24 @@ class CMJMetricsValidator:
445
452
  if con_vel < bounds.absolute_min:
446
453
  result.add_error(
447
454
  "peak_concentric_velocity",
448
- f"Peak concentric velocity {con_vel:.2f} m/s insufficient to leave ground",
455
+ f"Peak concentric velocity {con_vel:.2f} m/s "
456
+ "insufficient to leave ground",
449
457
  value=con_vel,
450
458
  bounds=(bounds.absolute_min, bounds.absolute_max),
451
459
  )
452
460
  else:
453
461
  result.add_error(
454
462
  "peak_concentric_velocity",
455
- f"Peak concentric velocity {con_vel:.2f} m/s exceeds elite capability",
463
+ f"Peak concentric velocity {con_vel:.2f} m/s exceeds "
464
+ "elite capability",
456
465
  value=con_vel,
457
466
  bounds=(bounds.absolute_min, bounds.absolute_max),
458
467
  )
459
468
  elif bounds.contains(con_vel, profile):
460
469
  result.add_info(
461
470
  "peak_concentric_velocity",
462
- f"Peak concentric velocity {con_vel:.2f} m/s within range for {profile.value}",
471
+ f"Peak concentric velocity {con_vel:.2f} m/s within range "
472
+ f"for {profile.value}",
463
473
  value=con_vel,
464
474
  )
465
475
  else:
@@ -492,15 +502,17 @@ class CMJMetricsValidator:
492
502
  if error_pct > MetricConsistency.HEIGHT_FLIGHT_TIME_TOLERANCE:
493
503
  result.add_error(
494
504
  "height_flight_time_consistency",
495
- f"Jump height {jump_height:.3f}m inconsistent with flight time {flight_time:.3f}s "
496
- f"(expected {expected_height:.3f}m, error {error_pct*100:.1f}%)",
505
+ f"Jump height {jump_height:.3f}m inconsistent with flight "
506
+ f"time {flight_time:.3f}s (expected {expected_height:.3f}m, "
507
+ f"error {error_pct * 100:.1f}%)",
497
508
  value=error_pct,
498
509
  bounds=(0, MetricConsistency.HEIGHT_FLIGHT_TIME_TOLERANCE),
499
510
  )
500
511
  else:
501
512
  result.add_info(
502
513
  "height_flight_time_consistency",
503
- f"Jump height and flight time consistent (error {error_pct*100:.1f}%)",
514
+ f"Jump height and flight time consistent "
515
+ f"(error {error_pct * 100:.1f}%)",
504
516
  value=error_pct,
505
517
  )
506
518
 
@@ -525,7 +537,7 @@ class CMJMetricsValidator:
525
537
  error_msg = (
526
538
  f"Peak velocity {velocity:.2f} m/s inconsistent with "
527
539
  f"jump height {jump_height:.3f}m (expected {expected_velocity:.2f} "
528
- f"m/s, error {error_pct*100:.1f}%)"
540
+ f"m/s, error {error_pct * 100:.1f}%)"
529
541
  )
530
542
  result.add_warning(
531
543
  "velocity_height_consistency",
@@ -536,7 +548,8 @@ class CMJMetricsValidator:
536
548
  else:
537
549
  result.add_info(
538
550
  "velocity_height_consistency",
539
- f"Peak velocity and jump height consistent (error {error_pct*100:.1f}%)",
551
+ f"Peak velocity and jump height consistent "
552
+ f"(error {error_pct * 100:.1f}%)",
540
553
  value=error_pct,
541
554
  )
542
555
 
@@ -577,14 +590,16 @@ class CMJMetricsValidator:
577
590
  if expected_min <= rsi <= expected_max:
578
591
  result.add_info(
579
592
  "rsi",
580
- f"RSI {rsi:.2f} within expected range [{expected_min:.2f}-{expected_max:.2f}] "
593
+ f"RSI {rsi:.2f} within expected range "
594
+ f"[{expected_min:.2f}-{expected_max:.2f}] "
581
595
  f"for {profile.value}",
582
596
  value=rsi,
583
597
  )
584
598
  else:
585
599
  result.add_warning(
586
600
  "rsi",
587
- f"RSI {rsi:.2f} outside typical range [{expected_min:.2f}-{expected_max:.2f}] "
601
+ f"RSI {rsi:.2f} outside typical range "
602
+ f"[{expected_min:.2f}-{expected_max:.2f}] "
588
603
  f"for {profile.value}",
589
604
  value=rsi,
590
605
  bounds=(expected_min, expected_max),
@@ -650,7 +665,8 @@ class CMJMetricsValidator:
650
665
  if ratio < MetricConsistency.CONTACT_DEPTH_RATIO_MIN:
651
666
  result.add_warning(
652
667
  "contact_depth_ratio",
653
- f"Contact time {ratio:.2f}s/m to depth ratio: Very fast for depth traversed",
668
+ f"Contact time {ratio:.2f}s/m to depth ratio: Very fast for "
669
+ "depth traversed",
654
670
  value=ratio,
655
671
  bounds=(
656
672
  MetricConsistency.CONTACT_DEPTH_RATIO_MIN,
@@ -702,7 +718,8 @@ class CMJMetricsValidator:
702
718
  if not TripleExtensionBounds.knee_angle_valid(knee, profile):
703
719
  result.add_warning(
704
720
  "knee_angle",
705
- f"Knee angle {knee:.1f}° outside expected range for {profile.value}",
721
+ f"Knee angle {knee:.1f}° outside expected range for "
722
+ f"{profile.value}",
706
723
  value=knee,
707
724
  )
708
725
  else:
@@ -717,13 +734,15 @@ class CMJMetricsValidator:
717
734
  if not TripleExtensionBounds.ankle_angle_valid(ankle, profile):
718
735
  result.add_warning(
719
736
  "ankle_angle",
720
- f"Ankle angle {ankle:.1f}° outside expected range for {profile.value}",
737
+ f"Ankle angle {ankle:.1f}° outside expected range for "
738
+ f"{profile.value}",
721
739
  value=ankle,
722
740
  )
723
741
  else:
724
742
  result.add_info(
725
743
  "ankle_angle",
726
- f"Ankle angle {ankle:.1f}° within expected range for {profile.value}",
744
+ f"Ankle angle {ankle:.1f}° within expected range for "
745
+ f"{profile.value}",
727
746
  value=ankle,
728
747
  )
729
748
 
@@ -227,7 +227,8 @@ class DropJumpMetricsValidator:
227
227
  profile_name = result.athlete_profile.value
228
228
  result.add_warning(
229
229
  "contact_time",
230
- f"Contact time {contact_time_s:.3f}s unusual for {profile_name} athlete",
230
+ f"Contact time {contact_time_s:.3f}s unusual for "
231
+ f"{profile_name} athlete",
231
232
  value=contact_time_s,
232
233
  )
233
234
 
@@ -285,7 +286,7 @@ class DropJumpMetricsValidator:
285
286
  contact_time_s = contact_time_ms / 1000.0
286
287
  flight_time_s = flight_time_ms / 1000.0
287
288
 
288
- if contact_time_s > 0:
289
+ if contact_time_s > 0 and flight_time_s > 0:
289
290
  rsi = flight_time_s / contact_time_s
290
291
  result.rsi = rsi
291
292
  result.contact_flight_ratio = contact_time_s / flight_time_s
@@ -317,7 +318,8 @@ class DropJumpMetricsValidator:
317
318
  """Validate consistency between kinematic and trajectory-based heights.
318
319
 
319
320
  Kinematic height (h = g*t²/8) comes from flight time (objective).
320
- Trajectory height comes from position tracking (subject to landmark detection noise).
321
+ Trajectory height comes from position tracking (subject to landmark
322
+ detection noise).
321
323
 
322
324
  Expected correlation: r > 0.95, absolute difference < 5% for quality video.
323
325
  """
@@ -336,8 +338,9 @@ class DropJumpMetricsValidator:
336
338
  result.add_warning(
337
339
  "height_consistency",
338
340
  f"Kinematic ({jump_height_kinematic_m:.3f}m) and trajectory "
339
- f"({jump_height_trajectory_m:.3f}m) heights differ by {percent_error:.1f}%. "
340
- f"May indicate landmark detection issues or video quality problems.",
341
+ f"({jump_height_trajectory_m:.3f}m) heights differ by "
342
+ f"{percent_error:.1f}%. May indicate landmark detection "
343
+ "issues or video quality problems.",
341
344
  value=percent_error,
342
345
  bounds=(0, 10),
343
346
  )
@@ -146,7 +146,8 @@ class AlgorithmConfig:
146
146
  """Complete algorithm configuration for reproducibility.
147
147
 
148
148
  Attributes:
149
- detection_method: Algorithm used ("backward_search" for CMJ, "forward_search" for drop)
149
+ detection_method: Algorithm used ("backward_search" for CMJ,
150
+ "forward_search" for drop)
150
151
  tracking_method: Pose tracking method ("mediapipe_pose")
151
152
  model_complexity: MediaPipe model complexity (0, 1, or 2)
152
153
  smoothing: Smoothing configuration
@@ -307,8 +307,9 @@ def _generate_warnings(
307
307
  # Tracking stability warnings
308
308
  if not indicators.tracking_stable:
309
309
  warnings.append(
310
- f"Unstable landmark tracking detected (variance {indicators.position_variance:.4f}). "
311
- "This may indicate jitter or occlusion. Consider better lighting or camera position."
310
+ f"Unstable landmark tracking detected "
311
+ f"(variance {indicators.position_variance:.4f}). This may indicate "
312
+ "jitter or occlusion. Consider better lighting or camera position."
312
313
  )
313
314
 
314
315
  # Outlier warnings
@@ -349,8 +350,8 @@ def _generate_warnings(
349
350
  # Overall confidence warning
350
351
  if confidence == "low":
351
352
  warnings.append(
352
- "⚠️ LOW CONFIDENCE: Results may be unreliable. "
353
- "Review quality indicators and consider re-recording with better conditions."
353
+ "⚠️ LOW CONFIDENCE: Results may be unreliable. Review quality "
354
+ "indicators and consider re-recording with better conditions."
354
355
  )
355
356
  elif confidence == "medium":
356
357
  warnings.append(
@@ -124,7 +124,8 @@ def _store_smoothed_landmarks(
124
124
  )
125
125
 
126
126
 
127
- def _smooth_landmarks_core( # NOSONAR(S1172) - polyorder used via closure capture in smoother_fn
127
+ def _smooth_landmarks_core( # NOSONAR(S1172) - polyorder used via closure
128
+ # capture in smoother_fn
128
129
  landmark_sequence: LandmarkSequence,
129
130
  window_length: int,
130
131
  polyorder: int,
@@ -136,7 +137,8 @@ def _smooth_landmarks_core( # NOSONAR(S1172) - polyorder used via closure captu
136
137
  Args:
137
138
  landmark_sequence: List of landmark dictionaries from each frame
138
139
  window_length: Length of filter window (must be odd)
139
- polyorder: Order of polynomial used to fit samples (captured by smoother_fn closure)
140
+ polyorder: Order of polynomial used to fit samples (captured by
141
+ smoother_fn closure)
140
142
  smoother_fn: Function that takes (x_coords, y_coords, valid_frames)
141
143
  and returns (x_smooth, y_smooth)
142
144
 
@@ -46,8 +46,9 @@ class VideoProcessor:
46
46
  self.width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
47
47
  self.height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
48
48
 
49
- # Extract rotation metadata from video (iPhones store rotation in side_data_list)
50
- # OpenCV ignores rotation metadata, so we need to extract and apply it manually
49
+ # Extract rotation metadata from video (iPhones store rotation in
50
+ # side_data_list). OpenCV ignores rotation metadata, so we need to
51
+ # extract and apply it manually
51
52
  self.rotation = 0 # Will be set by _extract_video_metadata()
52
53
 
53
54
  # Extract codec information from video metadata
@@ -98,7 +98,8 @@ def _find_stable_baseline(
98
98
  """Find first stable period and return baseline position.
99
99
 
100
100
  Returns:
101
- Tuple of (baseline_start_frame, baseline_position). Returns (-1, 0.0) if not found.
101
+ Tuple of (baseline_start_frame, baseline_position). Returns (-1, 0.0)
102
+ if not found.
102
103
  """
103
104
  stable_window = min_stable_frames
104
105
 
@@ -159,8 +160,8 @@ def _find_drop_from_baseline(
159
160
  f"{position_change_threshold:.4f}"
160
161
  )
161
162
  print(
162
- f" avg_position: {avg_position:.4f} vs "
163
- f"baseline: {baseline_position:.4f}"
163
+ f" avg_position: {avg_position:.4f} vs baseline: "
164
+ f"{baseline_position:.4f}"
164
165
  )
165
166
 
166
167
  return drop_frame
@@ -179,7 +180,8 @@ def detect_drop_start(
179
180
  debug: bool = False,
180
181
  ) -> int:
181
182
  """
182
- Detect when the drop jump actually starts by finding stable period then detecting drop.
183
+ Detect when the drop jump actually starts by finding stable period then
184
+ detecting drop.
183
185
 
184
186
  Strategy:
185
187
  1. Scan forward to find first STABLE period (low variance over N frames)
@@ -191,7 +193,8 @@ def detect_drop_start(
191
193
  Args:
192
194
  positions: Array of vertical positions (0-1 normalized, y increases downward)
193
195
  fps: Video frame rate
194
- min_stationary_duration: Minimum duration (seconds) of stable period (default: 1.0s)
196
+ min_stationary_duration: Minimum duration (seconds) of stable period
197
+ (default: 1.0s)
195
198
  position_change_threshold: Position change indicating start of drop
196
199
  (default: 0.02 = 2% of frame)
197
200
  smoothing_window: Window for computing position variance
@@ -832,9 +835,11 @@ def extract_foot_positions_and_visibilities(
832
835
  smoothed_landmarks: list[dict[str, tuple[float, float, float]] | None],
833
836
  ) -> tuple[np.ndarray, np.ndarray]:
834
837
  """
835
- Extract vertical positions and average visibilities from smoothed landmarks.
838
+ Extract vertical positions and average visibilities from smoothed
839
+ landmarks.
836
840
 
837
- This utility function eliminates code duplication between CLI and programmatic usage.
841
+ This utility function eliminates code duplication between CLI and
842
+ programmatic usage.
838
843
 
839
844
  Args:
840
845
  smoothed_landmarks: Smoothed landmark sequence from tracking
@@ -133,7 +133,8 @@ class AnalysisParameters:
133
133
  default=None,
134
134
  help="[EXPERT] Override pose tracking confidence",
135
135
  )
136
- def dropjump_analyze( # NOSONAR(S107) - Click CLI requires individual parameters for each option
136
+ def dropjump_analyze( # NOSONAR(S107) - Click CLI requires individual
137
+ # parameters for each option
137
138
  video_path: tuple[str, ...],
138
139
  output: str | None,
139
140
  json_output: str | None,
@@ -153,10 +154,12 @@ def dropjump_analyze( # NOSONAR(S107) - Click CLI requires individual parameter
153
154
  tracking_confidence: float | None,
154
155
  ) -> None:
155
156
  """
156
- Analyze drop-jump video(s) to estimate ground contact time, flight time, and jump height.
157
+ Analyze drop-jump video(s) to estimate ground contact time, flight time,
158
+ and jump height.
157
159
 
158
- Uses intelligent auto-tuning to select optimal parameters based on video characteristics.
159
- Parameters are automatically adjusted for frame rate, tracking quality, and analysis preset.
160
+ Uses intelligent auto-tuning to select optimal parameters based on video
161
+ characteristics. Parameters are automatically adjusted for frame rate,
162
+ tracking quality, and analysis preset.
160
163
 
161
164
  VIDEO_PATH: Path(s) to video file(s). Supports glob patterns in batch mode
162
165
  (e.g., "videos/*.mp4").
@@ -235,7 +235,8 @@ def _identify_main_contact_phase(
235
235
  ]
236
236
 
237
237
  if ground_after_air and first_ground_idx < first_air_idx:
238
- # Check if first ground is at higher elevation (lower y) than ground after air
238
+ # Check if first ground is at higher elevation (lower y) than
239
+ # ground after air
239
240
  first_ground_y = float(
240
241
  np.mean(foot_y_positions[first_ground_start : first_ground_end + 1])
241
242
  )
@@ -394,8 +395,10 @@ def calculate_drop_jump_metrics(
394
395
  foot_y_positions: Vertical positions of feet (normalized 0-1)
395
396
  fps: Video frame rate
396
397
  drop_start_frame: Optional manual drop start frame
397
- velocity_threshold: Velocity threshold used for contact detection (for interpolation)
398
- smoothing_window: Window size for velocity/acceleration smoothing (must be odd)
398
+ velocity_threshold: Velocity threshold used for contact detection
399
+ (for interpolation)
400
+ smoothing_window: Window size for velocity/acceleration smoothing
401
+ (must be odd)
399
402
  polyorder: Polynomial order for Savitzky-Golay filter (default: 2)
400
403
  use_curvature: Whether to use curvature analysis for refining transitions
401
404
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kinemotion
3
- Version: 0.30.0
3
+ Version: 0.31.1
4
4
  Summary: Video-based kinematic analysis for athletic performance
5
5
  Project-URL: Homepage, https://github.com/feniix/kinemotion
6
6
  Project-URL: Repository, https://github.com/feniix/kinemotion
@@ -0,0 +1,35 @@
1
+ kinemotion/__init__.py,sha256=sxdDOekOrIgjxm842gy-6zfq7OWmGl9ShJtXCm4JI7c,723
2
+ kinemotion/api.py,sha256=IcvtjK38Eoh5u53yu99NaeChLDNvv7NSnlDcK8fhVS8,39349
3
+ kinemotion/cli.py,sha256=cqYV_7URH0JUDy1VQ_EDLv63FmNO4Ns20m6s1XAjiP4,464
4
+ kinemotion/cmj/__init__.py,sha256=Ynv0-Oco4I3Y1Ubj25m3h9h2XFqeNwpAewXmAYOmwfU,127
5
+ kinemotion/cmj/analysis.py,sha256=88zRNufM88Mc4p_YdnbXoSJM-pWAe4C9I7TqcH7QM9U,19815
6
+ kinemotion/cmj/cli.py,sha256=Mj2h9It1jVjAauvtCxfLWTRijj7zbYhxZuebhw2Zz6w,10828
7
+ kinemotion/cmj/debug_overlay.py,sha256=fXmWoHhqMLGo4vTtB6Ezs3yLUDOLw63zLIgU2gFlJQU,15892
8
+ kinemotion/cmj/joint_angles.py,sha256=HmheIEiKcQz39cRezk4h-htorOhGNPsqKIR9RsAEKts,9960
9
+ kinemotion/cmj/kinematics.py,sha256=qRBe87NkX-7HQTQ8RoF-EpvfcffgP5vycJJRrxpHboc,10307
10
+ kinemotion/core/__init__.py,sha256=HsqolRa60cW3vrG8F9Lvr9WvWcs5hCmsTzSgo7imi-4,1278
11
+ kinemotion/core/auto_tuning.py,sha256=j6cul_qC6k0XyryCG93C1AWH2MKPj3UBMzuX02xaqfI,11235
12
+ kinemotion/core/cli_utils.py,sha256=8xQvTiZFAQULGdpB9g-Mvf0doSgoXi1ZZfVY2T2zWss,7278
13
+ kinemotion/core/cmj_metrics_validator.py,sha256=VVRn56pG3GKna_EDv8jQ5msYNv4VOUR5fTcqpSUzdIA,31334
14
+ kinemotion/core/cmj_validation_bounds.py,sha256=NXW0d4S8hDqSkzAiyX9UyWDnKsWf61ygEKTKE2a1uNc,14069
15
+ kinemotion/core/debug_overlay_utils.py,sha256=TyUb5okv5qw8oeaX3jsUO_kpwf1NnaHEAOTm-8LwTno,4587
16
+ kinemotion/core/dropjump_metrics_validator.py,sha256=zTkWslex9qGQuJSEZltUXYyFLBtisPGGxwqpYpE_Mx4,12040
17
+ kinemotion/core/dropjump_validation_bounds.py,sha256=Ow7T-0IK_qy0k9UZdiCuT_tttxklWAkuXFalQkF8Jbs,6927
18
+ kinemotion/core/filtering.py,sha256=f-m-aA59e4WqE6u-9MA51wssu7rI-Y_7n1cG8IWdeRQ,11241
19
+ kinemotion/core/formatting.py,sha256=G_3eqgOtym9RFOZVEwCxye4A2cyrmgvtQ214vIshowU,2480
20
+ kinemotion/core/metadata.py,sha256=iz9YdkesHo-85TVBCoQVn7zkbrSde_fqjU79s_b-TZk,6829
21
+ kinemotion/core/pose.py,sha256=ztemdZ_ysVVK3gbXabm8qS_dr1VfJX9KZjmcO-Z-iNE,8532
22
+ kinemotion/core/quality.py,sha256=dPGQp08y8DdEUbUdjTThnUOUsALgF0D2sdz50cm6wLI,13098
23
+ kinemotion/core/smoothing.py,sha256=J-GjFP6xW9TO4teOqFd5523zTY2jYIY1Nd99cZvoM3s,14110
24
+ kinemotion/core/video_io.py,sha256=fDdyYVIKqUSgCjBJa8l_S0SrDPDAhrWYfsDBNRuz1oM,7549
25
+ kinemotion/dropjump/__init__.py,sha256=yc1XiZ9vfo5h_n7PKVSiX2TTgaIfGL7Y7SkQtiDZj_E,838
26
+ kinemotion/dropjump/analysis.py,sha256=Sz1lnAsQSp9fzkDy8AuqQtE47LKBqNwiLkaKzz4W_Wk,29099
27
+ kinemotion/dropjump/cli.py,sha256=n_Wfv3AC6YIgRPYhO3F2nTSai0NR7fh95nAoWjryQeY,16250
28
+ kinemotion/dropjump/debug_overlay.py,sha256=LkPw6ucb7beoYWS4L-Lvjs1KLCm5wAWDAfiznUeV2IQ,5668
29
+ kinemotion/dropjump/kinematics.py,sha256=IH6nCOwTuocQNX1VPS_am9vPpMRUUla0a0MjDhEiXnA,17129
30
+ kinemotion/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
+ kinemotion-0.31.1.dist-info/METADATA,sha256=bZB-v07C8eFhrmm0QArfwocLbYOxHkRIwLJkX_5ZQxM,26020
32
+ kinemotion-0.31.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
33
+ kinemotion-0.31.1.dist-info/entry_points.txt,sha256=zaqnAnjLvcdrk1Qvj5nvXZCZ2gp0prS7it1zTJygcIY,50
34
+ kinemotion-0.31.1.dist-info/licenses/LICENSE,sha256=KZajvqsHw0NoOHOi2q0FZ4NBe9HdV6oey-IPYAtHXfg,1088
35
+ kinemotion-0.31.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,35 +0,0 @@
1
- kinemotion/__init__.py,sha256=sxdDOekOrIgjxm842gy-6zfq7OWmGl9ShJtXCm4JI7c,723
2
- kinemotion/api.py,sha256=ozt6GLZtw2ZWki0tBlkpOQgcNH_M7GfrRPlc_Rg8ROw,39284
3
- kinemotion/cli.py,sha256=cqYV_7URH0JUDy1VQ_EDLv63FmNO4Ns20m6s1XAjiP4,464
4
- kinemotion/cmj/__init__.py,sha256=Ynv0-Oco4I3Y1Ubj25m3h9h2XFqeNwpAewXmAYOmwfU,127
5
- kinemotion/cmj/analysis.py,sha256=il7-sfM89ZetxLhmw9boViaP4E8Y3mlS_XI-B5txmMs,19795
6
- kinemotion/cmj/cli.py,sha256=12FEfWrseG4kCUbgHHdBPkWp6zzVQ0VAzfgNJotArmM,10792
7
- kinemotion/cmj/debug_overlay.py,sha256=D-y2FQKI01KY0WXFKTKg6p9Qj3AkXCE7xjau3Ais080,15886
8
- kinemotion/cmj/joint_angles.py,sha256=HmheIEiKcQz39cRezk4h-htorOhGNPsqKIR9RsAEKts,9960
9
- kinemotion/cmj/kinematics.py,sha256=ax2RijtAWItZPNRmNr-CvC7bOSsZQw2qdCEnm5hUUpU,10247
10
- kinemotion/core/__init__.py,sha256=HsqolRa60cW3vrG8F9Lvr9WvWcs5hCmsTzSgo7imi-4,1278
11
- kinemotion/core/auto_tuning.py,sha256=j6cul_qC6k0XyryCG93C1AWH2MKPj3UBMzuX02xaqfI,11235
12
- kinemotion/core/cli_utils.py,sha256=Pq1JF7yvK1YbH0tOUWKjplthCbWsJQt4Lv7esPYH4FM,7254
13
- kinemotion/core/cmj_metrics_validator.py,sha256=bbOPTbFqDEZv3lDA8qejjCcMqXE7TYvHizCcWzHRW9Y,30902
14
- kinemotion/core/cmj_validation_bounds.py,sha256=NXW0d4S8hDqSkzAiyX9UyWDnKsWf61ygEKTKE2a1uNc,14069
15
- kinemotion/core/debug_overlay_utils.py,sha256=TyUb5okv5qw8oeaX3jsUO_kpwf1NnaHEAOTm-8LwTno,4587
16
- kinemotion/core/dropjump_metrics_validator.py,sha256=yGcg8ub6Z791qj5BxCn9mHin608tfxvxxfIeTql8HcY,11967
17
- kinemotion/core/dropjump_validation_bounds.py,sha256=Ow7T-0IK_qy0k9UZdiCuT_tttxklWAkuXFalQkF8Jbs,6927
18
- kinemotion/core/filtering.py,sha256=f-m-aA59e4WqE6u-9MA51wssu7rI-Y_7n1cG8IWdeRQ,11241
19
- kinemotion/core/formatting.py,sha256=G_3eqgOtym9RFOZVEwCxye4A2cyrmgvtQ214vIshowU,2480
20
- kinemotion/core/metadata.py,sha256=PyGHL6sx7Hj21lyorg2VsWP9BGTj_y_-wWU6eKCEfJo,6817
21
- kinemotion/core/pose.py,sha256=ztemdZ_ysVVK3gbXabm8qS_dr1VfJX9KZjmcO-Z-iNE,8532
22
- kinemotion/core/quality.py,sha256=OC9nuf5IrQ9xURf3eA50VoNWOqkGwbjJpS90q2FDQzA,13082
23
- kinemotion/core/smoothing.py,sha256=x4o3BnG6k8OaV3emgpoJDF84CE9k5RYR7BeSYH_-8Es,14092
24
- kinemotion/core/video_io.py,sha256=SQBJSgAV8uOkAh96gNZRjd6XJG1G9dZzDc8kAZ_twy0,7538
25
- kinemotion/dropjump/__init__.py,sha256=yc1XiZ9vfo5h_n7PKVSiX2TTgaIfGL7Y7SkQtiDZj_E,838
26
- kinemotion/dropjump/analysis.py,sha256=BQ5NqSPNJjFQOb-W4bXSLvjCgWd-nvqx5NElyeqZJC4,29067
27
- kinemotion/dropjump/cli.py,sha256=ZyroaYPwz8TgfL39Wcaj6m68Awl6lYXC75ttaflU-c0,16236
28
- kinemotion/dropjump/debug_overlay.py,sha256=LkPw6ucb7beoYWS4L-Lvjs1KLCm5wAWDAfiznUeV2IQ,5668
29
- kinemotion/dropjump/kinematics.py,sha256=AfqIS8kaI3B8olPX9EY1QxQsuNmuJA5GRsI1EL4NHHg,17091
30
- kinemotion/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
- kinemotion-0.30.0.dist-info/METADATA,sha256=AUhSzQW2siu-R4KfUyS5YRDy1i9eYzzYxOMdWZXF2ww,26020
32
- kinemotion-0.30.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
33
- kinemotion-0.30.0.dist-info/entry_points.txt,sha256=zaqnAnjLvcdrk1Qvj5nvXZCZ2gp0prS7it1zTJygcIY,50
34
- kinemotion-0.30.0.dist-info/licenses/LICENSE,sha256=KZajvqsHw0NoOHOi2q0FZ4NBe9HdV6oey-IPYAtHXfg,1088
35
- kinemotion-0.30.0.dist-info/RECORD,,