kinemotion 0.12.1__py3-none-any.whl → 0.12.3__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.

@@ -12,6 +12,109 @@ from .kinematics import DropJumpMetrics
12
12
  class DebugOverlayRenderer(BaseDebugOverlayRenderer):
13
13
  """Renders debug information on video frames."""
14
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
+
15
118
  def render_frame(
16
119
  self,
17
120
  frame: np.ndarray,
@@ -37,67 +140,20 @@ class DebugOverlayRenderer(BaseDebugOverlayRenderer):
37
140
  """
38
141
  annotated = frame.copy()
39
142
 
40
- # Draw landmarks if available
143
+ # Draw landmarks
41
144
  if landmarks:
42
145
  if use_com:
43
- # Draw center of mass position
44
- com_x, com_y, _ = compute_center_of_mass(landmarks) # com_vis not used
45
- px = int(com_x * self.width)
46
- py = int(com_y * self.height)
47
-
48
- # Draw CoM with larger circle
49
- color = (
50
- (0, 255, 0)
51
- if contact_state == ContactState.ON_GROUND
52
- else (0, 0, 255)
53
- )
54
- cv2.circle(annotated, (px, py), 15, color, -1)
55
- cv2.circle(annotated, (px, py), 17, (255, 255, 255), 2) # White border
56
-
57
- # Draw body segments for reference
58
- # Draw hip midpoint
59
- if "left_hip" in landmarks and "right_hip" in landmarks:
60
- lh_x, lh_y, _ = landmarks["left_hip"]
61
- rh_x, rh_y, _ = landmarks["right_hip"]
62
- hip_x = int((lh_x + rh_x) / 2 * self.width)
63
- hip_y = int((lh_y + rh_y) / 2 * self.height)
64
- cv2.circle(
65
- annotated, (hip_x, hip_y), 8, (255, 165, 0), -1
66
- ) # Orange
67
- # Draw line from hip to CoM
68
- cv2.line(annotated, (hip_x, hip_y), (px, py), (255, 165, 0), 2)
146
+ self._draw_com_visualization(annotated, landmarks, contact_state)
69
147
  else:
70
- # Draw foot position (original method)
71
- foot_x, foot_y = compute_average_foot_position(landmarks)
72
- px = int(foot_x * self.width)
73
- py = int(foot_y * self.height)
74
-
75
- # Draw foot position circle
76
- color = (
77
- (0, 255, 0)
78
- if contact_state == ContactState.ON_GROUND
79
- else (0, 0, 255)
80
- )
81
- cv2.circle(annotated, (px, py), 10, color, -1)
82
-
83
- # Draw individual foot landmarks
84
- foot_keys = ["left_ankle", "right_ankle", "left_heel", "right_heel"]
85
- for key in foot_keys:
86
- if key in landmarks:
87
- x, y, vis = landmarks[key]
88
- if vis > 0.5:
89
- lx = int(x * self.width)
90
- ly = int(y * self.height)
91
- cv2.circle(annotated, (lx, ly), 5, (255, 255, 0), -1)
148
+ self._draw_foot_visualization(annotated, landmarks, contact_state)
92
149
 
93
150
  # Draw contact state
94
- state_text = f"State: {contact_state.value}"
95
151
  state_color = (
96
152
  (0, 255, 0) if contact_state == ContactState.ON_GROUND else (0, 0, 255)
97
153
  )
98
154
  cv2.putText(
99
155
  annotated,
100
- state_text,
156
+ f"State: {contact_state.value}",
101
157
  (10, 30),
102
158
  cv2.FONT_HERSHEY_SIMPLEX,
103
159
  1,
@@ -116,52 +172,8 @@ class DebugOverlayRenderer(BaseDebugOverlayRenderer):
116
172
  2,
117
173
  )
118
174
 
119
- # Draw metrics if in relevant phase
175
+ # Draw phase labels
120
176
  if metrics:
121
- y_offset = 110
122
- if (
123
- metrics.contact_start_frame
124
- and metrics.contact_end_frame
125
- and metrics.contact_start_frame
126
- <= frame_idx
127
- <= metrics.contact_end_frame
128
- ):
129
- cv2.putText(
130
- annotated,
131
- "GROUND CONTACT",
132
- (10, y_offset),
133
- cv2.FONT_HERSHEY_SIMPLEX,
134
- 0.7,
135
- (0, 255, 0),
136
- 2,
137
- )
138
- y_offset += 40
139
-
140
- if (
141
- metrics.flight_start_frame
142
- and metrics.flight_end_frame
143
- and metrics.flight_start_frame <= frame_idx <= metrics.flight_end_frame
144
- ):
145
- cv2.putText(
146
- annotated,
147
- "FLIGHT PHASE",
148
- (10, y_offset),
149
- cv2.FONT_HERSHEY_SIMPLEX,
150
- 0.7,
151
- (0, 0, 255),
152
- 2,
153
- )
154
- y_offset += 40
155
-
156
- if metrics.peak_height_frame == frame_idx:
157
- cv2.putText(
158
- annotated,
159
- "PEAK HEIGHT",
160
- (10, y_offset),
161
- cv2.FONT_HERSHEY_SIMPLEX,
162
- 0.7,
163
- (255, 0, 255),
164
- 2,
165
- )
177
+ self._draw_phase_labels(annotated, frame_idx, metrics)
166
178
 
167
179
  return annotated
@@ -343,29 +343,26 @@ def calculate_drop_jump_metrics(
343
343
  contact_states: list[ContactState],
344
344
  foot_y_positions: np.ndarray,
345
345
  fps: float,
346
- drop_height_m: float | None = None,
347
346
  drop_start_frame: int | None = None,
348
347
  velocity_threshold: float = 0.02,
349
348
  smoothing_window: int = 5,
350
349
  polyorder: int = 2,
351
350
  use_curvature: bool = True,
352
- kinematic_correction_factor: float = 1.0,
353
351
  ) -> DropJumpMetrics:
354
352
  """
355
353
  Calculate drop-jump metrics from contact states and positions.
356
354
 
355
+ Jump height is calculated from flight time using kinematic formula: h = g × t² / 8
356
+
357
357
  Args:
358
358
  contact_states: Contact state for each frame
359
359
  foot_y_positions: Vertical positions of feet (normalized 0-1)
360
360
  fps: Video frame rate
361
- drop_height_m: Known drop box/platform height in meters for calibration (optional)
361
+ drop_start_frame: Optional manual drop start frame
362
362
  velocity_threshold: Velocity threshold used for contact detection (for interpolation)
363
363
  smoothing_window: Window size for velocity/acceleration smoothing (must be odd)
364
364
  polyorder: Polynomial order for Savitzky-Golay filter (default: 2)
365
365
  use_curvature: Whether to use curvature analysis for refining transitions
366
- kinematic_correction_factor: Correction factor for kinematic jump height calculation
367
- (default: 1.0 = no correction). Historical testing suggested 1.35, but this is
368
- unvalidated. Use calibrated measurement (--drop-height) for validated results.
369
366
 
370
367
  Returns:
371
368
  DropJumpMetrics object with calculated values
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kinemotion
3
- Version: 0.12.1
3
+ Version: 0.12.3
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
@@ -1,5 +1,5 @@
1
1
  kinemotion/__init__.py,sha256=REBC9wrwYC_grvCS00qEOyign65Zc1sc-5buLpyqQxA,654
2
- kinemotion/api.py,sha256=-m4KnlpXWswNyHc_keuNtKZKy8EONSUqrSTNtvoq_OA,31193
2
+ kinemotion/api.py,sha256=jcUVn8UHysj8GNzdNShf3y2pp20G4iTBb1jvkpptUvU,31116
3
3
  kinemotion/cli.py,sha256=cqYV_7URH0JUDy1VQ_EDLv63FmNO4Ns20m6s1XAjiP4,464
4
4
  kinemotion/cmj/__init__.py,sha256=Ynv0-Oco4I3Y1Ubj25m3h9h2XFqeNwpAewXmAYOmwfU,127
5
5
  kinemotion/cmj/analysis.py,sha256=4HYGn4VDIB6oExAees-VcPfpNgWOltpgwjyNTU7YAb4,18263
@@ -8,21 +8,21 @@ kinemotion/cmj/debug_overlay.py,sha256=D-y2FQKI01KY0WXFKTKg6p9Qj3AkXCE7xjau3Ais0
8
8
  kinemotion/cmj/joint_angles.py,sha256=8ucpDGPvbt4iX3tx9eVxJEUv0laTm2Y58_--VzJCogE,9113
9
9
  kinemotion/cmj/kinematics.py,sha256=Xl_PlC2OqMoA-zOc3SRB_GqI0AgLlJol5FTPe5J_qLc,7573
10
10
  kinemotion/core/__init__.py,sha256=3yzDhb5PekDNjydqrs8aWGneUGJBt-lB0SoB_Y2FXqU,1010
11
- kinemotion/core/auto_tuning.py,sha256=cvmxUI-CbahpOJQtR2r5jOx4Q6yKPe3DO1o15hOQIdw,10508
11
+ kinemotion/core/auto_tuning.py,sha256=j6cul_qC6k0XyryCG93C1AWH2MKPj3UBMzuX02xaqfI,11235
12
12
  kinemotion/core/cli_utils.py,sha256=Pq1JF7yvK1YbH0tOUWKjplthCbWsJQt4Lv7esPYH4FM,7254
13
13
  kinemotion/core/debug_overlay_utils.py,sha256=TyUb5okv5qw8oeaX3jsUO_kpwf1NnaHEAOTm-8LwTno,4587
14
14
  kinemotion/core/filtering.py,sha256=f-m-aA59e4WqE6u-9MA51wssu7rI-Y_7n1cG8IWdeRQ,11241
15
- kinemotion/core/pose.py,sha256=Wfd1RR-2ZznYpWeQUbySwcV3mvReqn8n3XO6S7pGq4M,8390
16
- kinemotion/core/smoothing.py,sha256=Zdhqw4NyCrZaEb-Jo3sASzP-QlEL5sVTgHoXU8zT_xU,14136
17
- kinemotion/core/video_io.py,sha256=UtmUndw22uFnZBK_BmeE912yRYH1YnU_P8LjuN33DPc,6461
15
+ kinemotion/core/pose.py,sha256=ztemdZ_ysVVK3gbXabm8qS_dr1VfJX9KZjmcO-Z-iNE,8532
16
+ kinemotion/core/smoothing.py,sha256=C9GK3PAN16RpqJw2UWeVslSTJZEvALeVADjtnJnSF88,14240
17
+ kinemotion/core/video_io.py,sha256=kH5FYPx3y3lFZ3ybdgxaZfKPdHJ37eqxSeAaZjyQnJk,6817
18
18
  kinemotion/dropjump/__init__.py,sha256=yc1XiZ9vfo5h_n7PKVSiX2TTgaIfGL7Y7SkQtiDZj_E,838
19
- kinemotion/dropjump/analysis.py,sha256=crO0SUq8TiMHdK5hPuHHuOFrEGhGdPoeb5rXQUvqCog,26103
20
- kinemotion/dropjump/cli.py,sha256=emnMlg2Td4iS7go9ckTFnolPEytX9MKoPRhfjBwyArU,21731
21
- kinemotion/dropjump/debug_overlay.py,sha256=2L4VAZwWFnaOQ7LAF3ALXCjEaVNzkfpLT5-h0qKL_6g,5707
22
- kinemotion/dropjump/kinematics.py,sha256=WC1HuIKx3CfEg9m9jFME74IAHSJRcbqo2_yysZIBqJw,15880
19
+ kinemotion/dropjump/analysis.py,sha256=xx5NWy6s0eb9BEyO_FByY1Ahunaoh3TyaTAxjlPrvxg,27153
20
+ kinemotion/dropjump/cli.py,sha256=Oni7gntysA6Zwb_ehsAnk6Ytd2ofUhN0yXVCsCsiris,21196
21
+ kinemotion/dropjump/debug_overlay.py,sha256=LkPw6ucb7beoYWS4L-Lvjs1KLCm5wAWDAfiznUeV2IQ,5668
22
+ kinemotion/dropjump/kinematics.py,sha256=txDxpDti3VJVctWGbe3aIrlIx83UY8-ynzlX01TOvTA,15577
23
23
  kinemotion/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
- kinemotion-0.12.1.dist-info/METADATA,sha256=50i4pFgm8p1uND7dmF4WhjQu5FL0p60qeARx29b39QY,18990
25
- kinemotion-0.12.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
26
- kinemotion-0.12.1.dist-info/entry_points.txt,sha256=zaqnAnjLvcdrk1Qvj5nvXZCZ2gp0prS7it1zTJygcIY,50
27
- kinemotion-0.12.1.dist-info/licenses/LICENSE,sha256=KZajvqsHw0NoOHOi2q0FZ4NBe9HdV6oey-IPYAtHXfg,1088
28
- kinemotion-0.12.1.dist-info/RECORD,,
24
+ kinemotion-0.12.3.dist-info/METADATA,sha256=rbD5mEdFYxRAlAbmaobBcrnMaFh0mFd2L3GyHipRlGY,18990
25
+ kinemotion-0.12.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
26
+ kinemotion-0.12.3.dist-info/entry_points.txt,sha256=zaqnAnjLvcdrk1Qvj5nvXZCZ2gp0prS7it1zTJygcIY,50
27
+ kinemotion-0.12.3.dist-info/licenses/LICENSE,sha256=KZajvqsHw0NoOHOi2q0FZ4NBe9HdV6oey-IPYAtHXfg,1088
28
+ kinemotion-0.12.3.dist-info/RECORD,,