kinemotion 0.38.1__py3-none-any.whl → 0.39.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/api.py +34 -9
- kinemotion/cmj/analysis.py +105 -23
- kinemotion/cmj/kinematics.py +39 -6
- kinemotion/dropjump/analysis.py +6 -24
- kinemotion/dropjump/kinematics.py +16 -1
- kinemotion/dropjump/metrics_validator.py +1 -1
- {kinemotion-0.38.1.dist-info → kinemotion-0.39.0.dist-info}/METADATA +1 -1
- {kinemotion-0.38.1.dist-info → kinemotion-0.39.0.dist-info}/RECORD +11 -11
- {kinemotion-0.38.1.dist-info → kinemotion-0.39.0.dist-info}/WHEEL +0 -0
- {kinemotion-0.38.1.dist-info → kinemotion-0.39.0.dist-info}/entry_points.txt +0 -0
- {kinemotion-0.38.1.dist-info → kinemotion-0.39.0.dist-info}/licenses/LICENSE +0 -0
kinemotion/api.py
CHANGED
|
@@ -8,7 +8,7 @@ from pathlib import Path
|
|
|
8
8
|
|
|
9
9
|
import numpy as np
|
|
10
10
|
|
|
11
|
-
from .cmj.analysis import detect_cmj_phases
|
|
11
|
+
from .cmj.analysis import compute_average_hip_position, detect_cmj_phases
|
|
12
12
|
from .cmj.debug_overlay import CMJDebugOverlayRenderer
|
|
13
13
|
from .cmj.kinematics import CMJMetrics, calculate_cmj_metrics
|
|
14
14
|
from .cmj.metrics_validator import CMJMetricsValidator
|
|
@@ -263,11 +263,13 @@ def _calculate_foot_visibility(frame_landmarks: dict) -> float:
|
|
|
263
263
|
|
|
264
264
|
def _extract_vertical_positions(
|
|
265
265
|
smoothed_landmarks: list,
|
|
266
|
+
target: str = "foot",
|
|
266
267
|
) -> tuple[np.ndarray, np.ndarray]:
|
|
267
|
-
"""Extract vertical
|
|
268
|
+
"""Extract vertical positions and visibilities from smoothed landmarks.
|
|
268
269
|
|
|
269
270
|
Args:
|
|
270
271
|
smoothed_landmarks: Smoothed landmark sequence
|
|
272
|
+
target: Tracking target "foot" or "hip" (default: "foot")
|
|
271
273
|
|
|
272
274
|
Returns:
|
|
273
275
|
Tuple of (vertical_positions, visibilities) as numpy arrays
|
|
@@ -277,9 +279,23 @@ def _extract_vertical_positions(
|
|
|
277
279
|
|
|
278
280
|
for frame_landmarks in smoothed_landmarks:
|
|
279
281
|
if frame_landmarks:
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
282
|
+
if target == "hip":
|
|
283
|
+
_, y = compute_average_hip_position(frame_landmarks)
|
|
284
|
+
# For hips, we can use average visibility of hips if needed,
|
|
285
|
+
# but currently _calculate_foot_visibility is specific to feet.
|
|
286
|
+
# We'll stick to foot visibility for now as it indicates
|
|
287
|
+
# overall leg tracking quality, or we could implement
|
|
288
|
+
# _calculate_hip_visibility. For simplicity, we'll use foot
|
|
289
|
+
# visibility as a proxy for "body visibility" or just use 1.0
|
|
290
|
+
# since hips are usually visible if feet are. Actually, let's
|
|
291
|
+
# just use foot visibility for consistency in quality checks.
|
|
292
|
+
vis = _calculate_foot_visibility(frame_landmarks)
|
|
293
|
+
else:
|
|
294
|
+
_, y = compute_average_foot_position(frame_landmarks)
|
|
295
|
+
vis = _calculate_foot_visibility(frame_landmarks)
|
|
296
|
+
|
|
297
|
+
position_list.append(y)
|
|
298
|
+
visibilities_list.append(vis)
|
|
283
299
|
else:
|
|
284
300
|
position_list.append(position_list[-1] if position_list else 0.5)
|
|
285
301
|
visibilities_list.append(0.0)
|
|
@@ -915,13 +931,21 @@ def process_cmj_video(
|
|
|
915
931
|
# Apply smoothing
|
|
916
932
|
smoothed_landmarks = _apply_smoothing(landmarks_sequence, params, verbose)
|
|
917
933
|
|
|
918
|
-
# Extract
|
|
934
|
+
# Extract vertical positions
|
|
919
935
|
if verbose:
|
|
920
|
-
print("Extracting
|
|
936
|
+
print("Extracting vertical positions (Hip and Foot)...")
|
|
937
|
+
|
|
938
|
+
# Primary: Hips (for depth, velocity, general phases)
|
|
921
939
|
vertical_positions, visibilities = _extract_vertical_positions(
|
|
922
|
-
smoothed_landmarks
|
|
940
|
+
smoothed_landmarks, target="hip"
|
|
923
941
|
)
|
|
924
|
-
|
|
942
|
+
|
|
943
|
+
# Secondary: Feet (for precise landing detection)
|
|
944
|
+
foot_positions, _ = _extract_vertical_positions(
|
|
945
|
+
smoothed_landmarks, target="foot"
|
|
946
|
+
)
|
|
947
|
+
|
|
948
|
+
tracking_method = "hip_hybrid"
|
|
925
949
|
|
|
926
950
|
# Detect CMJ phases
|
|
927
951
|
if verbose:
|
|
@@ -932,6 +956,7 @@ def process_cmj_video(
|
|
|
932
956
|
video.fps,
|
|
933
957
|
window_length=params.smoothing_window,
|
|
934
958
|
polyorder=params.polyorder,
|
|
959
|
+
landing_positions=foot_positions, # Use feet for landing
|
|
935
960
|
)
|
|
936
961
|
|
|
937
962
|
if phases is None:
|
kinemotion/cmj/analysis.py
CHANGED
|
@@ -390,36 +390,90 @@ def find_lowest_frame(
|
|
|
390
390
|
|
|
391
391
|
|
|
392
392
|
def find_landing_frame(
|
|
393
|
-
accelerations: np.ndarray,
|
|
393
|
+
accelerations: np.ndarray,
|
|
394
|
+
velocities: np.ndarray,
|
|
395
|
+
peak_height_frame: int,
|
|
396
|
+
fps: float,
|
|
394
397
|
) -> float:
|
|
395
|
-
"""Find landing frame after peak height
|
|
398
|
+
"""Find landing frame after peak height.
|
|
399
|
+
|
|
400
|
+
Robust detection strategy:
|
|
401
|
+
1. Find peak downward velocity (maximum positive velocity) after peak height.
|
|
402
|
+
This corresponds to the moment just before or at initial ground contact.
|
|
403
|
+
2. Look for maximum deceleration (impact) *after* the peak velocity.
|
|
404
|
+
This filters out mid-air tracking noise/flutter that can cause false
|
|
405
|
+
deceleration spikes while the athlete is still accelerating downward.
|
|
396
406
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
407
|
+
Args:
|
|
408
|
+
accelerations: Vertical acceleration array (deriv=2)
|
|
409
|
+
velocities: Vertical velocity array (deriv=1)
|
|
410
|
+
peak_height_frame: Frame index of peak jump height
|
|
411
|
+
fps: Video frame rate
|
|
400
412
|
|
|
401
|
-
|
|
402
|
-
|
|
413
|
+
Returns:
|
|
414
|
+
Frame index of landing impact.
|
|
403
415
|
"""
|
|
404
|
-
landing_search_start = peak_height_frame
|
|
405
416
|
# Search window extended to 1.0s to accommodate all realistic flight times
|
|
406
|
-
|
|
407
|
-
|
|
417
|
+
search_end = min(len(accelerations), peak_height_frame + int(fps * 1.0))
|
|
418
|
+
|
|
419
|
+
# 1. Find peak downward velocity (max positive value)
|
|
420
|
+
# Search from peak height to end of window
|
|
421
|
+
vel_search_window = velocities[peak_height_frame:search_end]
|
|
422
|
+
|
|
423
|
+
if len(vel_search_window) == 0:
|
|
424
|
+
return float(peak_height_frame + int(fps * 0.3))
|
|
425
|
+
|
|
426
|
+
# Index relative to peak_height_frame
|
|
427
|
+
peak_vel_rel_idx = int(np.argmax(vel_search_window))
|
|
428
|
+
peak_vel_frame = peak_height_frame + peak_vel_rel_idx
|
|
429
|
+
|
|
430
|
+
# 2. Search for impact (min acceleration) starting from peak velocity
|
|
431
|
+
# We allow a small buffer (e.g., 1-2 frames) before peak velocity just in case
|
|
432
|
+
# peak velocity coincides with impact start due to smoothing
|
|
433
|
+
landing_search_start = max(peak_height_frame, peak_vel_frame - 2)
|
|
434
|
+
landing_search_end = search_end
|
|
435
|
+
|
|
408
436
|
landing_accelerations = accelerations[landing_search_start:landing_search_end]
|
|
409
437
|
|
|
410
438
|
if len(landing_accelerations) == 0:
|
|
439
|
+
# Fallback if window is empty
|
|
411
440
|
return float(peak_height_frame + int(fps * 0.3))
|
|
412
441
|
|
|
413
|
-
#
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
landing_idx = int(np.argmin(landing_accelerations_filtered)) + skip_frames
|
|
419
|
-
else:
|
|
420
|
-
landing_idx = int(np.argmin(landing_accelerations))
|
|
442
|
+
# Find minimum acceleration (maximum deceleration spike)
|
|
443
|
+
landing_rel_idx = int(np.argmin(landing_accelerations))
|
|
444
|
+
landing_frame = landing_search_start + landing_rel_idx
|
|
445
|
+
|
|
446
|
+
return float(landing_frame)
|
|
421
447
|
|
|
422
|
-
|
|
448
|
+
|
|
449
|
+
def compute_average_hip_position(
|
|
450
|
+
landmarks: dict[str, tuple[float, float, float]],
|
|
451
|
+
) -> tuple[float, float]:
|
|
452
|
+
"""
|
|
453
|
+
Compute average hip position from hip landmarks.
|
|
454
|
+
|
|
455
|
+
Args:
|
|
456
|
+
landmarks: Dictionary of landmark positions
|
|
457
|
+
|
|
458
|
+
Returns:
|
|
459
|
+
(x, y) average hip position in normalized coordinates
|
|
460
|
+
"""
|
|
461
|
+
hip_keys = ["left_hip", "right_hip"]
|
|
462
|
+
|
|
463
|
+
x_positions = []
|
|
464
|
+
y_positions = []
|
|
465
|
+
|
|
466
|
+
for key in hip_keys:
|
|
467
|
+
if key in landmarks:
|
|
468
|
+
x, y, visibility = landmarks[key]
|
|
469
|
+
if visibility > 0.5: # Only use visible landmarks
|
|
470
|
+
x_positions.append(x)
|
|
471
|
+
y_positions.append(y)
|
|
472
|
+
|
|
473
|
+
if not x_positions:
|
|
474
|
+
return (0.5, 0.5) # Default to center if no visible hips
|
|
475
|
+
|
|
476
|
+
return (float(np.mean(x_positions)), float(np.mean(y_positions)))
|
|
423
477
|
|
|
424
478
|
|
|
425
479
|
def find_standing_end(
|
|
@@ -490,6 +544,7 @@ def detect_cmj_phases(
|
|
|
490
544
|
fps: float,
|
|
491
545
|
window_length: int = 5,
|
|
492
546
|
polyorder: int = 2,
|
|
547
|
+
landing_positions: np.ndarray | None = None,
|
|
493
548
|
) -> tuple[float | None, float, float, float] | None:
|
|
494
549
|
"""
|
|
495
550
|
Detect all phases of a counter movement jump using a simplified, robust approach.
|
|
@@ -501,16 +556,18 @@ def detect_cmj_phases(
|
|
|
501
556
|
4. Find landing (impact after peak height)
|
|
502
557
|
|
|
503
558
|
Args:
|
|
504
|
-
positions: Array of vertical positions (normalized 0-1)
|
|
559
|
+
positions: Array of vertical positions (normalized 0-1). Typically Hips/CoM.
|
|
505
560
|
fps: Video frame rate
|
|
506
561
|
window_length: Window size for derivative calculations
|
|
507
562
|
polyorder: Polynomial order for Savitzky-Golay filter
|
|
563
|
+
landing_positions: Optional array of positions for landing detection
|
|
564
|
+
(e.g., Feet). If None, uses `positions` (Hips) for landing too.
|
|
508
565
|
|
|
509
566
|
Returns:
|
|
510
567
|
Tuple of (standing_end_frame, lowest_point_frame, takeoff_frame, landing_frame)
|
|
511
568
|
with fractional precision, or None if phases cannot be detected.
|
|
512
569
|
"""
|
|
513
|
-
# Compute SIGNED velocities and accelerations
|
|
570
|
+
# Compute SIGNED velocities and accelerations for primary signal (Hips)
|
|
514
571
|
velocities = compute_signed_velocity(
|
|
515
572
|
positions, window_length=window_length, polyorder=polyorder
|
|
516
573
|
)
|
|
@@ -526,7 +583,32 @@ def detect_cmj_phases(
|
|
|
526
583
|
# Step 2-4: Find all phases using helper functions
|
|
527
584
|
takeoff_frame = find_takeoff_frame(velocities, peak_height_frame, fps)
|
|
528
585
|
lowest_point = find_lowest_frame(velocities, positions, takeoff_frame, fps)
|
|
529
|
-
landing_frame = find_landing_frame(accelerations, peak_height_frame, fps)
|
|
530
|
-
standing_end = find_standing_end(velocities, lowest_point, positions, accelerations)
|
|
531
586
|
|
|
587
|
+
# Determine landing frame
|
|
588
|
+
if landing_positions is not None:
|
|
589
|
+
# Use specific landing signal (Feet) for landing detection
|
|
590
|
+
landing_velocities = compute_signed_velocity(
|
|
591
|
+
landing_positions, window_length=window_length, polyorder=polyorder
|
|
592
|
+
)
|
|
593
|
+
landing_accelerations = compute_acceleration_from_derivative(
|
|
594
|
+
landing_positions, window_length=window_length, polyorder=polyorder
|
|
595
|
+
)
|
|
596
|
+
# We still reference peak_height_frame from Hips, as Feet peak
|
|
597
|
+
# might be different/noisy but generally they align in time.
|
|
598
|
+
landing_frame = find_landing_frame(
|
|
599
|
+
landing_accelerations,
|
|
600
|
+
landing_velocities,
|
|
601
|
+
peak_height_frame,
|
|
602
|
+
fps,
|
|
603
|
+
)
|
|
604
|
+
else:
|
|
605
|
+
# Use primary signal (Hips)
|
|
606
|
+
landing_frame = find_landing_frame(
|
|
607
|
+
accelerations,
|
|
608
|
+
velocities,
|
|
609
|
+
peak_height_frame,
|
|
610
|
+
fps,
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
standing_end = find_standing_end(velocities, lowest_point, positions, accelerations)
|
|
532
614
|
return (standing_end, lowest_point, takeoff_frame, landing_frame)
|
kinemotion/cmj/kinematics.py
CHANGED
|
@@ -180,6 +180,24 @@ def calculate_cmj_metrics(
|
|
|
180
180
|
g = 9.81 # gravity in m/s^2
|
|
181
181
|
jump_height = (g * flight_time**2) / 8
|
|
182
182
|
|
|
183
|
+
# Determine scaling factor (meters per normalized unit)
|
|
184
|
+
# We use the flight phase displacement in normalized units compared to
|
|
185
|
+
# kinematic jump height
|
|
186
|
+
flight_start_idx = int(takeoff_frame)
|
|
187
|
+
flight_end_idx = int(landing_frame)
|
|
188
|
+
flight_positions = positions[flight_start_idx:flight_end_idx]
|
|
189
|
+
|
|
190
|
+
scale_factor = 0.0
|
|
191
|
+
if len(flight_positions) > 0:
|
|
192
|
+
# Peak height is minimum y value (highest point in frame)
|
|
193
|
+
peak_flight_pos = np.min(flight_positions)
|
|
194
|
+
takeoff_pos = positions[flight_start_idx]
|
|
195
|
+
# Displacement is upward (takeoff_pos - peak_pos) because y decreases upward
|
|
196
|
+
flight_displacement = takeoff_pos - peak_flight_pos
|
|
197
|
+
|
|
198
|
+
if flight_displacement > 0.001: # Avoid division by zero or noise
|
|
199
|
+
scale_factor = jump_height / flight_displacement
|
|
200
|
+
|
|
183
201
|
# Calculate countermovement depth
|
|
184
202
|
if standing_start_frame is not None:
|
|
185
203
|
standing_position = positions[int(standing_start_frame)]
|
|
@@ -188,7 +206,10 @@ def calculate_cmj_metrics(
|
|
|
188
206
|
standing_position = positions[0]
|
|
189
207
|
|
|
190
208
|
lowest_position = positions[int(lowest_point_frame)]
|
|
191
|
-
|
|
209
|
+
# Depth in normalized units
|
|
210
|
+
depth_normalized = abs(standing_position - lowest_position)
|
|
211
|
+
# Convert to meters
|
|
212
|
+
countermovement_depth = depth_normalized * scale_factor
|
|
192
213
|
|
|
193
214
|
# Calculate phase durations
|
|
194
215
|
if standing_start_frame is not None:
|
|
@@ -201,8 +222,12 @@ def calculate_cmj_metrics(
|
|
|
201
222
|
|
|
202
223
|
concentric_duration = (takeoff_frame - lowest_point_frame) / fps
|
|
203
224
|
|
|
225
|
+
# Velocity scaling factor: units/frame -> meters/second
|
|
226
|
+
# v_m_s = v_units_frame * fps * scale_factor
|
|
227
|
+
velocity_scale = scale_factor * fps
|
|
228
|
+
|
|
204
229
|
# Calculate peak velocities
|
|
205
|
-
# Eccentric phase:
|
|
230
|
+
# Eccentric phase: Downward motion = Positive velocity in image coords
|
|
206
231
|
if standing_start_frame is not None:
|
|
207
232
|
eccentric_start_idx = int(standing_start_frame)
|
|
208
233
|
else:
|
|
@@ -212,18 +237,26 @@ def calculate_cmj_metrics(
|
|
|
212
237
|
eccentric_velocities = velocities[eccentric_start_idx:eccentric_end_idx]
|
|
213
238
|
|
|
214
239
|
if len(eccentric_velocities) > 0:
|
|
215
|
-
# Peak eccentric velocity is
|
|
216
|
-
|
|
240
|
+
# Peak eccentric velocity is maximum positive value (fastest downward)
|
|
241
|
+
# We take max and ensure it's positive (it should be)
|
|
242
|
+
peak_eccentric_velocity = float(np.max(eccentric_velocities)) * velocity_scale
|
|
243
|
+
# If max is negative (weird), it means no downward motion detected
|
|
244
|
+
if peak_eccentric_velocity < 0:
|
|
245
|
+
peak_eccentric_velocity = 0.0
|
|
217
246
|
else:
|
|
218
247
|
peak_eccentric_velocity = 0.0
|
|
219
248
|
|
|
220
|
-
# Concentric phase:
|
|
249
|
+
# Concentric phase: Upward motion = Negative velocity in image coords
|
|
221
250
|
concentric_start_idx = int(lowest_point_frame)
|
|
222
251
|
concentric_end_idx = int(takeoff_frame)
|
|
223
252
|
concentric_velocities = velocities[concentric_start_idx:concentric_end_idx]
|
|
224
253
|
|
|
225
254
|
if len(concentric_velocities) > 0:
|
|
226
|
-
|
|
255
|
+
# Peak concentric velocity is minimum value (most negative = fastest upward)
|
|
256
|
+
# We take abs to report magnitude
|
|
257
|
+
peak_concentric_velocity = (
|
|
258
|
+
abs(float(np.min(concentric_velocities))) * velocity_scale
|
|
259
|
+
)
|
|
227
260
|
else:
|
|
228
261
|
peak_concentric_velocity = 0.0
|
|
229
262
|
|
kinemotion/dropjump/analysis.py
CHANGED
|
@@ -704,8 +704,8 @@ def find_landing_from_acceleration(
|
|
|
704
704
|
"""
|
|
705
705
|
Find landing frame by detecting impact acceleration after takeoff.
|
|
706
706
|
|
|
707
|
-
|
|
708
|
-
|
|
707
|
+
Detects the moment of initial ground contact, characterized by a sharp
|
|
708
|
+
deceleration (positive acceleration spike) as downward velocity is arrested.
|
|
709
709
|
|
|
710
710
|
Args:
|
|
711
711
|
positions: Array of vertical positions (normalized 0-1)
|
|
@@ -736,29 +736,11 @@ def find_landing_from_acceleration(
|
|
|
736
736
|
if landing_search_end <= landing_search_start:
|
|
737
737
|
return min(len(positions) - 1, peak_frame + int(fps * 0.2))
|
|
738
738
|
|
|
739
|
-
# Find impact: maximum
|
|
739
|
+
# Find impact: maximum negative acceleration after peak (deceleration on impact)
|
|
740
|
+
# The impact creates a large upward force (negative acceleration in Y-down)
|
|
740
741
|
landing_accelerations = accelerations[landing_search_start:landing_search_end]
|
|
741
|
-
impact_idx = int(np.
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
# After acceleration peak, look for position stabilization (full ground contact)
|
|
745
|
-
# Check where vertical position stops decreasing (athlete stops compressing)
|
|
746
|
-
stabilization_search_start = impact_frame
|
|
747
|
-
stabilization_search_end = min(len(positions), impact_frame + int(fps * 0.2))
|
|
748
|
-
|
|
749
|
-
landing_frame = impact_frame
|
|
750
|
-
if stabilization_search_end > stabilization_search_start + 3:
|
|
751
|
-
# Find where position reaches maximum (lowest point) and starts stabilizing
|
|
752
|
-
search_positions = positions[
|
|
753
|
-
stabilization_search_start:stabilization_search_end
|
|
754
|
-
]
|
|
755
|
-
|
|
756
|
-
# Look for the frame where position reaches its maximum (deepest landing)
|
|
757
|
-
max_pos_idx = int(np.argmax(search_positions))
|
|
758
|
-
|
|
759
|
-
# Landing is just after max position (athlete at deepest landing compression)
|
|
760
|
-
landing_frame = stabilization_search_start + max_pos_idx
|
|
761
|
-
landing_frame = min(len(positions) - 1, landing_frame)
|
|
742
|
+
impact_idx = int(np.argmin(landing_accelerations))
|
|
743
|
+
landing_frame = landing_search_start + impact_idx
|
|
762
744
|
|
|
763
745
|
return landing_frame
|
|
764
746
|
|
|
@@ -28,6 +28,7 @@ class DropJumpDataDict(TypedDict, total=False):
|
|
|
28
28
|
flight_time_ms: float | None
|
|
29
29
|
jump_height_m: float | None
|
|
30
30
|
jump_height_kinematic_m: float | None
|
|
31
|
+
jump_height_trajectory_m: float | None
|
|
31
32
|
jump_height_trajectory_normalized: float | None
|
|
32
33
|
contact_start_frame: int | None
|
|
33
34
|
contact_end_frame: int | None
|
|
@@ -56,7 +57,10 @@ class DropJumpMetrics:
|
|
|
56
57
|
self.flight_time: float | None = None
|
|
57
58
|
self.jump_height: float | None = None
|
|
58
59
|
self.jump_height_kinematic: float | None = None # From flight time
|
|
59
|
-
|
|
60
|
+
# From position tracking (normalized)
|
|
61
|
+
self.jump_height_trajectory: float | None = None
|
|
62
|
+
# From position tracking (meters)
|
|
63
|
+
self.jump_height_trajectory_m: float | None = None
|
|
60
64
|
self.drop_start_frame: int | None = None # Frame when athlete leaves box
|
|
61
65
|
self.contact_start_frame: int | None = None
|
|
62
66
|
self.contact_end_frame: int | None = None
|
|
@@ -90,6 +94,9 @@ class DropJumpMetrics:
|
|
|
90
94
|
"jump_height_kinematic_m": format_float_metric(
|
|
91
95
|
self.jump_height_kinematic, 1, 3
|
|
92
96
|
),
|
|
97
|
+
"jump_height_trajectory_m": format_float_metric(
|
|
98
|
+
self.jump_height_trajectory_m, 1, 3
|
|
99
|
+
),
|
|
93
100
|
"jump_height_trajectory_normalized": format_float_metric(
|
|
94
101
|
self.jump_height_trajectory, 1, 4
|
|
95
102
|
),
|
|
@@ -408,6 +415,14 @@ def _analyze_flight_phase(
|
|
|
408
415
|
height_normalized = float(takeoff_position - peak_position)
|
|
409
416
|
metrics.jump_height_trajectory = height_normalized
|
|
410
417
|
|
|
418
|
+
# Calculate scale factor and metric height
|
|
419
|
+
# Scale factor = kinematic height / normalized height
|
|
420
|
+
if height_normalized > 0.001:
|
|
421
|
+
scale_factor = metrics.jump_height_kinematic / height_normalized
|
|
422
|
+
metrics.jump_height_trajectory_m = height_normalized * scale_factor
|
|
423
|
+
else:
|
|
424
|
+
metrics.jump_height_trajectory_m = 0.0
|
|
425
|
+
|
|
411
426
|
|
|
412
427
|
def calculate_drop_jump_metrics(
|
|
413
428
|
contact_states: list[ContactState],
|
|
@@ -83,7 +83,7 @@ class DropJumpMetricsValidator(MetricsValidator):
|
|
|
83
83
|
flight_time_ms = data.get("flight_time_ms")
|
|
84
84
|
jump_height_m = data.get("jump_height_m")
|
|
85
85
|
jump_height_kinematic_m = data.get("jump_height_kinematic_m")
|
|
86
|
-
jump_height_trajectory_m = data.get("
|
|
86
|
+
jump_height_trajectory_m = data.get("jump_height_trajectory_m")
|
|
87
87
|
|
|
88
88
|
# Validate individual metrics
|
|
89
89
|
if contact_time_ms is not None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kinemotion
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.39.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
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
kinemotion/__init__.py,sha256=wPItmyGJUOFM6GPRVhAEvRz0-ErI7e2qiUREYJ9EfPQ,943
|
|
2
|
-
kinemotion/api.py,sha256=
|
|
2
|
+
kinemotion/api.py,sha256=1MsBvkznlP_OaBZ37a0hG9uMTESv2iJMHR01tnPv5sA,41046
|
|
3
3
|
kinemotion/cli.py,sha256=cqYV_7URH0JUDy1VQ_EDLv63FmNO4Ns20m6s1XAjiP4,464
|
|
4
4
|
kinemotion/cmj/__init__.py,sha256=Ynv0-Oco4I3Y1Ubj25m3h9h2XFqeNwpAewXmAYOmwfU,127
|
|
5
|
-
kinemotion/cmj/analysis.py,sha256=
|
|
5
|
+
kinemotion/cmj/analysis.py,sha256=qtULzp9uYzm5M0_Qu5YGJpuwjg9fz1VKAg6xg4NJxvM,21639
|
|
6
6
|
kinemotion/cmj/cli.py,sha256=Mj2h9It1jVjAauvtCxfLWTRijj7zbYhxZuebhw2Zz6w,10828
|
|
7
7
|
kinemotion/cmj/debug_overlay.py,sha256=fXmWoHhqMLGo4vTtB6Ezs3yLUDOLw63zLIgU2gFlJQU,15892
|
|
8
8
|
kinemotion/cmj/joint_angles.py,sha256=HmheIEiKcQz39cRezk4h-htorOhGNPsqKIR9RsAEKts,9960
|
|
9
|
-
kinemotion/cmj/kinematics.py,sha256=
|
|
9
|
+
kinemotion/cmj/kinematics.py,sha256=Lq9m9MNQxnXv31VhKmXVrlM7rRkhi8PxW50N_CC8_8Y,11860
|
|
10
10
|
kinemotion/cmj/metrics_validator.py,sha256=V_fmlczYH06SBtwqESv-IfGi3wDsIy3RQbd7VwOyNo0,31359
|
|
11
11
|
kinemotion/cmj/validation_bounds.py,sha256=9ZTo68fl3ooyWjXXyTMRLpK9tFANa_rQf3oHhq7iQGE,11995
|
|
12
12
|
kinemotion/core/__init__.py,sha256=HsqolRa60cW3vrG8F9Lvr9WvWcs5hCmsTzSgo7imi-4,1278
|
|
@@ -24,15 +24,15 @@ kinemotion/core/smoothing.py,sha256=GAfC-jxu1eqNyDjsUXqUBicKx9um5hrk49wz1FxfRNM,
|
|
|
24
24
|
kinemotion/core/validation.py,sha256=LmKfSl4Ayw3DgwKD9IrhsPdzp5ia4drLsHA2UuU1SCM,6310
|
|
25
25
|
kinemotion/core/video_io.py,sha256=fDdyYVIKqUSgCjBJa8l_S0SrDPDAhrWYfsDBNRuz1oM,7549
|
|
26
26
|
kinemotion/dropjump/__init__.py,sha256=tC3H3BrCg8Oj-db-Vrtx4PH_llR1Ppkd5jwaOjhQcLg,862
|
|
27
|
-
kinemotion/dropjump/analysis.py,sha256=
|
|
27
|
+
kinemotion/dropjump/analysis.py,sha256=MjxO-vps0nz_hXlnGk7cgq3jFenJYzsM0VVpHwnHXsM,27935
|
|
28
28
|
kinemotion/dropjump/cli.py,sha256=n_Wfv3AC6YIgRPYhO3F2nTSai0NR7fh95nAoWjryQeY,16250
|
|
29
29
|
kinemotion/dropjump/debug_overlay.py,sha256=LkPw6ucb7beoYWS4L-Lvjs1KLCm5wAWDAfiznUeV2IQ,5668
|
|
30
|
-
kinemotion/dropjump/kinematics.py,sha256=
|
|
31
|
-
kinemotion/dropjump/metrics_validator.py,sha256=
|
|
30
|
+
kinemotion/dropjump/kinematics.py,sha256=kH-XM66wlOCYMpjvyb6_Qh5ZebyOfFZ47rmhgE1Tww4,19404
|
|
31
|
+
kinemotion/dropjump/metrics_validator.py,sha256=CrTlGup8q2kyPXtA6HNwm7_yq0AsBaDllG7RVZdXmYA,9342
|
|
32
32
|
kinemotion/dropjump/validation_bounds.py,sha256=5b4I3CKPybuvrbn-nP5yCcGF_sH4Vtyw3a5AWWvWnBk,4645
|
|
33
33
|
kinemotion/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
|
-
kinemotion-0.
|
|
35
|
-
kinemotion-0.
|
|
36
|
-
kinemotion-0.
|
|
37
|
-
kinemotion-0.
|
|
38
|
-
kinemotion-0.
|
|
34
|
+
kinemotion-0.39.0.dist-info/METADATA,sha256=ytvO76fvfntV_Vpjkc-4BezahvOO91a2I6X3PUM0eAk,26020
|
|
35
|
+
kinemotion-0.39.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
36
|
+
kinemotion-0.39.0.dist-info/entry_points.txt,sha256=zaqnAnjLvcdrk1Qvj5nvXZCZ2gp0prS7it1zTJygcIY,50
|
|
37
|
+
kinemotion-0.39.0.dist-info/licenses/LICENSE,sha256=KZajvqsHw0NoOHOi2q0FZ4NBe9HdV6oey-IPYAtHXfg,1088
|
|
38
|
+
kinemotion-0.39.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|