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.
- kinemotion/api.py +0 -2
- kinemotion/core/auto_tuning.py +66 -30
- kinemotion/core/pose.py +134 -95
- kinemotion/core/smoothing.py +2 -2
- kinemotion/core/video_io.py +53 -29
- kinemotion/dropjump/analysis.py +205 -123
- kinemotion/dropjump/cli.py +60 -57
- kinemotion/dropjump/debug_overlay.py +109 -97
- kinemotion/dropjump/kinematics.py +3 -6
- {kinemotion-0.12.1.dist-info → kinemotion-0.12.3.dist-info}/METADATA +1 -1
- {kinemotion-0.12.1.dist-info → kinemotion-0.12.3.dist-info}/RECORD +14 -14
- {kinemotion-0.12.1.dist-info → kinemotion-0.12.3.dist-info}/WHEEL +0 -0
- {kinemotion-0.12.1.dist-info → kinemotion-0.12.3.dist-info}/entry_points.txt +0 -0
- {kinemotion-0.12.1.dist-info → kinemotion-0.12.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -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
|
|
143
|
+
# Draw landmarks
|
|
41
144
|
if landmarks:
|
|
42
145
|
if use_com:
|
|
43
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
175
|
+
# Draw phase labels
|
|
120
176
|
if metrics:
|
|
121
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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=
|
|
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=
|
|
16
|
-
kinemotion/core/smoothing.py,sha256=
|
|
17
|
-
kinemotion/core/video_io.py,sha256=
|
|
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=
|
|
20
|
-
kinemotion/dropjump/cli.py,sha256=
|
|
21
|
-
kinemotion/dropjump/debug_overlay.py,sha256=
|
|
22
|
-
kinemotion/dropjump/kinematics.py,sha256=
|
|
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.
|
|
25
|
-
kinemotion-0.12.
|
|
26
|
-
kinemotion-0.12.
|
|
27
|
-
kinemotion-0.12.
|
|
28
|
-
kinemotion-0.12.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|