kinemotion 0.63.0__py3-none-any.whl → 0.65.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.
- kinemotion/cmj/analysis.py +26 -25
- kinemotion/cmj/kinematics.py +7 -7
- kinemotion/cmj/metrics_validator.py +16 -13
- kinemotion/cmj/validation_bounds.py +4 -1
- kinemotion/core/debug_overlay_utils.py +2 -2
- kinemotion/core/pose.py +1 -1
- kinemotion/core/smoothing.py +27 -21
- kinemotion/core/types.py +42 -0
- kinemotion/core/video_io.py +11 -0
- kinemotion/dropjump/analysis.py +19 -18
- kinemotion/dropjump/metrics_validator.py +2 -1
- kinemotion/dropjump/validation_bounds.py +2 -1
- {kinemotion-0.63.0.dist-info → kinemotion-0.65.0.dist-info}/METADATA +2 -1
- {kinemotion-0.63.0.dist-info → kinemotion-0.65.0.dist-info}/RECORD +17 -16
- {kinemotion-0.63.0.dist-info → kinemotion-0.65.0.dist-info}/WHEEL +0 -0
- {kinemotion-0.63.0.dist-info → kinemotion-0.65.0.dist-info}/entry_points.txt +0 -0
- {kinemotion-0.63.0.dist-info → kinemotion-0.65.0.dist-info}/licenses/LICENSE +0 -0
kinemotion/cmj/analysis.py
CHANGED
|
@@ -8,11 +8,12 @@ from scipy.signal import savgol_filter
|
|
|
8
8
|
from ..core.experimental import unused
|
|
9
9
|
from ..core.smoothing import compute_acceleration_from_derivative
|
|
10
10
|
from ..core.timing import NULL_TIMER, Timer
|
|
11
|
+
from ..core.types import FloatArray
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
def compute_signed_velocity(
|
|
14
|
-
positions:
|
|
15
|
-
) ->
|
|
15
|
+
positions: FloatArray, window_length: int = 5, polyorder: int = 2
|
|
16
|
+
) -> FloatArray:
|
|
16
17
|
"""
|
|
17
18
|
Compute SIGNED velocity for CMJ phase detection.
|
|
18
19
|
|
|
@@ -59,8 +60,8 @@ class CMJPhase(Enum):
|
|
|
59
60
|
since="0.34.0",
|
|
60
61
|
)
|
|
61
62
|
def find_standing_phase(
|
|
62
|
-
positions:
|
|
63
|
-
velocities:
|
|
63
|
+
positions: FloatArray,
|
|
64
|
+
velocities: FloatArray,
|
|
64
65
|
fps: float,
|
|
65
66
|
min_standing_duration: float = 0.5,
|
|
66
67
|
velocity_threshold: float = 0.01,
|
|
@@ -112,7 +113,7 @@ def find_standing_phase(
|
|
|
112
113
|
since="0.34.0",
|
|
113
114
|
)
|
|
114
115
|
def find_countermovement_start(
|
|
115
|
-
velocities:
|
|
116
|
+
velocities: FloatArray,
|
|
116
117
|
countermovement_threshold: float = 0.015,
|
|
117
118
|
min_eccentric_frames: int = 3,
|
|
118
119
|
standing_start: int | None = None,
|
|
@@ -152,8 +153,8 @@ def find_countermovement_start(
|
|
|
152
153
|
|
|
153
154
|
|
|
154
155
|
def find_lowest_point(
|
|
155
|
-
positions:
|
|
156
|
-
velocities:
|
|
156
|
+
positions: FloatArray,
|
|
157
|
+
velocities: FloatArray,
|
|
157
158
|
min_search_frame: int = 80,
|
|
158
159
|
) -> int:
|
|
159
160
|
"""
|
|
@@ -195,8 +196,8 @@ def find_lowest_point(
|
|
|
195
196
|
|
|
196
197
|
|
|
197
198
|
def find_cmj_takeoff_from_velocity_peak(
|
|
198
|
-
positions:
|
|
199
|
-
velocities:
|
|
199
|
+
positions: FloatArray,
|
|
200
|
+
velocities: FloatArray,
|
|
200
201
|
lowest_point_frame: int,
|
|
201
202
|
fps: float,
|
|
202
203
|
) -> float:
|
|
@@ -232,9 +233,9 @@ def find_cmj_takeoff_from_velocity_peak(
|
|
|
232
233
|
|
|
233
234
|
|
|
234
235
|
def find_cmj_landing_from_position_peak(
|
|
235
|
-
positions:
|
|
236
|
-
velocities:
|
|
237
|
-
accelerations:
|
|
236
|
+
positions: FloatArray,
|
|
237
|
+
velocities: FloatArray,
|
|
238
|
+
accelerations: FloatArray,
|
|
238
239
|
takeoff_frame: int,
|
|
239
240
|
fps: float,
|
|
240
241
|
) -> float:
|
|
@@ -290,8 +291,8 @@ def find_cmj_landing_from_position_peak(
|
|
|
290
291
|
since="0.34.0",
|
|
291
292
|
)
|
|
292
293
|
def find_interpolated_takeoff_landing(
|
|
293
|
-
positions:
|
|
294
|
-
velocities:
|
|
294
|
+
positions: FloatArray,
|
|
295
|
+
velocities: FloatArray,
|
|
295
296
|
lowest_point_frame: int,
|
|
296
297
|
window_length: int = 5,
|
|
297
298
|
polyorder: int = 2,
|
|
@@ -334,7 +335,7 @@ def find_interpolated_takeoff_landing(
|
|
|
334
335
|
return (takeoff_frame, landing_frame)
|
|
335
336
|
|
|
336
337
|
|
|
337
|
-
def find_takeoff_frame(velocities:
|
|
338
|
+
def find_takeoff_frame(velocities: FloatArray, peak_height_frame: int, fps: float) -> float:
|
|
338
339
|
"""Find takeoff frame as peak upward velocity before peak height.
|
|
339
340
|
|
|
340
341
|
Robust detection: When velocities are nearly identical (flat), detects
|
|
@@ -364,7 +365,7 @@ def find_takeoff_frame(velocities: np.ndarray, peak_height_frame: int, fps: floa
|
|
|
364
365
|
|
|
365
366
|
|
|
366
367
|
def find_lowest_frame(
|
|
367
|
-
velocities:
|
|
368
|
+
velocities: FloatArray, positions: FloatArray, takeoff_frame: float, fps: float
|
|
368
369
|
) -> float:
|
|
369
370
|
"""Find lowest point frame before takeoff."""
|
|
370
371
|
lowest_search_start = max(0, int(takeoff_frame) - int(fps * 0.4))
|
|
@@ -385,8 +386,8 @@ def find_lowest_frame(
|
|
|
385
386
|
|
|
386
387
|
|
|
387
388
|
def find_landing_frame(
|
|
388
|
-
accelerations:
|
|
389
|
-
velocities:
|
|
389
|
+
accelerations: FloatArray,
|
|
390
|
+
velocities: FloatArray,
|
|
390
391
|
peak_height_frame: int,
|
|
391
392
|
fps: float,
|
|
392
393
|
) -> float:
|
|
@@ -455,8 +456,8 @@ def compute_average_hip_position(
|
|
|
455
456
|
"""
|
|
456
457
|
hip_keys = ["left_hip", "right_hip"]
|
|
457
458
|
|
|
458
|
-
x_positions = []
|
|
459
|
-
y_positions = []
|
|
459
|
+
x_positions: list[float] = []
|
|
460
|
+
y_positions: list[float] = []
|
|
460
461
|
|
|
461
462
|
for key in hip_keys:
|
|
462
463
|
if key in landmarks:
|
|
@@ -472,10 +473,10 @@ def compute_average_hip_position(
|
|
|
472
473
|
|
|
473
474
|
|
|
474
475
|
def find_standing_end(
|
|
475
|
-
velocities:
|
|
476
|
+
velocities: FloatArray,
|
|
476
477
|
lowest_point: float,
|
|
477
|
-
_positions:
|
|
478
|
-
accelerations:
|
|
478
|
+
_positions: FloatArray | None = None,
|
|
479
|
+
accelerations: FloatArray | None = None,
|
|
479
480
|
) -> float | None:
|
|
480
481
|
"""
|
|
481
482
|
Find end of standing phase before lowest point.
|
|
@@ -535,11 +536,11 @@ def find_standing_end(
|
|
|
535
536
|
|
|
536
537
|
|
|
537
538
|
def detect_cmj_phases(
|
|
538
|
-
positions:
|
|
539
|
+
positions: FloatArray,
|
|
539
540
|
fps: float,
|
|
540
541
|
window_length: int = 5,
|
|
541
542
|
polyorder: int = 2,
|
|
542
|
-
landing_positions:
|
|
543
|
+
landing_positions: FloatArray | None = None,
|
|
543
544
|
timer: Timer | None = None,
|
|
544
545
|
) -> tuple[float | None, float, float, float] | None:
|
|
545
546
|
"""
|
kinemotion/cmj/kinematics.py
CHANGED
|
@@ -4,9 +4,9 @@ from dataclasses import dataclass
|
|
|
4
4
|
from typing import TYPE_CHECKING, TypedDict
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
|
-
from numpy.typing import NDArray
|
|
8
7
|
|
|
9
8
|
from ..core.formatting import format_float_metric
|
|
9
|
+
from ..core.types import FloatArray
|
|
10
10
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
12
12
|
from ..core.cmj_metrics_validator import ValidationResult
|
|
@@ -136,7 +136,7 @@ class CMJMetrics:
|
|
|
136
136
|
|
|
137
137
|
|
|
138
138
|
def _calculate_scale_factor(
|
|
139
|
-
positions:
|
|
139
|
+
positions: FloatArray,
|
|
140
140
|
takeoff_frame: float,
|
|
141
141
|
landing_frame: float,
|
|
142
142
|
jump_height: float,
|
|
@@ -169,7 +169,7 @@ def _calculate_scale_factor(
|
|
|
169
169
|
|
|
170
170
|
|
|
171
171
|
def _calculate_countermovement_depth(
|
|
172
|
-
positions:
|
|
172
|
+
positions: FloatArray,
|
|
173
173
|
standing_start_frame: float | None,
|
|
174
174
|
lowest_point_frame: float,
|
|
175
175
|
scale_factor: float,
|
|
@@ -222,7 +222,7 @@ def _calculate_phase_durations(
|
|
|
222
222
|
|
|
223
223
|
|
|
224
224
|
def _calculate_peak_velocities(
|
|
225
|
-
velocities:
|
|
225
|
+
velocities: FloatArray,
|
|
226
226
|
standing_start_frame: float | None,
|
|
227
227
|
lowest_point_frame: float,
|
|
228
228
|
takeoff_frame: float,
|
|
@@ -261,7 +261,7 @@ def _calculate_peak_velocities(
|
|
|
261
261
|
|
|
262
262
|
|
|
263
263
|
def _calculate_transition_time(
|
|
264
|
-
velocities:
|
|
264
|
+
velocities: FloatArray,
|
|
265
265
|
lowest_point_frame: float,
|
|
266
266
|
fps: float,
|
|
267
267
|
) -> float | None:
|
|
@@ -291,8 +291,8 @@ def _calculate_transition_time(
|
|
|
291
291
|
|
|
292
292
|
|
|
293
293
|
def calculate_cmj_metrics(
|
|
294
|
-
positions:
|
|
295
|
-
velocities:
|
|
294
|
+
positions: FloatArray,
|
|
295
|
+
velocities: FloatArray,
|
|
296
296
|
standing_start_frame: float | None,
|
|
297
297
|
lowest_point_frame: float,
|
|
298
298
|
takeoff_frame: float,
|
|
@@ -16,6 +16,7 @@ from kinemotion.cmj.validation_bounds import (
|
|
|
16
16
|
TripleExtensionBounds,
|
|
17
17
|
estimate_athlete_profile,
|
|
18
18
|
)
|
|
19
|
+
from kinemotion.core.types import MetricsDict
|
|
19
20
|
from kinemotion.core.validation import (
|
|
20
21
|
AthleteProfile,
|
|
21
22
|
MetricBounds,
|
|
@@ -80,7 +81,7 @@ class CMJMetricsValidator(MetricsValidator):
|
|
|
80
81
|
"""
|
|
81
82
|
return data.get(key_with_suffix) or data.get(key_without_suffix)
|
|
82
83
|
|
|
83
|
-
def validate(self, metrics:
|
|
84
|
+
def validate(self, metrics: MetricsDict) -> CMJValidationResult:
|
|
84
85
|
"""Validate CMJ metrics comprehensively.
|
|
85
86
|
|
|
86
87
|
Args:
|
|
@@ -128,7 +129,7 @@ class CMJMetricsValidator(MetricsValidator):
|
|
|
128
129
|
return result
|
|
129
130
|
|
|
130
131
|
def _check_flight_time(
|
|
131
|
-
self, metrics:
|
|
132
|
+
self, metrics: MetricsDict, result: CMJValidationResult, profile: AthleteProfile
|
|
132
133
|
) -> None:
|
|
133
134
|
"""Validate flight time."""
|
|
134
135
|
flight_time_raw = self._get_metric_value(metrics, "flight_time_ms", "flight_time")
|
|
@@ -176,7 +177,7 @@ class CMJMetricsValidator(MetricsValidator):
|
|
|
176
177
|
)
|
|
177
178
|
|
|
178
179
|
def _check_jump_height(
|
|
179
|
-
self, metrics:
|
|
180
|
+
self, metrics: MetricsDict, result: CMJValidationResult, profile: AthleteProfile
|
|
180
181
|
) -> None:
|
|
181
182
|
"""Validate jump height."""
|
|
182
183
|
jump_height = self._get_metric_value(metrics, "jump_height_m", "jump_height")
|
|
@@ -217,7 +218,7 @@ class CMJMetricsValidator(MetricsValidator):
|
|
|
217
218
|
)
|
|
218
219
|
|
|
219
220
|
def _check_countermovement_depth(
|
|
220
|
-
self, metrics:
|
|
221
|
+
self, metrics: MetricsDict, result: CMJValidationResult, profile: AthleteProfile
|
|
221
222
|
) -> None:
|
|
222
223
|
"""Validate countermovement depth."""
|
|
223
224
|
depth = self._get_metric_value(metrics, "countermovement_depth_m", "countermovement_depth")
|
|
@@ -258,7 +259,7 @@ class CMJMetricsValidator(MetricsValidator):
|
|
|
258
259
|
)
|
|
259
260
|
|
|
260
261
|
def _check_concentric_duration(
|
|
261
|
-
self, metrics:
|
|
262
|
+
self, metrics: MetricsDict, result: CMJValidationResult, profile: AthleteProfile
|
|
262
263
|
) -> None:
|
|
263
264
|
"""Validate concentric duration (contact time)."""
|
|
264
265
|
duration_raw = self._get_metric_value(
|
|
@@ -308,7 +309,7 @@ class CMJMetricsValidator(MetricsValidator):
|
|
|
308
309
|
)
|
|
309
310
|
|
|
310
311
|
def _check_eccentric_duration(
|
|
311
|
-
self, metrics:
|
|
312
|
+
self, metrics: MetricsDict, result: CMJValidationResult, profile: AthleteProfile
|
|
312
313
|
) -> None:
|
|
313
314
|
"""Validate eccentric duration."""
|
|
314
315
|
duration_raw = self._get_metric_value(
|
|
@@ -349,7 +350,7 @@ class CMJMetricsValidator(MetricsValidator):
|
|
|
349
350
|
)
|
|
350
351
|
|
|
351
352
|
def _check_peak_velocities(
|
|
352
|
-
self, metrics:
|
|
353
|
+
self, metrics: MetricsDict, result: CMJValidationResult, profile: AthleteProfile
|
|
353
354
|
) -> None:
|
|
354
355
|
"""Validate peak eccentric and concentric velocities."""
|
|
355
356
|
# Eccentric
|
|
@@ -419,7 +420,7 @@ class CMJMetricsValidator(MetricsValidator):
|
|
|
419
420
|
)
|
|
420
421
|
|
|
421
422
|
def _check_flight_time_height_consistency(
|
|
422
|
-
self, metrics:
|
|
423
|
+
self, metrics: MetricsDict, result: CMJValidationResult
|
|
423
424
|
) -> None:
|
|
424
425
|
"""Verify jump height is consistent with flight time."""
|
|
425
426
|
flight_time_ms = metrics.get("flight_time_ms")
|
|
@@ -455,7 +456,7 @@ class CMJMetricsValidator(MetricsValidator):
|
|
|
455
456
|
)
|
|
456
457
|
|
|
457
458
|
def _check_velocity_height_consistency(
|
|
458
|
-
self, metrics:
|
|
459
|
+
self, metrics: MetricsDict, result: CMJValidationResult
|
|
459
460
|
) -> None:
|
|
460
461
|
"""Verify peak velocity is consistent with jump height."""
|
|
461
462
|
velocity = metrics.get("peak_concentric_velocity_m_s")
|
|
@@ -491,7 +492,7 @@ class CMJMetricsValidator(MetricsValidator):
|
|
|
491
492
|
)
|
|
492
493
|
|
|
493
494
|
def _check_rsi_validity(
|
|
494
|
-
self, metrics:
|
|
495
|
+
self, metrics: MetricsDict, result: CMJValidationResult, profile: AthleteProfile
|
|
495
496
|
) -> None:
|
|
496
497
|
"""Validate Reactive Strength Index."""
|
|
497
498
|
flight_time_raw = self._get_metric_value(metrics, "flight_time_ms", "flight_time")
|
|
@@ -555,7 +556,7 @@ class CMJMetricsValidator(MetricsValidator):
|
|
|
555
556
|
bounds=(expected_min, expected_max),
|
|
556
557
|
)
|
|
557
558
|
|
|
558
|
-
def _check_depth_height_ratio(self, metrics:
|
|
559
|
+
def _check_depth_height_ratio(self, metrics: MetricsDict, result: CMJValidationResult) -> None:
|
|
559
560
|
"""Check countermovement depth to jump height ratio."""
|
|
560
561
|
depth = metrics.get("countermovement_depth_m")
|
|
561
562
|
jump_height = metrics.get("jump_height_m")
|
|
@@ -595,7 +596,9 @@ class CMJMetricsValidator(MetricsValidator):
|
|
|
595
596
|
value=ratio,
|
|
596
597
|
)
|
|
597
598
|
|
|
598
|
-
def _check_contact_depth_ratio(
|
|
599
|
+
def _check_contact_depth_ratio(
|
|
600
|
+
self, metrics: MetricsDict, result: CMJValidationResult
|
|
601
|
+
) -> None:
|
|
599
602
|
"""Check contact time to countermovement depth ratio."""
|
|
600
603
|
contact_ms = metrics.get("concentric_duration_ms")
|
|
601
604
|
depth = metrics.get("countermovement_depth_m")
|
|
@@ -636,7 +639,7 @@ class CMJMetricsValidator(MetricsValidator):
|
|
|
636
639
|
)
|
|
637
640
|
|
|
638
641
|
def _check_triple_extension(
|
|
639
|
-
self, metrics:
|
|
642
|
+
self, metrics: MetricsDict, result: CMJValidationResult, profile: AthleteProfile
|
|
640
643
|
) -> None:
|
|
641
644
|
"""Validate triple extension angles."""
|
|
642
645
|
angles = metrics.get("triple_extension")
|
|
@@ -15,6 +15,7 @@ References:
|
|
|
15
15
|
- Bogdanis (2012): Plyometric training effects
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
+
from kinemotion.core.types import MetricsDict
|
|
18
19
|
from kinemotion.core.validation import AthleteProfile, MetricBounds
|
|
19
20
|
|
|
20
21
|
|
|
@@ -299,7 +300,9 @@ ATHLETE_PROFILES = {
|
|
|
299
300
|
}
|
|
300
301
|
|
|
301
302
|
|
|
302
|
-
def estimate_athlete_profile(
|
|
303
|
+
def estimate_athlete_profile(
|
|
304
|
+
metrics_dict: MetricsDict, gender: str | None = None
|
|
305
|
+
) -> AthleteProfile:
|
|
303
306
|
"""Estimate athlete profile from metrics.
|
|
304
307
|
|
|
305
308
|
Uses jump height as primary classifier:
|
|
@@ -5,10 +5,10 @@ import shutil
|
|
|
5
5
|
import subprocess
|
|
6
6
|
import time
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import Self
|
|
9
8
|
|
|
10
9
|
import cv2
|
|
11
10
|
import numpy as np
|
|
11
|
+
from typing_extensions import Self
|
|
12
12
|
|
|
13
13
|
from .timing import NULL_TIMER, Timer
|
|
14
14
|
|
|
@@ -48,7 +48,7 @@ def create_video_writer(
|
|
|
48
48
|
|
|
49
49
|
for codec in codecs_to_try:
|
|
50
50
|
try:
|
|
51
|
-
fourcc = cv2.VideoWriter_fourcc(*codec)
|
|
51
|
+
fourcc = cv2.VideoWriter_fourcc(*codec) # type: ignore[attr-defined]
|
|
52
52
|
writer = cv2.VideoWriter(output_path, fourcc, fps, (display_width, display_height))
|
|
53
53
|
if writer.isOpened():
|
|
54
54
|
used_codec = codec
|
kinemotion/core/pose.py
CHANGED
|
@@ -25,7 +25,7 @@ class PoseTracker:
|
|
|
25
25
|
timer: Optional Timer for measuring operations
|
|
26
26
|
"""
|
|
27
27
|
self.timer = timer or NULL_TIMER
|
|
28
|
-
self.mp_pose = mp.solutions.pose
|
|
28
|
+
self.mp_pose = mp.solutions.pose # type: ignore[attr-defined]
|
|
29
29
|
self.pose = self.mp_pose.Pose(
|
|
30
30
|
static_image_mode=False, # Use tracking mode for better performance
|
|
31
31
|
min_detection_confidence=min_detection_confidence,
|
kinemotion/core/smoothing.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Landmark smoothing utilities to reduce jitter in pose tracking."""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from collections.abc import Callable
|
|
4
4
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
from scipy.signal import savgol_filter
|
|
@@ -10,11 +10,10 @@ from .filtering import (
|
|
|
10
10
|
reject_outliers,
|
|
11
11
|
)
|
|
12
12
|
from .timing import NULL_TIMER, Timer
|
|
13
|
+
from .types import FloatArray, LandmarkCoord, LandmarkSequence
|
|
13
14
|
|
|
14
|
-
# Type
|
|
15
|
-
|
|
16
|
-
LandmarkFrame: TypeAlias = dict[str, LandmarkCoord] | None
|
|
17
|
-
LandmarkSequence: TypeAlias = list[LandmarkFrame]
|
|
15
|
+
# Type alias for smoothing function callback
|
|
16
|
+
SmootherFn = Callable[[list[float], list[float], list[int]], tuple[FloatArray, FloatArray]]
|
|
18
17
|
|
|
19
18
|
|
|
20
19
|
def _extract_landmark_coordinates(
|
|
@@ -31,9 +30,9 @@ def _extract_landmark_coordinates(
|
|
|
31
30
|
Returns:
|
|
32
31
|
Tuple of (x_coords, y_coords, valid_frames)
|
|
33
32
|
"""
|
|
34
|
-
x_coords = []
|
|
35
|
-
y_coords = []
|
|
36
|
-
valid_frames = []
|
|
33
|
+
x_coords: list[float] = []
|
|
34
|
+
y_coords: list[float] = []
|
|
35
|
+
valid_frames: list[int] = []
|
|
37
36
|
|
|
38
37
|
for i, frame_landmarks in enumerate(landmark_sequence):
|
|
39
38
|
if frame_landmarks is not None and landmark_name in frame_landmarks:
|
|
@@ -86,8 +85,8 @@ def _store_smoothed_landmarks(
|
|
|
86
85
|
smoothed_sequence: LandmarkSequence,
|
|
87
86
|
landmark_sequence: LandmarkSequence,
|
|
88
87
|
landmark_name: str,
|
|
89
|
-
x_smooth:
|
|
90
|
-
y_smooth:
|
|
88
|
+
x_smooth: FloatArray,
|
|
89
|
+
y_smooth: FloatArray,
|
|
91
90
|
valid_frames: list[int],
|
|
92
91
|
) -> None:
|
|
93
92
|
"""
|
|
@@ -103,7 +102,10 @@ def _store_smoothed_landmarks(
|
|
|
103
102
|
"""
|
|
104
103
|
for idx, frame_idx in enumerate(valid_frames):
|
|
105
104
|
if frame_idx >= len(smoothed_sequence):
|
|
106
|
-
|
|
105
|
+
empty_frames: list[dict[str, LandmarkCoord]] = [{}] * (
|
|
106
|
+
frame_idx - len(smoothed_sequence) + 1
|
|
107
|
+
)
|
|
108
|
+
smoothed_sequence.extend(empty_frames)
|
|
107
109
|
|
|
108
110
|
# Ensure smoothed_sequence[frame_idx] is a dict, not None
|
|
109
111
|
if smoothed_sequence[frame_idx] is None:
|
|
@@ -130,7 +132,7 @@ def _smooth_landmarks_core( # NOSONAR(S1172) - polyorder used via closure
|
|
|
130
132
|
landmark_sequence: LandmarkSequence,
|
|
131
133
|
window_length: int,
|
|
132
134
|
polyorder: int,
|
|
133
|
-
smoother_fn
|
|
135
|
+
smoother_fn: SmootherFn,
|
|
134
136
|
) -> LandmarkSequence:
|
|
135
137
|
"""
|
|
136
138
|
Core smoothing logic shared by both standard and advanced smoothing.
|
|
@@ -150,7 +152,7 @@ def _smooth_landmarks_core( # NOSONAR(S1172) - polyorder used via closure
|
|
|
150
152
|
if landmark_names is None:
|
|
151
153
|
return landmark_sequence
|
|
152
154
|
|
|
153
|
-
smoothed_sequence:
|
|
155
|
+
smoothed_sequence: LandmarkSequence = []
|
|
154
156
|
|
|
155
157
|
for landmark_name in landmark_names:
|
|
156
158
|
x_coords, y_coords, valid_frames = _extract_landmark_coordinates(
|
|
@@ -202,9 +204,11 @@ def smooth_landmarks(
|
|
|
202
204
|
if window_length % 2 == 0:
|
|
203
205
|
window_length += 1
|
|
204
206
|
|
|
205
|
-
def savgol_smoother(
|
|
206
|
-
|
|
207
|
-
|
|
207
|
+
def savgol_smoother(
|
|
208
|
+
x_coords: list[float], y_coords: list[float], _valid_frames: list[int]
|
|
209
|
+
) -> tuple[FloatArray, FloatArray]:
|
|
210
|
+
x_smooth: FloatArray = savgol_filter(x_coords, window_length, polyorder)
|
|
211
|
+
y_smooth: FloatArray = savgol_filter(y_coords, window_length, polyorder)
|
|
208
212
|
return x_smooth, y_smooth
|
|
209
213
|
|
|
210
214
|
return _smooth_landmarks_core(landmark_sequence, window_length, polyorder, savgol_smoother)
|
|
@@ -376,9 +380,11 @@ def smooth_landmarks_advanced(
|
|
|
376
380
|
if window_length % 2 == 0:
|
|
377
381
|
window_length += 1
|
|
378
382
|
|
|
379
|
-
def advanced_smoother(
|
|
380
|
-
|
|
381
|
-
|
|
383
|
+
def advanced_smoother(
|
|
384
|
+
x_coords: list[float], y_coords: list[float], _valid_frames: list[int]
|
|
385
|
+
) -> tuple[FloatArray, FloatArray]:
|
|
386
|
+
x_array: FloatArray = np.array(x_coords)
|
|
387
|
+
y_array: FloatArray = np.array(y_coords)
|
|
382
388
|
|
|
383
389
|
# Step 1: Outlier rejection
|
|
384
390
|
if use_outlier_rejection:
|
|
@@ -414,8 +420,8 @@ def smooth_landmarks_advanced(
|
|
|
414
420
|
else:
|
|
415
421
|
# Standard Savitzky-Golay
|
|
416
422
|
with timer.measure("smoothing_savgol"):
|
|
417
|
-
x_smooth = savgol_filter(x_array, window_length, polyorder)
|
|
418
|
-
y_smooth = savgol_filter(y_array, window_length, polyorder)
|
|
423
|
+
x_smooth: FloatArray = savgol_filter(x_array, window_length, polyorder) # type: ignore[reportUnknownVariableType]
|
|
424
|
+
y_smooth: FloatArray = savgol_filter(y_array, window_length, polyorder) # type: ignore[reportUnknownVariableType]
|
|
419
425
|
|
|
420
426
|
return x_smooth, y_smooth
|
|
421
427
|
|
kinemotion/core/types.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Central type definitions for the kinemotion package.
|
|
2
|
+
|
|
3
|
+
This module provides all type aliases used throughout the codebase to ensure
|
|
4
|
+
consistent typing and better IDE support.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, TypeAlias
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
from numpy.typing import NDArray
|
|
11
|
+
|
|
12
|
+
# NumPy array types for various use cases
|
|
13
|
+
FloatArray: TypeAlias = NDArray[np.floating[Any]]
|
|
14
|
+
Float64Array: TypeAlias = NDArray[np.float64]
|
|
15
|
+
IntArray: TypeAlias = NDArray[np.integer[Any]]
|
|
16
|
+
UInt8Array: TypeAlias = NDArray[np.uint8]
|
|
17
|
+
BoolArray: TypeAlias = NDArray[np.bool_]
|
|
18
|
+
|
|
19
|
+
# MediaPipe landmark types
|
|
20
|
+
# Using dict-based representation since MediaPipe lacks proper type stubs
|
|
21
|
+
LandmarkCoord: TypeAlias = tuple[float, float, float] # (x, y, visibility)
|
|
22
|
+
LandmarkFrame: TypeAlias = dict[str, LandmarkCoord] | None
|
|
23
|
+
LandmarkSequence: TypeAlias = list[LandmarkFrame]
|
|
24
|
+
|
|
25
|
+
# Metrics dictionary type
|
|
26
|
+
# Uses Any because metrics can contain:
|
|
27
|
+
# - Simple values: float, int, str
|
|
28
|
+
# - Nested dicts: e.g. "triple_extension" contains angle data
|
|
29
|
+
# - Wrapper structures: e.g. {"data": {...actual metrics...}}
|
|
30
|
+
MetricsDict: TypeAlias = dict[str, Any]
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"FloatArray",
|
|
34
|
+
"Float64Array",
|
|
35
|
+
"IntArray",
|
|
36
|
+
"UInt8Array",
|
|
37
|
+
"BoolArray",
|
|
38
|
+
"LandmarkCoord",
|
|
39
|
+
"LandmarkFrame",
|
|
40
|
+
"LandmarkSequence",
|
|
41
|
+
"MetricsDict",
|
|
42
|
+
]
|
kinemotion/core/video_io.py
CHANGED
|
@@ -203,6 +203,17 @@ class VideoProcessor:
|
|
|
203
203
|
"""Release video capture."""
|
|
204
204
|
self.cap.release()
|
|
205
205
|
|
|
206
|
+
def __iter__(self) -> "VideoProcessor":
|
|
207
|
+
"""Make the processor iterable."""
|
|
208
|
+
return self
|
|
209
|
+
|
|
210
|
+
def __next__(self) -> np.ndarray:
|
|
211
|
+
"""Get the next frame during iteration."""
|
|
212
|
+
frame = self.read_frame()
|
|
213
|
+
if frame is None:
|
|
214
|
+
raise StopIteration
|
|
215
|
+
return frame
|
|
216
|
+
|
|
206
217
|
def __enter__(self) -> "VideoProcessor":
|
|
207
218
|
return self
|
|
208
219
|
|
kinemotion/dropjump/analysis.py
CHANGED
|
@@ -11,6 +11,7 @@ from ..core.smoothing import (
|
|
|
11
11
|
interpolate_threshold_crossing,
|
|
12
12
|
)
|
|
13
13
|
from ..core.timing import NULL_TIMER, Timer
|
|
14
|
+
from ..core.types import BoolArray, FloatArray
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
class ContactState(Enum):
|
|
@@ -27,7 +28,7 @@ class ContactState(Enum):
|
|
|
27
28
|
since="0.34.0",
|
|
28
29
|
)
|
|
29
30
|
def calculate_adaptive_threshold(
|
|
30
|
-
positions:
|
|
31
|
+
positions: FloatArray,
|
|
31
32
|
fps: float,
|
|
32
33
|
baseline_duration: float = 3.0,
|
|
33
34
|
multiplier: float = 1.5,
|
|
@@ -110,7 +111,7 @@ def calculate_adaptive_threshold(
|
|
|
110
111
|
|
|
111
112
|
|
|
112
113
|
def _find_stable_baseline(
|
|
113
|
-
positions:
|
|
114
|
+
positions: FloatArray,
|
|
114
115
|
min_stable_frames: int,
|
|
115
116
|
stability_threshold: float = 0.01,
|
|
116
117
|
debug: bool = False,
|
|
@@ -149,7 +150,7 @@ def _find_stable_baseline(
|
|
|
149
150
|
|
|
150
151
|
|
|
151
152
|
def _find_drop_from_baseline(
|
|
152
|
-
positions:
|
|
153
|
+
positions: FloatArray,
|
|
153
154
|
baseline_start: int,
|
|
154
155
|
baseline_position: float,
|
|
155
156
|
stable_window: int,
|
|
@@ -188,7 +189,7 @@ def _find_drop_from_baseline(
|
|
|
188
189
|
|
|
189
190
|
|
|
190
191
|
def detect_drop_start(
|
|
191
|
-
positions:
|
|
192
|
+
positions: FloatArray,
|
|
192
193
|
fps: float,
|
|
193
194
|
min_stationary_duration: float = 1.0,
|
|
194
195
|
position_change_threshold: float = 0.02,
|
|
@@ -254,10 +255,10 @@ def detect_drop_start(
|
|
|
254
255
|
|
|
255
256
|
|
|
256
257
|
def _filter_stationary_with_visibility(
|
|
257
|
-
is_stationary:
|
|
258
|
-
visibilities:
|
|
258
|
+
is_stationary: BoolArray,
|
|
259
|
+
visibilities: FloatArray | None,
|
|
259
260
|
visibility_threshold: float,
|
|
260
|
-
) ->
|
|
261
|
+
) -> BoolArray:
|
|
261
262
|
"""Apply visibility filter to stationary flags.
|
|
262
263
|
|
|
263
264
|
Args:
|
|
@@ -275,7 +276,7 @@ def _filter_stationary_with_visibility(
|
|
|
275
276
|
|
|
276
277
|
|
|
277
278
|
def _find_contact_frames(
|
|
278
|
-
is_stationary:
|
|
279
|
+
is_stationary: BoolArray,
|
|
279
280
|
min_contact_frames: int,
|
|
280
281
|
) -> set[int]:
|
|
281
282
|
"""Find frames with sustained contact using minimum duration filter.
|
|
@@ -308,7 +309,7 @@ def _find_contact_frames(
|
|
|
308
309
|
def _assign_contact_states(
|
|
309
310
|
n_frames: int,
|
|
310
311
|
contact_frames: set[int],
|
|
311
|
-
visibilities:
|
|
312
|
+
visibilities: FloatArray | None,
|
|
312
313
|
visibility_threshold: float,
|
|
313
314
|
) -> list[ContactState]:
|
|
314
315
|
"""Assign contact states based on contact frames and visibility.
|
|
@@ -334,11 +335,11 @@ def _assign_contact_states(
|
|
|
334
335
|
|
|
335
336
|
|
|
336
337
|
def detect_ground_contact(
|
|
337
|
-
foot_positions:
|
|
338
|
+
foot_positions: FloatArray,
|
|
338
339
|
velocity_threshold: float = 0.02,
|
|
339
340
|
min_contact_frames: int = 3,
|
|
340
341
|
visibility_threshold: float = 0.5,
|
|
341
|
-
visibilities:
|
|
342
|
+
visibilities: FloatArray | None = None,
|
|
342
343
|
window_length: int = 5,
|
|
343
344
|
polyorder: int = 2,
|
|
344
345
|
timer: Timer | None = None,
|
|
@@ -426,7 +427,7 @@ def find_contact_phases(
|
|
|
426
427
|
def _interpolate_phase_start(
|
|
427
428
|
start_idx: int,
|
|
428
429
|
state: ContactState,
|
|
429
|
-
velocities:
|
|
430
|
+
velocities: FloatArray,
|
|
430
431
|
velocity_threshold: float,
|
|
431
432
|
) -> float:
|
|
432
433
|
"""Interpolate start boundary of a phase with sub-frame precision.
|
|
@@ -454,7 +455,7 @@ def _interpolate_phase_start(
|
|
|
454
455
|
def _interpolate_phase_end(
|
|
455
456
|
end_idx: int,
|
|
456
457
|
state: ContactState,
|
|
457
|
-
velocities:
|
|
458
|
+
velocities: FloatArray,
|
|
458
459
|
velocity_threshold: float,
|
|
459
460
|
max_idx: int,
|
|
460
461
|
) -> float:
|
|
@@ -481,7 +482,7 @@ def _interpolate_phase_end(
|
|
|
481
482
|
|
|
482
483
|
|
|
483
484
|
def find_interpolated_phase_transitions(
|
|
484
|
-
foot_positions:
|
|
485
|
+
foot_positions: FloatArray,
|
|
485
486
|
contact_states: list[ContactState],
|
|
486
487
|
velocity_threshold: float,
|
|
487
488
|
smoothing_window: int = 5,
|
|
@@ -523,7 +524,7 @@ def find_interpolated_phase_transitions(
|
|
|
523
524
|
|
|
524
525
|
|
|
525
526
|
def refine_transition_with_curvature(
|
|
526
|
-
foot_positions:
|
|
527
|
+
foot_positions: FloatArray,
|
|
527
528
|
estimated_frame: float,
|
|
528
529
|
transition_type: str,
|
|
529
530
|
search_window: int = 3,
|
|
@@ -598,7 +599,7 @@ def refine_transition_with_curvature(
|
|
|
598
599
|
|
|
599
600
|
|
|
600
601
|
def find_interpolated_phase_transitions_with_curvature(
|
|
601
|
-
foot_positions:
|
|
602
|
+
foot_positions: FloatArray,
|
|
602
603
|
contact_states: list[ContactState],
|
|
603
604
|
velocity_threshold: float,
|
|
604
605
|
smoothing_window: int = 5,
|
|
@@ -684,8 +685,8 @@ def find_interpolated_phase_transitions_with_curvature(
|
|
|
684
685
|
|
|
685
686
|
|
|
686
687
|
def find_landing_from_acceleration(
|
|
687
|
-
positions:
|
|
688
|
-
accelerations:
|
|
688
|
+
positions: FloatArray,
|
|
689
|
+
accelerations: FloatArray,
|
|
689
690
|
takeoff_frame: int,
|
|
690
691
|
fps: float,
|
|
691
692
|
search_duration: float = 0.7,
|
|
@@ -9,6 +9,7 @@ of metric issues.
|
|
|
9
9
|
|
|
10
10
|
from dataclasses import dataclass
|
|
11
11
|
|
|
12
|
+
from kinemotion.core.types import MetricsDict
|
|
12
13
|
from kinemotion.core.validation import (
|
|
13
14
|
MetricsValidator,
|
|
14
15
|
ValidationResult,
|
|
@@ -57,7 +58,7 @@ class DropJumpValidationResult(ValidationResult):
|
|
|
57
58
|
class DropJumpMetricsValidator(MetricsValidator):
|
|
58
59
|
"""Comprehensive drop jump metrics validator."""
|
|
59
60
|
|
|
60
|
-
def validate(self, metrics:
|
|
61
|
+
def validate(self, metrics: MetricsDict) -> DropJumpValidationResult:
|
|
61
62
|
"""Validate drop jump metrics comprehensively.
|
|
62
63
|
|
|
63
64
|
Args:
|
|
@@ -15,6 +15,7 @@ References:
|
|
|
15
15
|
- Covens et al. (2019): Drop jump kinetics across athletes
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
+
from kinemotion.core.types import MetricsDict
|
|
18
19
|
from kinemotion.core.validation import AthleteProfile, MetricBounds
|
|
19
20
|
|
|
20
21
|
|
|
@@ -123,7 +124,7 @@ def _classify_combined_score(combined_score: float) -> AthleteProfile:
|
|
|
123
124
|
return AthleteProfile.ELITE
|
|
124
125
|
|
|
125
126
|
|
|
126
|
-
def estimate_athlete_profile(metrics:
|
|
127
|
+
def estimate_athlete_profile(metrics: MetricsDict, gender: str | None = None) -> AthleteProfile:
|
|
127
128
|
"""Estimate athlete profile from drop jump metrics.
|
|
128
129
|
|
|
129
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.
|
|
3
|
+
Version: 0.65.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
|
|
@@ -25,6 +25,7 @@ Requires-Dist: mediapipe>=0.10.9
|
|
|
25
25
|
Requires-Dist: numpy>=1.26.0
|
|
26
26
|
Requires-Dist: opencv-python>=4.9.0
|
|
27
27
|
Requires-Dist: scipy>=1.11.0
|
|
28
|
+
Requires-Dist: typing-extensions>=4.15.0
|
|
28
29
|
Description-Content-Type: text/markdown
|
|
29
30
|
|
|
30
31
|
# Kinemotion
|
|
@@ -2,41 +2,42 @@ kinemotion/__init__.py,sha256=Ho_BUtsM0PBxBW1ye9RlUg0ZqBlgGudRI9bZTF7QKUI,966
|
|
|
2
2
|
kinemotion/api.py,sha256=uG1e4bTnj2c-6cbZJEZ_LjMwFdaG32ba2KcK_XjE_NI,1040
|
|
3
3
|
kinemotion/cli.py,sha256=cqYV_7URH0JUDy1VQ_EDLv63FmNO4Ns20m6s1XAjiP4,464
|
|
4
4
|
kinemotion/cmj/__init__.py,sha256=SkAw9ka8Yd1Qfv9hcvk22m3EfucROzYrSNGNF5kDzho,113
|
|
5
|
-
kinemotion/cmj/analysis.py,sha256=
|
|
5
|
+
kinemotion/cmj/analysis.py,sha256=jM9ZX44h1__Cg2iIhAYRoo_5fPwIOeV5Q2FZ22rMvKY,22202
|
|
6
6
|
kinemotion/cmj/api.py,sha256=Xu6PepQKQFzRWZMESaWvHyPV-dueCH1TQQeQdil1KiQ,16626
|
|
7
7
|
kinemotion/cmj/cli.py,sha256=P2b77IIw6kqTSIkncxlShzhmjIwqMFBNd-pZxYP-TsI,9918
|
|
8
8
|
kinemotion/cmj/debug_overlay.py,sha256=bX9aPLhXiLCCMZW9v8Y4OiOAaZO0i-UGr-Pl8HCsmbI,15810
|
|
9
9
|
kinemotion/cmj/joint_angles.py,sha256=HmheIEiKcQz39cRezk4h-htorOhGNPsqKIR9RsAEKts,9960
|
|
10
|
-
kinemotion/cmj/kinematics.py,sha256=
|
|
11
|
-
kinemotion/cmj/metrics_validator.py,sha256=
|
|
12
|
-
kinemotion/cmj/validation_bounds.py,sha256=
|
|
10
|
+
kinemotion/cmj/kinematics.py,sha256=KwA8uSj3g1SeNf0NXMSHsp3gIw6Gfa-6QWIwdYdRXYw,13362
|
|
11
|
+
kinemotion/cmj/metrics_validator.py,sha256=3oFB331Xch2sRMTvqALiwOvsWkCUhrLQ7ZCZ4QhI2lA,30986
|
|
12
|
+
kinemotion/cmj/validation_bounds.py,sha256=Ry915JdInPXbqjaVGNY_urnDO1PAkCSJqHwNKRq-VkU,12048
|
|
13
13
|
kinemotion/core/__init__.py,sha256=U2fnLUGXQ0jbwpXhdksYKDXbeQndEHjn9gwTAEJ9Av0,1451
|
|
14
14
|
kinemotion/core/auto_tuning.py,sha256=lhAqPc-eLjMYx9BCvKdECE7TD2Dweb9KcifV6JHaXOE,11278
|
|
15
15
|
kinemotion/core/cli_utils.py,sha256=sQPbT6XWWau-sm9yuN5c3eS5xNzoQGGXwSz6hQXtRvM,1859
|
|
16
|
-
kinemotion/core/debug_overlay_utils.py,sha256=
|
|
16
|
+
kinemotion/core/debug_overlay_utils.py,sha256=Ne8GqEWUySwwtkmAzbBs527mh2QsBW2hbgV9CbdJjxE,8441
|
|
17
17
|
kinemotion/core/determinism.py,sha256=Frw-KAOvAxTL_XtxoWpXCjMbQPUKEAusK6JctlkeuRo,2509
|
|
18
18
|
kinemotion/core/experimental.py,sha256=IK05AF4aZS15ke85hF3TWCqRIXU1AlD_XKzFz735Ua8,3640
|
|
19
19
|
kinemotion/core/filtering.py,sha256=Oc__pV6iHEGyyovbqa5SUi-6v8QyvaRVwA0LRayM884,11355
|
|
20
20
|
kinemotion/core/formatting.py,sha256=G_3eqgOtym9RFOZVEwCxye4A2cyrmgvtQ214vIshowU,2480
|
|
21
21
|
kinemotion/core/metadata.py,sha256=bJAVa4nym__zx1hNowSZduMGKBSGOPxTbBQkjm6N0D0,7207
|
|
22
22
|
kinemotion/core/pipeline_utils.py,sha256=q32c1AJ8KI4Ht-K3ZiI7ectQKtg8k_FLdLy6WPBPWkU,14927
|
|
23
|
-
kinemotion/core/pose.py,sha256=
|
|
23
|
+
kinemotion/core/pose.py,sha256=55zqk02N6ibOCvVO5c73ZegRcDWDeKCnU_lKsdwvN4s,9023
|
|
24
24
|
kinemotion/core/quality.py,sha256=VUkRL2N6B7lfIZ2pE9han_U68JwarmZz1U0ygHkgkhE,13022
|
|
25
|
-
kinemotion/core/smoothing.py,sha256
|
|
25
|
+
kinemotion/core/smoothing.py,sha256=ELMHL7pzSqYffjnLDBUMBJIgt1AwOssDInE8IiXBbig,15942
|
|
26
26
|
kinemotion/core/timing.py,sha256=d1rjZc07Nbi5Jrio9AC-zeS0dNAlbPyNIydLz7X75Pk,7804
|
|
27
|
+
kinemotion/core/types.py,sha256=A_HclzKpf3By5DiJ0wY9B-dQJrIVAAhUfGab7qTSIL8,1279
|
|
27
28
|
kinemotion/core/validation.py,sha256=0xVv-ftWveV60fJ97kmZMuy2Qqqb5aZLR50dDIrjnhg,6773
|
|
28
|
-
kinemotion/core/video_io.py,sha256=
|
|
29
|
+
kinemotion/core/video_io.py,sha256=ufbwfYkxuerat4ab6Hn1tkI5T9TAOvHwREIazJ0Q2tE,8174
|
|
29
30
|
kinemotion/dropjump/__init__.py,sha256=tC3H3BrCg8Oj-db-Vrtx4PH_llR1Ppkd5jwaOjhQcLg,862
|
|
30
|
-
kinemotion/dropjump/analysis.py,sha256=
|
|
31
|
+
kinemotion/dropjump/analysis.py,sha256=YomuoJF_peyrBSpeT89Q5_sBgY0kEDyq7TFrtEnRLjs,28049
|
|
31
32
|
kinemotion/dropjump/api.py,sha256=uidio49CXisyWKd287CnCrM51GusG9DWAIUKGH85fpM,20584
|
|
32
33
|
kinemotion/dropjump/cli.py,sha256=gUef9nmyR5952h1WnfBGyCdFXQvzVTlCKYAjJGcO4sE,16819
|
|
33
34
|
kinemotion/dropjump/debug_overlay.py,sha256=9RQYXPRf0q2wdy6y2Ak2R4tpRceDwC8aJrXZzkmh3Wo,5942
|
|
34
35
|
kinemotion/dropjump/kinematics.py,sha256=dx4PuXKfKMKcsc_HX6sXj8rHXf9ksiZIOAIkJ4vBlY4,19637
|
|
35
|
-
kinemotion/dropjump/metrics_validator.py,sha256=
|
|
36
|
-
kinemotion/dropjump/validation_bounds.py,sha256=
|
|
36
|
+
kinemotion/dropjump/metrics_validator.py,sha256=lSfo4Lm5FHccl8ijUP6SA-kcSh50LS9hF8UIyWxcnW8,9243
|
|
37
|
+
kinemotion/dropjump/validation_bounds.py,sha256=x4yjcFxyvdMp5e7MkcoUosGLeGsxBh1Lft6h__AQ2G8,5124
|
|
37
38
|
kinemotion/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
|
-
kinemotion-0.
|
|
39
|
-
kinemotion-0.
|
|
40
|
-
kinemotion-0.
|
|
41
|
-
kinemotion-0.
|
|
42
|
-
kinemotion-0.
|
|
39
|
+
kinemotion-0.65.0.dist-info/METADATA,sha256=nueJEdfl6jQRaClefXb9-6J-pTFJN0E_XJ33Bf2mja0,26061
|
|
40
|
+
kinemotion-0.65.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
41
|
+
kinemotion-0.65.0.dist-info/entry_points.txt,sha256=zaqnAnjLvcdrk1Qvj5nvXZCZ2gp0prS7it1zTJygcIY,50
|
|
42
|
+
kinemotion-0.65.0.dist-info/licenses/LICENSE,sha256=KZajvqsHw0NoOHOi2q0FZ4NBe9HdV6oey-IPYAtHXfg,1088
|
|
43
|
+
kinemotion-0.65.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|