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.

Files changed (53) hide show
  1. kinemotion/__init__.py +3 -18
  2. kinemotion/api.py +7 -27
  3. kinemotion/cli.py +2 -4
  4. kinemotion/{countermovement_jump → cmj}/analysis.py +158 -16
  5. kinemotion/{countermovement_jump → cmj}/api.py +18 -46
  6. kinemotion/{countermovement_jump → cmj}/cli.py +46 -6
  7. kinemotion/cmj/debug_overlay.py +457 -0
  8. kinemotion/{countermovement_jump → cmj}/joint_angles.py +31 -96
  9. kinemotion/{countermovement_jump → cmj}/metrics_validator.py +293 -184
  10. kinemotion/{countermovement_jump → cmj}/validation_bounds.py +18 -1
  11. kinemotion/core/__init__.py +2 -11
  12. kinemotion/core/auto_tuning.py +107 -149
  13. kinemotion/core/cli_utils.py +0 -74
  14. kinemotion/core/debug_overlay_utils.py +15 -142
  15. kinemotion/core/experimental.py +51 -55
  16. kinemotion/core/filtering.py +56 -116
  17. kinemotion/core/pipeline_utils.py +2 -2
  18. kinemotion/core/pose.py +98 -47
  19. kinemotion/core/quality.py +6 -4
  20. kinemotion/core/smoothing.py +51 -65
  21. kinemotion/core/types.py +0 -15
  22. kinemotion/core/validation.py +7 -76
  23. kinemotion/core/video_io.py +27 -41
  24. kinemotion/{drop_jump → dropjump}/__init__.py +8 -2
  25. kinemotion/{drop_jump → dropjump}/analysis.py +120 -282
  26. kinemotion/{drop_jump → dropjump}/api.py +33 -59
  27. kinemotion/{drop_jump → dropjump}/cli.py +136 -70
  28. kinemotion/dropjump/debug_overlay.py +182 -0
  29. kinemotion/{drop_jump → dropjump}/kinematics.py +65 -175
  30. kinemotion/{drop_jump → dropjump}/metrics_validator.py +51 -25
  31. kinemotion/{drop_jump → dropjump}/validation_bounds.py +1 -1
  32. kinemotion/models/rtmpose-s_simcc-body7_pt-body7-halpe26_700e-256x192-7f134165_20230605.onnx +3 -0
  33. kinemotion/models/yolox_tiny_8xb8-300e_humanart-6f3252f9.onnx +3 -0
  34. {kinemotion-0.76.3.dist-info → kinemotion-1.0.0.dist-info}/METADATA +26 -75
  35. kinemotion-1.0.0.dist-info/RECORD +49 -0
  36. kinemotion/core/overlay_constants.py +0 -61
  37. kinemotion/core/video_analysis_base.py +0 -132
  38. kinemotion/countermovement_jump/debug_overlay.py +0 -325
  39. kinemotion/drop_jump/debug_overlay.py +0 -241
  40. kinemotion/squat_jump/__init__.py +0 -5
  41. kinemotion/squat_jump/analysis.py +0 -377
  42. kinemotion/squat_jump/api.py +0 -610
  43. kinemotion/squat_jump/cli.py +0 -309
  44. kinemotion/squat_jump/debug_overlay.py +0 -163
  45. kinemotion/squat_jump/kinematics.py +0 -342
  46. kinemotion/squat_jump/metrics_validator.py +0 -438
  47. kinemotion/squat_jump/validation_bounds.py +0 -221
  48. kinemotion-0.76.3.dist-info/RECORD +0 -57
  49. /kinemotion/{countermovement_jump → cmj}/__init__.py +0 -0
  50. /kinemotion/{countermovement_jump → cmj}/kinematics.py +0 -0
  51. {kinemotion-0.76.3.dist-info → kinemotion-1.0.0.dist-info}/WHEEL +0 -0
  52. {kinemotion-0.76.3.dist-info → kinemotion-1.0.0.dist-info}/entry_points.txt +0 -0
  53. {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)