kinemotion 0.76.3__py3-none-any.whl → 1.0.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/__init__.py +3 -18
- kinemotion/api.py +7 -27
- kinemotion/cli.py +2 -4
- kinemotion/{countermovement_jump → cmj}/analysis.py +158 -16
- kinemotion/{countermovement_jump → cmj}/api.py +18 -46
- kinemotion/{countermovement_jump → cmj}/cli.py +46 -6
- kinemotion/cmj/debug_overlay.py +457 -0
- kinemotion/{countermovement_jump → cmj}/joint_angles.py +31 -96
- kinemotion/{countermovement_jump → cmj}/metrics_validator.py +293 -184
- kinemotion/{countermovement_jump → cmj}/validation_bounds.py +18 -1
- kinemotion/core/__init__.py +2 -11
- kinemotion/core/auto_tuning.py +107 -149
- kinemotion/core/cli_utils.py +0 -74
- kinemotion/core/debug_overlay_utils.py +15 -142
- kinemotion/core/experimental.py +51 -55
- kinemotion/core/filtering.py +56 -116
- kinemotion/core/pipeline_utils.py +2 -2
- kinemotion/core/pose.py +98 -47
- kinemotion/core/quality.py +6 -4
- kinemotion/core/smoothing.py +51 -65
- kinemotion/core/types.py +0 -15
- kinemotion/core/validation.py +7 -76
- kinemotion/core/video_io.py +27 -41
- kinemotion/{drop_jump → dropjump}/__init__.py +8 -2
- kinemotion/{drop_jump → dropjump}/analysis.py +120 -282
- kinemotion/{drop_jump → dropjump}/api.py +33 -59
- kinemotion/{drop_jump → dropjump}/cli.py +136 -70
- kinemotion/dropjump/debug_overlay.py +182 -0
- kinemotion/{drop_jump → dropjump}/kinematics.py +65 -175
- kinemotion/{drop_jump → dropjump}/metrics_validator.py +51 -25
- kinemotion/{drop_jump → dropjump}/validation_bounds.py +1 -1
- kinemotion/models/rtmpose-s_simcc-body7_pt-body7-halpe26_700e-256x192-7f134165_20230605.onnx +3 -0
- kinemotion/models/yolox_tiny_8xb8-300e_humanart-6f3252f9.onnx +3 -0
- {kinemotion-0.76.3.dist-info → kinemotion-1.0.0.dist-info}/METADATA +26 -75
- kinemotion-1.0.0.dist-info/RECORD +49 -0
- kinemotion/core/overlay_constants.py +0 -61
- kinemotion/core/video_analysis_base.py +0 -132
- kinemotion/countermovement_jump/debug_overlay.py +0 -325
- kinemotion/drop_jump/debug_overlay.py +0 -241
- kinemotion/squat_jump/__init__.py +0 -5
- kinemotion/squat_jump/analysis.py +0 -377
- kinemotion/squat_jump/api.py +0 -610
- kinemotion/squat_jump/cli.py +0 -309
- kinemotion/squat_jump/debug_overlay.py +0 -163
- kinemotion/squat_jump/kinematics.py +0 -342
- kinemotion/squat_jump/metrics_validator.py +0 -438
- kinemotion/squat_jump/validation_bounds.py +0 -221
- kinemotion-0.76.3.dist-info/RECORD +0 -57
- /kinemotion/{countermovement_jump → cmj}/__init__.py +0 -0
- /kinemotion/{countermovement_jump → cmj}/kinematics.py +0 -0
- {kinemotion-0.76.3.dist-info → kinemotion-1.0.0.dist-info}/WHEEL +0 -0
- {kinemotion-0.76.3.dist-info → kinemotion-1.0.0.dist-info}/entry_points.txt +0 -0
- {kinemotion-0.76.3.dist-info → kinemotion-1.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,342 +0,0 @@
|
|
|
1
|
-
"""Squat Jump (SJ) metrics calculation."""
|
|
2
|
-
|
|
3
|
-
from dataclasses import dataclass
|
|
4
|
-
from typing import TYPE_CHECKING, TypedDict
|
|
5
|
-
|
|
6
|
-
import numpy as np
|
|
7
|
-
|
|
8
|
-
from ..core.formatting import format_float_metric
|
|
9
|
-
from ..core.types import FloatArray
|
|
10
|
-
|
|
11
|
-
if TYPE_CHECKING:
|
|
12
|
-
from ..core.metadata import ResultMetadata
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class SJDataDict(TypedDict, total=False):
|
|
16
|
-
"""Type-safe dictionary for SJ measurement data."""
|
|
17
|
-
|
|
18
|
-
jump_height_m: float
|
|
19
|
-
flight_time_ms: float
|
|
20
|
-
squat_hold_duration_ms: float
|
|
21
|
-
concentric_duration_ms: float
|
|
22
|
-
peak_concentric_velocity_m_s: float
|
|
23
|
-
peak_force_n: float | None
|
|
24
|
-
peak_power_w: float | None
|
|
25
|
-
mean_power_w: float | None
|
|
26
|
-
squat_hold_start_frame: float | None
|
|
27
|
-
concentric_start_frame: float | None
|
|
28
|
-
takeoff_frame: float | None
|
|
29
|
-
landing_frame: float | None
|
|
30
|
-
mass_kg: float | None
|
|
31
|
-
tracking_method: str
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class SJResultDict(TypedDict, total=False):
|
|
35
|
-
"""Type-safe dictionary for complete SJ result with data and metadata."""
|
|
36
|
-
|
|
37
|
-
data: SJDataDict
|
|
38
|
-
metadata: dict # ResultMetadata.to_dict()
|
|
39
|
-
validation: dict # ValidationResult.to_dict()
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
@dataclass
|
|
43
|
-
class SJMetrics:
|
|
44
|
-
"""Metrics for a squat jump analysis.
|
|
45
|
-
|
|
46
|
-
Attributes:
|
|
47
|
-
jump_height: Maximum jump height in meters
|
|
48
|
-
flight_time: Time spent in the air in milliseconds
|
|
49
|
-
squat_hold_duration: Duration of static squat hold phase in milliseconds
|
|
50
|
-
concentric_duration: Duration of concentric phase in milliseconds
|
|
51
|
-
peak_concentric_velocity: Maximum upward velocity during concentric phase in m/s
|
|
52
|
-
peak_force: Maximum force during concentric phase in Newtons
|
|
53
|
-
peak_power: Maximum power during concentric phase in Watts
|
|
54
|
-
mean_power: Mean power during concentric phase in Watts
|
|
55
|
-
squat_hold_start_frame: Frame index where squat hold begins (0-indexed)
|
|
56
|
-
concentric_start_frame: Frame index where concentric phase begins (0-indexed)
|
|
57
|
-
takeoff_frame: Frame index where takeoff occurs (0-indexed)
|
|
58
|
-
landing_frame: Frame index where landing occurs (0-indexed)
|
|
59
|
-
mass_kg: Athlete mass in kilograms
|
|
60
|
-
tracking_method: Method used for position tracking
|
|
61
|
-
result_metadata: Metadata about the analysis process
|
|
62
|
-
"""
|
|
63
|
-
|
|
64
|
-
jump_height: float
|
|
65
|
-
flight_time: float
|
|
66
|
-
squat_hold_duration: float
|
|
67
|
-
concentric_duration: float
|
|
68
|
-
peak_concentric_velocity: float
|
|
69
|
-
peak_force: float | None = None
|
|
70
|
-
peak_power: float | None = None
|
|
71
|
-
mean_power: float | None = None
|
|
72
|
-
squat_hold_start_frame: float | None = None
|
|
73
|
-
concentric_start_frame: float | None = None
|
|
74
|
-
takeoff_frame: float | None = None
|
|
75
|
-
landing_frame: float | None = None
|
|
76
|
-
mass_kg: float | None = None
|
|
77
|
-
tracking_method: str = "hip"
|
|
78
|
-
result_metadata: "ResultMetadata | None" = None
|
|
79
|
-
|
|
80
|
-
def to_dict(self) -> SJResultDict:
|
|
81
|
-
"""Convert metrics to JSON-serializable dictionary.
|
|
82
|
-
|
|
83
|
-
Returns:
|
|
84
|
-
Dictionary containing all SJ metrics with proper formatting.
|
|
85
|
-
"""
|
|
86
|
-
data: dict[str, float | None | str] = {
|
|
87
|
-
"jump_height_m": format_float_metric(self.jump_height, 1, 3),
|
|
88
|
-
"flight_time_ms": format_float_metric(self.flight_time, 1000, 2),
|
|
89
|
-
"squat_hold_duration_ms": format_float_metric(self.squat_hold_duration, 1000, 2),
|
|
90
|
-
"concentric_duration_ms": format_float_metric(self.concentric_duration, 1000, 2),
|
|
91
|
-
"peak_concentric_velocity_m_s": format_float_metric(
|
|
92
|
-
self.peak_concentric_velocity, 1, 4
|
|
93
|
-
),
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if self.peak_force is not None:
|
|
97
|
-
data["peak_force_n"] = format_float_metric(self.peak_force, 1, 1)
|
|
98
|
-
if self.peak_power is not None:
|
|
99
|
-
data["peak_power_w"] = format_float_metric(self.peak_power, 1, 1)
|
|
100
|
-
if self.mean_power is not None:
|
|
101
|
-
data["mean_power_w"] = format_float_metric(self.mean_power, 1, 1)
|
|
102
|
-
|
|
103
|
-
if self.squat_hold_start_frame is not None:
|
|
104
|
-
data["squat_hold_start_frame"] = float(self.squat_hold_start_frame)
|
|
105
|
-
if self.concentric_start_frame is not None:
|
|
106
|
-
data["concentric_start_frame"] = float(self.concentric_start_frame)
|
|
107
|
-
if self.takeoff_frame is not None:
|
|
108
|
-
data["takeoff_frame"] = float(self.takeoff_frame)
|
|
109
|
-
if self.landing_frame is not None:
|
|
110
|
-
data["landing_frame"] = float(self.landing_frame)
|
|
111
|
-
if self.mass_kg is not None:
|
|
112
|
-
data["mass_kg"] = float(self.mass_kg)
|
|
113
|
-
data["tracking_method"] = self.tracking_method
|
|
114
|
-
|
|
115
|
-
result: SJResultDict = {"data": data} # type: ignore[typeddict-item]
|
|
116
|
-
|
|
117
|
-
if self.result_metadata is not None:
|
|
118
|
-
result["metadata"] = self.result_metadata.to_dict()
|
|
119
|
-
|
|
120
|
-
return result
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def calculate_sj_metrics(
|
|
124
|
-
positions: FloatArray,
|
|
125
|
-
velocities: FloatArray,
|
|
126
|
-
squat_hold_start: int,
|
|
127
|
-
concentric_start: int,
|
|
128
|
-
takeoff_frame: int,
|
|
129
|
-
landing_frame: int,
|
|
130
|
-
fps: float,
|
|
131
|
-
mass_kg: float | None = None,
|
|
132
|
-
tracking_method: str = "hip",
|
|
133
|
-
) -> SJMetrics:
|
|
134
|
-
"""Calculate Squat Jump metrics from phase transitions.
|
|
135
|
-
|
|
136
|
-
Args:
|
|
137
|
-
positions: 1D array of vertical positions in normalized coordinates
|
|
138
|
-
velocities: 1D array of vertical velocities in normalized coordinates
|
|
139
|
-
squat_hold_start: Frame index where squat hold begins
|
|
140
|
-
concentric_start: Frame index where concentric phase begins
|
|
141
|
-
takeoff_frame: Frame index where takeoff occurs
|
|
142
|
-
landing_frame: Frame index where landing occurs
|
|
143
|
-
fps: Video frames per second
|
|
144
|
-
mass_kg: Athlete mass in kilograms (for power calculations)
|
|
145
|
-
tracking_method: Method used for position tracking
|
|
146
|
-
|
|
147
|
-
Returns:
|
|
148
|
-
SJMetrics object containing all calculated metrics
|
|
149
|
-
"""
|
|
150
|
-
# Calculate jump height from flight time
|
|
151
|
-
g = 9.81 # Gravity acceleration (m/s²)
|
|
152
|
-
flight_time = (landing_frame - takeoff_frame) / fps
|
|
153
|
-
jump_height = (g * flight_time**2) / 8
|
|
154
|
-
|
|
155
|
-
# Calculate concentric duration
|
|
156
|
-
concentric_duration = (takeoff_frame - concentric_start) / fps
|
|
157
|
-
|
|
158
|
-
# Calculate squat hold duration
|
|
159
|
-
squat_hold_duration = (concentric_start - squat_hold_start) / fps
|
|
160
|
-
|
|
161
|
-
# Calculate peak concentric velocity (upward is positive)
|
|
162
|
-
if takeoff_frame > concentric_start:
|
|
163
|
-
peak_concentric_velocity = np.max(np.abs(velocities[concentric_start:takeoff_frame]))
|
|
164
|
-
else:
|
|
165
|
-
peak_concentric_velocity = 0.0
|
|
166
|
-
|
|
167
|
-
# Calculate power and force if mass is provided
|
|
168
|
-
peak_power = _calculate_peak_power(velocities, concentric_start, takeoff_frame, mass_kg)
|
|
169
|
-
mean_power = _calculate_mean_power(
|
|
170
|
-
positions, velocities, concentric_start, takeoff_frame, fps, mass_kg
|
|
171
|
-
)
|
|
172
|
-
peak_force = _calculate_peak_force(
|
|
173
|
-
positions, velocities, concentric_start, takeoff_frame, fps, mass_kg
|
|
174
|
-
)
|
|
175
|
-
|
|
176
|
-
return SJMetrics(
|
|
177
|
-
jump_height=jump_height,
|
|
178
|
-
flight_time=flight_time,
|
|
179
|
-
squat_hold_duration=squat_hold_duration,
|
|
180
|
-
concentric_duration=concentric_duration,
|
|
181
|
-
peak_concentric_velocity=peak_concentric_velocity,
|
|
182
|
-
peak_force=peak_force,
|
|
183
|
-
peak_power=peak_power,
|
|
184
|
-
mean_power=mean_power,
|
|
185
|
-
squat_hold_start_frame=float(squat_hold_start),
|
|
186
|
-
concentric_start_frame=float(concentric_start),
|
|
187
|
-
takeoff_frame=float(takeoff_frame),
|
|
188
|
-
landing_frame=float(landing_frame),
|
|
189
|
-
mass_kg=mass_kg,
|
|
190
|
-
tracking_method=tracking_method,
|
|
191
|
-
)
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
def _calculate_peak_power(
|
|
195
|
-
velocities: FloatArray,
|
|
196
|
-
concentric_start: int,
|
|
197
|
-
takeoff_frame: int,
|
|
198
|
-
mass_kg: float | None,
|
|
199
|
-
) -> float | None:
|
|
200
|
-
"""Calculate peak power using Sayers et al. (1999) regression equation.
|
|
201
|
-
|
|
202
|
-
Formula: Peak Power (W) = 60.7 × jump_height_cm + 45.3 × mass_kg − 2055
|
|
203
|
-
|
|
204
|
-
Validation (Sayers et al., 1999, N=108):
|
|
205
|
-
- R² = 0.87 (strong correlation with force plate data)
|
|
206
|
-
- SEE = 355.0 W
|
|
207
|
-
- Error: < 1% underestimation
|
|
208
|
-
- Superior to Lewis formula (73% error) and Harman equation
|
|
209
|
-
|
|
210
|
-
Args:
|
|
211
|
-
velocities: 1D array of vertical velocities
|
|
212
|
-
concentric_start: Frame index where concentric phase begins
|
|
213
|
-
takeoff_frame: Frame index where takeoff occurs
|
|
214
|
-
mass_kg: Athlete mass in kilograms
|
|
215
|
-
|
|
216
|
-
Returns:
|
|
217
|
-
Peak power in Watts, or None if mass is not provided
|
|
218
|
-
"""
|
|
219
|
-
if mass_kg is None:
|
|
220
|
-
return None
|
|
221
|
-
|
|
222
|
-
g = 9.81
|
|
223
|
-
|
|
224
|
-
# Calculate takeoff velocity (negative = upward in normalized coords)
|
|
225
|
-
if takeoff_frame > concentric_start:
|
|
226
|
-
takeoff_velocity = np.min(velocities[concentric_start:takeoff_frame])
|
|
227
|
-
else:
|
|
228
|
-
takeoff_velocity = 0.0
|
|
229
|
-
|
|
230
|
-
# Calculate jump height from takeoff velocity: h = v² / (2g)
|
|
231
|
-
# Use absolute value since v is negative for upward motion
|
|
232
|
-
jump_height_m = (takeoff_velocity**2) / (2 * g)
|
|
233
|
-
|
|
234
|
-
# Convert to centimeters for Sayers formula
|
|
235
|
-
jump_height_cm = jump_height_m * 100
|
|
236
|
-
|
|
237
|
-
# Sayers et al. (1999) regression equation
|
|
238
|
-
peak_power = 60.7 * jump_height_cm + 45.3 * mass_kg - 2055
|
|
239
|
-
|
|
240
|
-
return float(peak_power)
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
def _calculate_mean_power(
|
|
244
|
-
positions: FloatArray,
|
|
245
|
-
velocities: FloatArray,
|
|
246
|
-
concentric_start: int,
|
|
247
|
-
takeoff_frame: int,
|
|
248
|
-
fps: float,
|
|
249
|
-
mass_kg: float | None,
|
|
250
|
-
) -> float | None:
|
|
251
|
-
"""Calculate mean power during concentric phase using work-energy theorem.
|
|
252
|
-
|
|
253
|
-
Formula: Mean Power (W) = (mass × g × jump_height) / concentric_duration
|
|
254
|
-
|
|
255
|
-
This represents the true mean power output during the concentric phase.
|
|
256
|
-
Typical mean-to-peak power ratio: 60-75%.
|
|
257
|
-
|
|
258
|
-
Args:
|
|
259
|
-
positions: 1D array of vertical positions
|
|
260
|
-
velocities: 1D array of vertical velocities
|
|
261
|
-
concentric_start: Frame index where concentric phase begins
|
|
262
|
-
takeoff_frame: Frame index where takeoff occurs
|
|
263
|
-
fps: Video frames per second
|
|
264
|
-
mass_kg: Athlete mass in kilograms
|
|
265
|
-
|
|
266
|
-
Returns:
|
|
267
|
-
Mean power in Watts, or None if mass is not provided
|
|
268
|
-
"""
|
|
269
|
-
if mass_kg is None:
|
|
270
|
-
return None
|
|
271
|
-
|
|
272
|
-
# Calculate concentric duration
|
|
273
|
-
concentric_duration = (takeoff_frame - concentric_start) / fps
|
|
274
|
-
|
|
275
|
-
if concentric_duration <= 0:
|
|
276
|
-
return None
|
|
277
|
-
|
|
278
|
-
# Calculate takeoff velocity (negative = upward in normalized coords)
|
|
279
|
-
if takeoff_frame > concentric_start:
|
|
280
|
-
takeoff_velocity = np.min(velocities[concentric_start:takeoff_frame])
|
|
281
|
-
else:
|
|
282
|
-
takeoff_velocity = 0.0
|
|
283
|
-
|
|
284
|
-
# Calculate jump height from takeoff velocity: h = v² / (2g)
|
|
285
|
-
g = 9.81
|
|
286
|
-
jump_height_m = (takeoff_velocity**2) / (2 * g)
|
|
287
|
-
|
|
288
|
-
# Work-energy theorem: Mean Power = Work / Time
|
|
289
|
-
mean_power = (mass_kg * g * jump_height_m) / concentric_duration
|
|
290
|
-
|
|
291
|
-
return float(mean_power)
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
def _calculate_peak_force(
|
|
295
|
-
positions: FloatArray,
|
|
296
|
-
velocities: FloatArray,
|
|
297
|
-
concentric_start: int,
|
|
298
|
-
takeoff_frame: int,
|
|
299
|
-
fps: float,
|
|
300
|
-
mass_kg: float | None,
|
|
301
|
-
) -> float | None:
|
|
302
|
-
"""Calculate peak force during concentric phase.
|
|
303
|
-
|
|
304
|
-
Args:
|
|
305
|
-
positions: 1D array of vertical positions
|
|
306
|
-
velocities: 1D array of vertical velocities
|
|
307
|
-
concentric_start: Frame index where concentric phase begins
|
|
308
|
-
takeoff_frame: Frame index where takeoff occurs
|
|
309
|
-
fps: Video frames per second
|
|
310
|
-
mass_kg: Athlete mass in kilograms
|
|
311
|
-
|
|
312
|
-
Returns:
|
|
313
|
-
Peak force in Newtons, or None if mass is not provided
|
|
314
|
-
"""
|
|
315
|
-
if mass_kg is None:
|
|
316
|
-
return None
|
|
317
|
-
|
|
318
|
-
# Calculate concentric duration
|
|
319
|
-
concentric_duration = (takeoff_frame - concentric_start) / fps
|
|
320
|
-
|
|
321
|
-
if concentric_duration <= 0:
|
|
322
|
-
return None
|
|
323
|
-
|
|
324
|
-
# Calculate takeoff velocity (negative = upward in normalized coords)
|
|
325
|
-
if takeoff_frame > concentric_start:
|
|
326
|
-
takeoff_velocity = np.min(velocities[concentric_start:takeoff_frame])
|
|
327
|
-
else:
|
|
328
|
-
takeoff_velocity = 0.0
|
|
329
|
-
|
|
330
|
-
# Calculate average acceleration: a = v / t
|
|
331
|
-
# Use absolute value since we want magnitude of upward acceleration
|
|
332
|
-
g = 9.81
|
|
333
|
-
avg_acceleration = np.abs(takeoff_velocity) / concentric_duration
|
|
334
|
-
|
|
335
|
-
# Average force: F = ma + mg (overcoming gravity + accelerating)
|
|
336
|
-
avg_force = mass_kg * (avg_acceleration + g)
|
|
337
|
-
|
|
338
|
-
# Peak force is typically 1.2-1.5× average force
|
|
339
|
-
# Use 1.3 as validated in biomechanics literature
|
|
340
|
-
peak_force = 1.3 * avg_force
|
|
341
|
-
|
|
342
|
-
return float(peak_force)
|