kinemotion 0.71.0__py3-none-any.whl → 0.72.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.

Files changed (36) hide show
  1. kinemotion/__init__.py +1 -1
  2. kinemotion/api.py +2 -2
  3. kinemotion/cli.py +1 -1
  4. kinemotion/cmj/analysis.py +2 -4
  5. kinemotion/cmj/api.py +9 -7
  6. kinemotion/cmj/debug_overlay.py +154 -286
  7. kinemotion/cmj/joint_angles.py +96 -31
  8. kinemotion/cmj/metrics_validator.py +22 -29
  9. kinemotion/cmj/validation_bounds.py +1 -18
  10. kinemotion/core/__init__.py +0 -2
  11. kinemotion/core/auto_tuning.py +95 -100
  12. kinemotion/core/debug_overlay_utils.py +142 -15
  13. kinemotion/core/experimental.py +55 -51
  14. kinemotion/core/filtering.py +15 -11
  15. kinemotion/core/overlay_constants.py +61 -0
  16. kinemotion/core/pipeline_utils.py +1 -1
  17. kinemotion/core/pose.py +47 -98
  18. kinemotion/core/smoothing.py +65 -51
  19. kinemotion/core/types.py +15 -0
  20. kinemotion/core/validation.py +6 -7
  21. kinemotion/core/video_io.py +14 -9
  22. kinemotion/{dropjump → dj}/__init__.py +2 -2
  23. kinemotion/{dropjump → dj}/analysis.py +192 -75
  24. kinemotion/{dropjump → dj}/api.py +13 -17
  25. kinemotion/{dropjump → dj}/cli.py +62 -78
  26. kinemotion/dj/debug_overlay.py +241 -0
  27. kinemotion/{dropjump → dj}/kinematics.py +106 -44
  28. kinemotion/{dropjump → dj}/metrics_validator.py +1 -1
  29. kinemotion/{dropjump → dj}/validation_bounds.py +1 -1
  30. {kinemotion-0.71.0.dist-info → kinemotion-0.72.0.dist-info}/METADATA +1 -1
  31. kinemotion-0.72.0.dist-info/RECORD +50 -0
  32. kinemotion/dropjump/debug_overlay.py +0 -182
  33. kinemotion-0.71.0.dist-info/RECORD +0 -49
  34. {kinemotion-0.71.0.dist-info → kinemotion-0.72.0.dist-info}/WHEEL +0 -0
  35. {kinemotion-0.71.0.dist-info → kinemotion-0.72.0.dist-info}/entry_points.txt +0 -0
  36. {kinemotion-0.71.0.dist-info → kinemotion-0.72.0.dist-info}/licenses/LICENSE +0 -0
@@ -234,6 +234,16 @@ def _identify_main_contact_phase(
234
234
  ) -> tuple[int, int, bool]:
235
235
  """Identify the main contact phase and determine if it's a drop jump.
236
236
 
237
+ Drop jump detection strategy:
238
+ 1. With position-based filtering, box period is classified as IN_AIR
239
+ 2. Pattern: IN_AIR(box+drop) → ON_GROUND(contact) → IN_AIR(flight) → ON_GROUND(land)
240
+ 3. The FIRST ground phase is the contact phase (before the flight)
241
+ 4. The LAST ground phase is the landing (after the flight)
242
+
243
+ The key differentiator from regular jump:
244
+ - Drop jump: starts with IN_AIR, has 2+ ground phases with air between them
245
+ - Regular jump: starts with ON_GROUND, may have multiple phases
246
+
237
247
  Args:
238
248
  phases: All phase tuples
239
249
  ground_phases: Ground phases with indices
@@ -247,34 +257,43 @@ def _identify_main_contact_phase(
247
257
  contact_start, contact_end = ground_phases[0][0], ground_phases[0][1]
248
258
  is_drop_jump = False
249
259
 
250
- # Detect if this is a drop jump or regular jump
260
+ # Check if this looks like a drop jump pattern:
261
+ # Pattern: starts with IN_AIR → ON_GROUND → IN_AIR → ON_GROUND
251
262
  if air_phases_indexed and len(ground_phases) >= 2:
263
+ _, _, first_air_idx = air_phases_indexed[0]
252
264
  first_ground_start, first_ground_end, first_ground_idx = ground_phases[0]
253
- first_air_idx = air_phases_indexed[0][2]
254
-
255
- # Find ground phase after first air phase
256
- ground_after_air = [
257
- (start, end, idx) for start, end, idx in ground_phases if idx > first_air_idx
258
- ]
259
-
260
- if ground_after_air and first_ground_idx < first_air_idx:
261
- # Check if first ground is at higher elevation (lower y) than
262
- # ground after air using robust temporal averaging
263
- first_ground_y = _compute_robust_phase_position(
264
- foot_y_positions, first_ground_start, first_ground_end
265
- )
266
- second_ground_start, second_ground_end, _ = ground_after_air[0]
267
- second_ground_y = _compute_robust_phase_position(
268
- foot_y_positions, second_ground_start, second_ground_end
269
- )
270
-
271
- # If first ground is significantly higher (>7% of frame), it's a drop jump
272
- # Increased from 0.05 to 0.07 with 11-frame temporal averaging
273
- # for reproducibility (balances detection sensitivity with noise robustness)
274
- # Note: MediaPipe has inherent non-determinism (Google issue #3945)
275
- if second_ground_y - first_ground_y > 0.07:
265
+
266
+ # Drop jump pattern: first phase is IN_AIR (athlete on box/dropping)
267
+ # followed by ground contact, then flight, then landing
268
+ if first_air_idx == 0 and first_ground_idx == 1:
269
+ # First phase is air (box + drop), second phase is ground (contact)
270
+ # Check if there's a flight phase after contact
271
+ air_after_contact = [
272
+ (s, e, i) for s, e, i in air_phases_indexed if i > first_ground_idx
273
+ ]
274
+ if air_after_contact:
275
+ # This is a drop jump: first ground = contact, last ground = landing
276
276
  is_drop_jump = True
277
- contact_start, contact_end = second_ground_start, second_ground_end
277
+ contact_start, contact_end = first_ground_start, first_ground_end
278
+
279
+ # Legacy detection: first ground is on elevated box (lower y)
280
+ # This handles cases where box level IS detected as ground
281
+ if not is_drop_jump and first_ground_idx < first_air_idx:
282
+ ground_after_air = [
283
+ (start, end, idx) for start, end, idx in ground_phases if idx > first_air_idx
284
+ ]
285
+ if ground_after_air:
286
+ first_ground_y = _compute_robust_phase_position(
287
+ foot_y_positions, first_ground_start, first_ground_end
288
+ )
289
+ second_ground_start, second_ground_end, _ = ground_after_air[0]
290
+ second_ground_y = _compute_robust_phase_position(
291
+ foot_y_positions, second_ground_start, second_ground_end
292
+ )
293
+ # If first ground is significantly higher (>7% of frame), it's a drop jump
294
+ if second_ground_y - first_ground_y > 0.07:
295
+ is_drop_jump = True
296
+ contact_start, contact_end = second_ground_start, second_ground_end
278
297
 
279
298
  if not is_drop_jump:
280
299
  # Regular jump: use longest ground contact phase
@@ -317,6 +336,30 @@ def _find_precise_phase_timing(
317
336
  return contact_start_frac, contact_end_frac
318
337
 
319
338
 
339
+ def _find_landing_from_phases(
340
+ phases: list[tuple[int, int, ContactState]],
341
+ flight_start: int,
342
+ ) -> int | None:
343
+ """Find landing frame from phase detection.
344
+
345
+ Looks for the first ON_GROUND phase that starts after the flight_start frame.
346
+ This represents the first ground contact after the reactive jump.
347
+
348
+ Args:
349
+ phases: List of (start, end, state) phase tuples
350
+ flight_start: Frame where flight begins (takeoff)
351
+
352
+ Returns:
353
+ Landing frame (start of landing phase), or None if not found
354
+ """
355
+ for start, _, state in phases:
356
+ if state == ContactState.ON_GROUND and start > flight_start:
357
+ # Found the landing phase - return its start frame
358
+ return start
359
+
360
+ return None
361
+
362
+
320
363
  def _analyze_flight_phase(
321
364
  metrics: DropJumpMetrics,
322
365
  phases: list[tuple[int, int, ContactState]],
@@ -345,22 +388,20 @@ def _analyze_flight_phase(
345
388
  # Find takeoff frame (end of ground contact)
346
389
  flight_start = contact_end
347
390
 
348
- # Compute accelerations for landing detection
349
- accelerations = compute_acceleration_from_derivative(
350
- foot_y_positions, window_length=smoothing_window, polyorder=polyorder
351
- )
391
+ # Use phase detection for landing (more accurate than position-based)
392
+ # Find the next ON_GROUND phase after the flight phase
393
+ flight_end = _find_landing_from_phases(phases, flight_start)
352
394
 
353
- # Use acceleration-based landing detection (like CMJ)
354
- # This finds the actual ground impact, not just when velocity drops
355
- flight_end = find_landing_from_acceleration(
356
- foot_y_positions, accelerations, flight_start, fps, search_duration=0.7
357
- )
358
-
359
- # Store integer frame indices
360
- metrics.flight_start_frame = flight_start
361
- metrics.flight_end_frame = flight_end
395
+ # If phase detection fails, fall back to position-based detection
396
+ if flight_end is None:
397
+ accelerations = compute_acceleration_from_derivative(
398
+ foot_y_positions, window_length=smoothing_window, polyorder=polyorder
399
+ )
400
+ flight_end = find_landing_from_acceleration(
401
+ foot_y_positions, accelerations, flight_start, fps
402
+ )
362
403
 
363
- # Find precise sub-frame timing for takeoff
404
+ # Find precise sub-frame timing for takeoff and landing
364
405
  flight_start_frac = float(flight_start)
365
406
  flight_end_frac = float(flight_end)
366
407
 
@@ -373,6 +414,20 @@ def _analyze_flight_phase(
373
414
  flight_start_frac = end_frac
374
415
  break
375
416
 
417
+ # Find interpolated landing (start of landing ON_GROUND phase)
418
+ for start_frac, _, state in interpolated_phases:
419
+ if state == ContactState.ON_GROUND and int(start_frac) >= flight_end - 2:
420
+ flight_end_frac = start_frac
421
+ break
422
+
423
+ # Refine landing frame using floor of interpolated value
424
+ # This compensates for velocity-based detection being ~1-2 frames late
425
+ refined_flight_end = int(np.floor(flight_end_frac))
426
+
427
+ # Store integer frame indices (refined using interpolated values)
428
+ metrics.flight_start_frame = flight_start
429
+ metrics.flight_end_frame = refined_flight_end
430
+
376
431
  # Calculate flight time
377
432
  flight_frames_precise = flight_end_frac - flight_start_frac
378
433
  metrics.flight_time = flight_frames_precise / fps
@@ -497,15 +552,22 @@ def calculate_drop_jump_metrics(
497
552
  phases, ground_phases, air_phases_indexed, foot_y_positions
498
553
  )
499
554
 
500
- # Store integer frame indices
501
- metrics.contact_start_frame = contact_start
502
- metrics.contact_end_frame = contact_end
503
-
504
- # Find precise timing for contact phase
555
+ # Find precise timing for contact phase (uses curvature refinement)
505
556
  contact_start_frac, contact_end_frac = _find_precise_phase_timing(
506
557
  contact_start, contact_end, interpolated_phases
507
558
  )
508
559
 
560
+ # Refine contact_start using floor of interpolated value
561
+ # This compensates for velocity-based detection being ~1-2 frames late
562
+ # because velocity settles AFTER initial impact. Using floor() biases
563
+ # toward earlier detection, matching the moment of first ground contact.
564
+ refined_contact_start = int(np.floor(contact_start_frac))
565
+
566
+ # Store integer frame indices (refined start, raw end)
567
+ # Contact end (takeoff) uses raw value as velocity-based detection is accurate
568
+ metrics.contact_start_frame = refined_contact_start
569
+ metrics.contact_end_frame = contact_end
570
+
509
571
  # Calculate ground contact time
510
572
  contact_frames_precise = contact_end_frac - contact_start_frac
511
573
  metrics.ground_contact_time = contact_frames_precise / fps
@@ -14,7 +14,7 @@ from kinemotion.core.validation import (
14
14
  MetricsValidator,
15
15
  ValidationResult,
16
16
  )
17
- from kinemotion.dropjump.validation_bounds import (
17
+ from kinemotion.dj.validation_bounds import (
18
18
  DropJumpBounds,
19
19
  estimate_athlete_profile,
20
20
  )
@@ -124,7 +124,7 @@ def _classify_combined_score(combined_score: float) -> AthleteProfile:
124
124
  return AthleteProfile.ELITE
125
125
 
126
126
 
127
- def estimate_athlete_profile(metrics: MetricsDict, gender: str | None = None) -> AthleteProfile:
127
+ def estimate_athlete_profile(metrics: MetricsDict, _gender: str | None = None) -> AthleteProfile:
128
128
  """Estimate athlete profile from drop jump metrics.
129
129
 
130
130
  Uses jump_height and contact_time to classify athlete level.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kinemotion
3
- Version: 0.71.0
3
+ Version: 0.72.0
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,50 @@
1
+ kinemotion/__init__.py,sha256=jlhHJlxZJbp7CPfRWMlxwfDISBCdHadjigmXvcahXxU,1055
2
+ kinemotion/api.py,sha256=1GMi_7SbU3EyDdDRtnF55roB8iMR0t91qGOkqa4E6sI,1028
3
+ kinemotion/cli.py,sha256=ugrc1Dpx7abYGEyIDcslzNu8KR0VoOoGGUGUm87E_kQ,620
4
+ kinemotion/cmj/__init__.py,sha256=SkAw9ka8Yd1Qfv9hcvk22m3EfucROzYrSNGNF5kDzho,113
5
+ kinemotion/cmj/analysis.py,sha256=EQydClIbNkIj-FmCZGaPQe-COVW8fbO3139i9z1vomA,23643
6
+ kinemotion/cmj/api.py,sha256=P_lbqEqAKPO5n1Xn4IQZKNj9nLaO3ljkN2PgqvExGXU,18435
7
+ kinemotion/cmj/cli.py,sha256=P2b77IIw6kqTSIkncxlShzhmjIwqMFBNd-pZxYP-TsI,9918
8
+ kinemotion/cmj/debug_overlay.py,sha256=vF5Apiz8zDRpgrVzf52manLW99m1kHQAPSdUkar5rPs,11474
9
+ kinemotion/cmj/joint_angles.py,sha256=by5M4LDtUfd2_Z9DmcgUl0nsvarsBYjgsE8KWWYcn08,11255
10
+ kinemotion/cmj/kinematics.py,sha256=KwA8uSj3g1SeNf0NXMSHsp3gIw6Gfa-6QWIwdYdRXYw,13362
11
+ kinemotion/cmj/metrics_validator.py,sha256=IQofafpwLCXER3ucZXNfiJKFFKPOVxXnC4BNLHOMnNY,30013
12
+ kinemotion/cmj/validation_bounds.py,sha256=-0iXDhH-RntiGZi_Co22V6qtA5D-hLzkrPkVcfoNd2U,11343
13
+ kinemotion/core/__init__.py,sha256=8hMvfNK7v_eqswuk_J5s5FRGvPtp2-R4kasVMGchFkM,1766
14
+ kinemotion/core/auto_tuning.py,sha256=rliPTLueMbOjYRb4hjb0af7DVMtxLT92wpnVve75GvA,10478
15
+ kinemotion/core/cli_utils.py,sha256=sQPbT6XWWau-sm9yuN5c3eS5xNzoQGGXwSz6hQXtRvM,1859
16
+ kinemotion/core/debug_overlay_utils.py,sha256=QaVkHuFZpXUrdiMlm8ylQn6baJOj8jcZeiV4kDqODt0,17441
17
+ kinemotion/core/determinism.py,sha256=Frw-KAOvAxTL_XtxoWpXCjMbQPUKEAusK6JctlkeuRo,2509
18
+ kinemotion/core/experimental.py,sha256=G1EpkmWQ8d-rPaN1n0P7mF6XUzrbW0Br3nVkIzJ1D9M,3694
19
+ kinemotion/core/filtering.py,sha256=7KUeclXqZpNQA8WKNocDwhCxZpwwtizI3wvAEyq9SBo,11603
20
+ kinemotion/core/formatting.py,sha256=G_3eqgOtym9RFOZVEwCxye4A2cyrmgvtQ214vIshowU,2480
21
+ kinemotion/core/metadata.py,sha256=bJAVa4nym__zx1hNowSZduMGKBSGOPxTbBQkjm6N0D0,7207
22
+ kinemotion/core/model_downloader.py,sha256=mqhJBHGaNe0aN9qbcBqvcTk9FDd7xaHqEcwD-fyP89c,5205
23
+ kinemotion/core/overlay_constants.py,sha256=zZreHHWe00p2XuCJsbRFqN6g-AAUAnx53LwKqHm1Bl8,1438
24
+ kinemotion/core/pipeline_utils.py,sha256=FzfdKNhM0eK9Y5wbNP9Jab_nmrZxcJfL3cstpO4yfxc,15155
25
+ kinemotion/core/pose.py,sha256=Z795p0EnaTUeWHO8FuApFcMGTLwZ47JOjs5f5TzRvdk,14224
26
+ kinemotion/core/pose_landmarks.py,sha256=LcEbL5K5xKia6dCzWf6Ft18UIE1CLMMqCZ3KUjwUDzM,1558
27
+ kinemotion/core/quality.py,sha256=VUkRL2N6B7lfIZ2pE9han_U68JwarmZz1U0ygHkgkhE,13022
28
+ kinemotion/core/smoothing.py,sha256=F1DCsnvPBi62XJLygOJ5MkNlRa7BCLg_E9ORtCWcoKk,16562
29
+ kinemotion/core/timing.py,sha256=ITX77q4hbtajRuWfgwYhws8nCvOeKFlEdKjCu8lD9_w,7938
30
+ kinemotion/core/types.py,sha256=m141buSkEsqflt5VFaTHtRq_IcimjI3_T_EfaNpIVxY,1652
31
+ kinemotion/core/validation.py,sha256=rrhpI24Iq8WGtNaMg0beTWMbEGccdKF-f-pk-FCKJzI,6749
32
+ kinemotion/core/video_io.py,sha256=84IxC1n3HvYK28MSa5fqumdzlPDhP8k9IPB3OCvWku0,9198
33
+ kinemotion/dj/__init__.py,sha256=yBbEbPdY6sqozWtTvfbvuUZnrVWSSjBp61xK34M29F4,878
34
+ kinemotion/dj/analysis.py,sha256=dR5Dqxo_ub9EAOR95oPI4oJKtIofSH0EodopuoywsO8,33339
35
+ kinemotion/dj/api.py,sha256=v-T-VurOoOIAWVyfR5IUCnUc4bHjBuxB2pP8qJG7TLs,20799
36
+ kinemotion/dj/cli.py,sha256=FaBX637x7VcLcB8HupaZCkVS7sp8C0YuaKM0h-DBNIA,15906
37
+ kinemotion/dj/debug_overlay.py,sha256=X4mvCi5Qi1gnvSZZAsUs-0ZRUx9mVBbEUznOFO21HO8,8470
38
+ kinemotion/dj/kinematics.py,sha256=1K291z-PeJTqJbJDeIKWat90mVbxxh4B4hjz-nTFk88,22618
39
+ kinemotion/dj/metrics_validator.py,sha256=BZbqareRaIfCcehTUvNPO3xzkq4X27xDD867e_w7Fmo,9237
40
+ kinemotion/dj/validation_bounds.py,sha256=k31qy-kCXTiCTx0RPo2t8yZ-faLxqGO-AeF05QfBFb0,5125
41
+ kinemotion/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
+ kinemotion/models/pose_landmarker_lite.task,sha256=WZKeHR7pUodzXd2DOxnPSsRtKbx6_du_Z1PEWWkNV0o,5777746
43
+ kinemotion/models/rtmpose-s_simcc-body7_pt-body7-halpe26_700e-256x192-7f134165_20230605.onnx,sha256=dfZTq8kbhv8RxWiXS0HUIJNCUpxYTBN45dFIorPflEs,133
44
+ kinemotion/models/yolox_tiny_8xb8-300e_humanart-6f3252f9.onnx,sha256=UsutHVQ6GP3X5pCcp52EN8q7o2J3d-TnxZqlF48kY6I,133
45
+ kinemotion/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
+ kinemotion-0.72.0.dist-info/METADATA,sha256=_OdCWsVJMVu8s2tebWVeW5XnFTi_aMZYVTw01XWN_p0,26125
47
+ kinemotion-0.72.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
48
+ kinemotion-0.72.0.dist-info/entry_points.txt,sha256=zaqnAnjLvcdrk1Qvj5nvXZCZ2gp0prS7it1zTJygcIY,50
49
+ kinemotion-0.72.0.dist-info/licenses/LICENSE,sha256=KZajvqsHw0NoOHOi2q0FZ4NBe9HdV6oey-IPYAtHXfg,1088
50
+ kinemotion-0.72.0.dist-info/RECORD,,
@@ -1,182 +0,0 @@
1
- """Debug overlay rendering for drop jump analysis."""
2
-
3
- import cv2
4
- import numpy as np
5
-
6
- from ..core.debug_overlay_utils import BaseDebugOverlayRenderer
7
- from ..core.pose import compute_center_of_mass
8
- from .analysis import ContactState, compute_average_foot_position
9
- from .kinematics import DropJumpMetrics
10
-
11
-
12
- class DebugOverlayRenderer(BaseDebugOverlayRenderer):
13
- """Renders debug information on video frames."""
14
-
15
- def _draw_com_visualization(
16
- self,
17
- frame: np.ndarray,
18
- landmarks: dict[str, tuple[float, float, float]],
19
- contact_state: ContactState,
20
- ) -> None:
21
- """Draw center of mass visualization on frame."""
22
- com_x, com_y, _ = compute_center_of_mass(landmarks)
23
- px = int(com_x * self.width)
24
- py = int(com_y * self.height)
25
-
26
- color = (0, 255, 0) if contact_state == ContactState.ON_GROUND else (0, 0, 255)
27
- cv2.circle(frame, (px, py), 15, color, -1)
28
- cv2.circle(frame, (px, py), 17, (255, 255, 255), 2)
29
-
30
- # Draw hip midpoint reference
31
- if "left_hip" in landmarks and "right_hip" in landmarks:
32
- lh_x, lh_y, _ = landmarks["left_hip"]
33
- rh_x, rh_y, _ = landmarks["right_hip"]
34
- hip_x = int((lh_x + rh_x) / 2 * self.width)
35
- hip_y = int((lh_y + rh_y) / 2 * self.height)
36
- cv2.circle(frame, (hip_x, hip_y), 8, (255, 165, 0), -1)
37
- cv2.line(frame, (hip_x, hip_y), (px, py), (255, 165, 0), 2)
38
-
39
- def _draw_foot_visualization(
40
- self,
41
- frame: np.ndarray,
42
- landmarks: dict[str, tuple[float, float, float]],
43
- contact_state: ContactState,
44
- ) -> None:
45
- """Draw foot position visualization on frame."""
46
- foot_x, foot_y = compute_average_foot_position(landmarks)
47
- px = int(foot_x * self.width)
48
- py = int(foot_y * self.height)
49
-
50
- color = (0, 255, 0) if contact_state == ContactState.ON_GROUND else (0, 0, 255)
51
- cv2.circle(frame, (px, py), 10, color, -1)
52
-
53
- # Draw individual foot landmarks
54
- foot_keys = ["left_ankle", "right_ankle", "left_heel", "right_heel"]
55
- for key in foot_keys:
56
- if key in landmarks:
57
- x, y, vis = landmarks[key]
58
- if vis > 0.5:
59
- lx = int(x * self.width)
60
- ly = int(y * self.height)
61
- cv2.circle(frame, (lx, ly), 5, (255, 255, 0), -1)
62
-
63
- def _draw_phase_labels(
64
- self,
65
- frame: np.ndarray,
66
- frame_idx: int,
67
- metrics: DropJumpMetrics,
68
- ) -> None:
69
- """Draw phase labels (ground contact, flight, peak) on frame."""
70
- y_offset = 110
71
-
72
- # Ground contact phase
73
- if (
74
- metrics.contact_start_frame
75
- and metrics.contact_end_frame
76
- and metrics.contact_start_frame <= frame_idx <= metrics.contact_end_frame
77
- ):
78
- cv2.putText(
79
- frame,
80
- "GROUND CONTACT",
81
- (10, y_offset),
82
- cv2.FONT_HERSHEY_SIMPLEX,
83
- 0.7,
84
- (0, 255, 0),
85
- 2,
86
- )
87
- y_offset += 40
88
-
89
- # Flight phase
90
- if (
91
- metrics.flight_start_frame
92
- and metrics.flight_end_frame
93
- and metrics.flight_start_frame <= frame_idx <= metrics.flight_end_frame
94
- ):
95
- cv2.putText(
96
- frame,
97
- "FLIGHT PHASE",
98
- (10, y_offset),
99
- cv2.FONT_HERSHEY_SIMPLEX,
100
- 0.7,
101
- (0, 0, 255),
102
- 2,
103
- )
104
- y_offset += 40
105
-
106
- # Peak height
107
- if metrics.peak_height_frame == frame_idx:
108
- cv2.putText(
109
- frame,
110
- "PEAK HEIGHT",
111
- (10, y_offset),
112
- cv2.FONT_HERSHEY_SIMPLEX,
113
- 0.7,
114
- (255, 0, 255),
115
- 2,
116
- )
117
-
118
- def render_frame(
119
- self,
120
- frame: np.ndarray,
121
- landmarks: dict[str, tuple[float, float, float]] | None,
122
- contact_state: ContactState,
123
- frame_idx: int,
124
- metrics: DropJumpMetrics | None = None,
125
- use_com: bool = False,
126
- ) -> np.ndarray:
127
- """
128
- Render debug overlay on frame.
129
-
130
- Args:
131
- frame: Original video frame
132
- landmarks: Pose landmarks for this frame
133
- contact_state: Ground contact state
134
- frame_idx: Current frame index
135
- metrics: Drop-jump metrics (optional)
136
- use_com: Whether to visualize CoM instead of feet (optional)
137
-
138
- Returns:
139
- Frame with debug overlay
140
- """
141
- with self.timer.measure("debug_video_copy"):
142
- annotated = frame.copy()
143
-
144
- def _draw_overlays() -> None:
145
- # Draw landmarks
146
- if landmarks:
147
- if use_com:
148
- self._draw_com_visualization(annotated, landmarks, contact_state)
149
- else:
150
- self._draw_foot_visualization(annotated, landmarks, contact_state)
151
-
152
- # Draw contact state
153
- state_color = (0, 255, 0) if contact_state == ContactState.ON_GROUND else (0, 0, 255)
154
- cv2.putText(
155
- annotated,
156
- f"State: {contact_state.value}",
157
- (10, 30),
158
- cv2.FONT_HERSHEY_SIMPLEX,
159
- 1,
160
- state_color,
161
- 2,
162
- )
163
-
164
- # Draw frame number
165
- cv2.putText(
166
- annotated,
167
- f"Frame: {frame_idx}",
168
- (10, 70),
169
- cv2.FONT_HERSHEY_SIMPLEX,
170
- 0.7,
171
- (255, 255, 255),
172
- 2,
173
- )
174
-
175
- # Draw phase labels
176
- if metrics:
177
- self._draw_phase_labels(annotated, frame_idx, metrics)
178
-
179
- with self.timer.measure("debug_video_draw"):
180
- _draw_overlays()
181
-
182
- return annotated
@@ -1,49 +0,0 @@
1
- kinemotion/__init__.py,sha256=HkD8habCcfxGobxZcACOStla-L1nYHMIZp0th00Q3E8,1061
2
- kinemotion/api.py,sha256=uG1e4bTnj2c-6cbZJEZ_LjMwFdaG32ba2KcK_XjE_NI,1040
3
- kinemotion/cli.py,sha256=_Us9krSce4GUKtlLIPrFUhKmPWURzeJ1-ydR_YU2VGw,626
4
- kinemotion/cmj/__init__.py,sha256=SkAw9ka8Yd1Qfv9hcvk22m3EfucROzYrSNGNF5kDzho,113
5
- kinemotion/cmj/analysis.py,sha256=WcFNJVd9zpwvDrbe41VshXqP9MFfptlgYD4Nph5mHLA,23675
6
- kinemotion/cmj/api.py,sha256=5PDV_vX3k63Ko4OEttyUkV4fWyklyy-CG_UyNKgNoyY,18476
7
- kinemotion/cmj/cli.py,sha256=P2b77IIw6kqTSIkncxlShzhmjIwqMFBNd-pZxYP-TsI,9918
8
- kinemotion/cmj/debug_overlay.py,sha256=bX9aPLhXiLCCMZW9v8Y4OiOAaZO0i-UGr-Pl8HCsmbI,15810
9
- kinemotion/cmj/joint_angles.py,sha256=HmheIEiKcQz39cRezk4h-htorOhGNPsqKIR9RsAEKts,9960
10
- kinemotion/cmj/kinematics.py,sha256=KwA8uSj3g1SeNf0NXMSHsp3gIw6Gfa-6QWIwdYdRXYw,13362
11
- kinemotion/cmj/metrics_validator.py,sha256=JWuWFfDXyZMTHXFWVdI0MhaQjMR3cjd01tTrDy_if2U,30290
12
- kinemotion/cmj/validation_bounds.py,sha256=Ry915JdInPXbqjaVGNY_urnDO1PAkCSJqHwNKRq-VkU,12048
13
- kinemotion/core/__init__.py,sha256=8WB7tAJPKOxgNzbhIEOnGnkRr0CcdNeTnz91Jsiyafo,1812
14
- kinemotion/core/auto_tuning.py,sha256=lhAqPc-eLjMYx9BCvKdECE7TD2Dweb9KcifV6JHaXOE,11278
15
- kinemotion/core/cli_utils.py,sha256=sQPbT6XWWau-sm9yuN5c3eS5xNzoQGGXwSz6hQXtRvM,1859
16
- kinemotion/core/debug_overlay_utils.py,sha256=D4aT8xstThPcV2i5D4KJZJEttW6E_4GE5QiERqe1MwI,13049
17
- kinemotion/core/determinism.py,sha256=Frw-KAOvAxTL_XtxoWpXCjMbQPUKEAusK6JctlkeuRo,2509
18
- kinemotion/core/experimental.py,sha256=IK05AF4aZS15ke85hF3TWCqRIXU1AlD_XKzFz735Ua8,3640
19
- kinemotion/core/filtering.py,sha256=Oc__pV6iHEGyyovbqa5SUi-6v8QyvaRVwA0LRayM884,11355
20
- kinemotion/core/formatting.py,sha256=G_3eqgOtym9RFOZVEwCxye4A2cyrmgvtQ214vIshowU,2480
21
- kinemotion/core/metadata.py,sha256=bJAVa4nym__zx1hNowSZduMGKBSGOPxTbBQkjm6N0D0,7207
22
- kinemotion/core/model_downloader.py,sha256=mqhJBHGaNe0aN9qbcBqvcTk9FDd7xaHqEcwD-fyP89c,5205
23
- kinemotion/core/pipeline_utils.py,sha256=B5jMXoiLaTh02uGA2MIe1uZLVSRGZ5nxbARuvdrjDrQ,15161
24
- kinemotion/core/pose.py,sha256=_qC4cbFD0Mp2JAGftZcY5AEDLgD2yRnTyRKD9bkqLI8,15306
25
- kinemotion/core/pose_landmarks.py,sha256=LcEbL5K5xKia6dCzWf6Ft18UIE1CLMMqCZ3KUjwUDzM,1558
26
- kinemotion/core/quality.py,sha256=VUkRL2N6B7lfIZ2pE9han_U68JwarmZz1U0ygHkgkhE,13022
27
- kinemotion/core/smoothing.py,sha256=ELMHL7pzSqYffjnLDBUMBJIgt1AwOssDInE8IiXBbig,15942
28
- kinemotion/core/timing.py,sha256=ITX77q4hbtajRuWfgwYhws8nCvOeKFlEdKjCu8lD9_w,7938
29
- kinemotion/core/types.py,sha256=A_HclzKpf3By5DiJ0wY9B-dQJrIVAAhUfGab7qTSIL8,1279
30
- kinemotion/core/validation.py,sha256=0xVv-ftWveV60fJ97kmZMuy2Qqqb5aZLR50dDIrjnhg,6773
31
- kinemotion/core/video_io.py,sha256=TxdLUEpekGytesL3X3k78WWgZTOd5fuge30hU4Uy48Y,9198
32
- kinemotion/dropjump/__init__.py,sha256=tC3H3BrCg8Oj-db-Vrtx4PH_llR1Ppkd5jwaOjhQcLg,862
33
- kinemotion/dropjump/analysis.py,sha256=YomuoJF_peyrBSpeT89Q5_sBgY0kEDyq7TFrtEnRLjs,28049
34
- kinemotion/dropjump/api.py,sha256=QlZxCrjOg78PXXip6Krb91RSYqH37x1AbBUy6U8uFt8,20833
35
- kinemotion/dropjump/cli.py,sha256=gUef9nmyR5952h1WnfBGyCdFXQvzVTlCKYAjJGcO4sE,16819
36
- kinemotion/dropjump/debug_overlay.py,sha256=9RQYXPRf0q2wdy6y2Ak2R4tpRceDwC8aJrXZzkmh3Wo,5942
37
- kinemotion/dropjump/kinematics.py,sha256=dx4PuXKfKMKcsc_HX6sXj8rHXf9ksiZIOAIkJ4vBlY4,19637
38
- kinemotion/dropjump/metrics_validator.py,sha256=lSfo4Lm5FHccl8ijUP6SA-kcSh50LS9hF8UIyWxcnW8,9243
39
- kinemotion/dropjump/validation_bounds.py,sha256=x4yjcFxyvdMp5e7MkcoUosGLeGsxBh1Lft6h__AQ2G8,5124
40
- kinemotion/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
- kinemotion/models/pose_landmarker_lite.task,sha256=WZKeHR7pUodzXd2DOxnPSsRtKbx6_du_Z1PEWWkNV0o,5777746
42
- kinemotion/models/rtmpose-s_simcc-body7_pt-body7-halpe26_700e-256x192-7f134165_20230605.onnx,sha256=dfZTq8kbhv8RxWiXS0HUIJNCUpxYTBN45dFIorPflEs,133
43
- kinemotion/models/yolox_tiny_8xb8-300e_humanart-6f3252f9.onnx,sha256=UsutHVQ6GP3X5pCcp52EN8q7o2J3d-TnxZqlF48kY6I,133
44
- kinemotion/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
- kinemotion-0.71.0.dist-info/METADATA,sha256=sKhmkomkzrmp1YnX0SC9dzsRvDNlrVtVQ5npxSPWVeI,26125
46
- kinemotion-0.71.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
47
- kinemotion-0.71.0.dist-info/entry_points.txt,sha256=zaqnAnjLvcdrk1Qvj5nvXZCZ2gp0prS7it1zTJygcIY,50
48
- kinemotion-0.71.0.dist-info/licenses/LICENSE,sha256=KZajvqsHw0NoOHOi2q0FZ4NBe9HdV6oey-IPYAtHXfg,1088
49
- kinemotion-0.71.0.dist-info/RECORD,,