kinemotion 0.11.0__py3-none-any.whl → 0.11.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of kinemotion might be problematic. Click here for more details.
- kinemotion/api.py +0 -5
- kinemotion/cmj/analysis.py +72 -88
- kinemotion/cmj/cli.py +0 -5
- {kinemotion-0.11.0.dist-info → kinemotion-0.11.1.dist-info}/METADATA +1 -1
- {kinemotion-0.11.0.dist-info → kinemotion-0.11.1.dist-info}/RECORD +8 -8
- {kinemotion-0.11.0.dist-info → kinemotion-0.11.1.dist-info}/WHEEL +0 -0
- {kinemotion-0.11.0.dist-info → kinemotion-0.11.1.dist-info}/entry_points.txt +0 -0
- {kinemotion-0.11.0.dist-info → kinemotion-0.11.1.dist-info}/licenses/LICENSE +0 -0
kinemotion/api.py
CHANGED
|
@@ -754,11 +754,6 @@ def process_cmj_video(
|
|
|
754
754
|
phases = detect_cmj_phases(
|
|
755
755
|
vertical_positions,
|
|
756
756
|
video.fps,
|
|
757
|
-
velocity_threshold=params.velocity_threshold,
|
|
758
|
-
countermovement_threshold=cm_threshold,
|
|
759
|
-
min_contact_frames=params.min_contact_frames,
|
|
760
|
-
min_eccentric_frames=params.min_contact_frames,
|
|
761
|
-
use_curvature=params.use_curvature,
|
|
762
757
|
window_length=params.smoothing_window,
|
|
763
758
|
polyorder=params.polyorder,
|
|
764
759
|
)
|
kinemotion/cmj/analysis.py
CHANGED
|
@@ -102,7 +102,6 @@ def find_standing_phase(
|
|
|
102
102
|
|
|
103
103
|
def find_countermovement_start(
|
|
104
104
|
velocities: np.ndarray,
|
|
105
|
-
fps: float,
|
|
106
105
|
countermovement_threshold: float = 0.015,
|
|
107
106
|
min_eccentric_frames: int = 3,
|
|
108
107
|
standing_start: int | None = None,
|
|
@@ -114,7 +113,6 @@ def find_countermovement_start(
|
|
|
114
113
|
|
|
115
114
|
Args:
|
|
116
115
|
velocities: Array of SIGNED vertical velocities
|
|
117
|
-
fps: Video frame rate
|
|
118
116
|
countermovement_threshold: Velocity threshold for detecting downward motion (POSITIVE)
|
|
119
117
|
min_eccentric_frames: Minimum consecutive frames of downward motion
|
|
120
118
|
standing_start: Optional frame where standing phase ended
|
|
@@ -143,7 +141,6 @@ def find_countermovement_start(
|
|
|
143
141
|
def find_lowest_point(
|
|
144
142
|
positions: np.ndarray,
|
|
145
143
|
velocities: np.ndarray,
|
|
146
|
-
eccentric_start: int | None = None,
|
|
147
144
|
min_search_frame: int = 80,
|
|
148
145
|
) -> int:
|
|
149
146
|
"""
|
|
@@ -155,7 +152,6 @@ def find_lowest_point(
|
|
|
155
152
|
Args:
|
|
156
153
|
positions: Array of vertical positions (higher value = lower in video)
|
|
157
154
|
velocities: Array of SIGNED vertical velocities (positive=down, negative=up)
|
|
158
|
-
eccentric_start: Optional frame where eccentric phase started
|
|
159
155
|
min_search_frame: Minimum frame to start searching (default: frame 80)
|
|
160
156
|
|
|
161
157
|
Returns:
|
|
@@ -381,9 +377,6 @@ def find_interpolated_takeoff_landing(
|
|
|
381
377
|
positions: np.ndarray,
|
|
382
378
|
velocities: np.ndarray,
|
|
383
379
|
lowest_point_frame: int,
|
|
384
|
-
velocity_threshold: float = 0.02,
|
|
385
|
-
min_flight_frames: int = 3,
|
|
386
|
-
use_curvature: bool = True,
|
|
387
380
|
window_length: int = 5,
|
|
388
381
|
polyorder: int = 2,
|
|
389
382
|
) -> tuple[float, float] | None:
|
|
@@ -397,9 +390,6 @@ def find_interpolated_takeoff_landing(
|
|
|
397
390
|
positions: Array of vertical positions
|
|
398
391
|
velocities: Array of vertical velocities
|
|
399
392
|
lowest_point_frame: Frame at lowest point
|
|
400
|
-
velocity_threshold: Velocity threshold (unused for CMJ, kept for API compatibility)
|
|
401
|
-
min_flight_frames: Minimum consecutive frames for valid flight phase
|
|
402
|
-
use_curvature: Whether to use trajectory curvature refinement
|
|
403
393
|
window_length: Window size for derivative calculations
|
|
404
394
|
polyorder: Polynomial order for Savitzky-Golay filter
|
|
405
395
|
|
|
@@ -428,14 +418,76 @@ def find_interpolated_takeoff_landing(
|
|
|
428
418
|
return (takeoff_frame, landing_frame)
|
|
429
419
|
|
|
430
420
|
|
|
421
|
+
def _find_takeoff_frame(
|
|
422
|
+
velocities: np.ndarray, peak_height_frame: int, fps: float
|
|
423
|
+
) -> float:
|
|
424
|
+
"""Find takeoff frame as peak upward velocity before peak height."""
|
|
425
|
+
takeoff_search_start = max(0, peak_height_frame - int(fps * 0.35))
|
|
426
|
+
takeoff_search_end = peak_height_frame - 2
|
|
427
|
+
|
|
428
|
+
takeoff_velocities = velocities[takeoff_search_start:takeoff_search_end]
|
|
429
|
+
|
|
430
|
+
if len(takeoff_velocities) > 0:
|
|
431
|
+
peak_vel_idx = int(np.argmin(takeoff_velocities))
|
|
432
|
+
return float(takeoff_search_start + peak_vel_idx)
|
|
433
|
+
else:
|
|
434
|
+
return float(peak_height_frame - int(fps * 0.3))
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def _find_lowest_frame(
|
|
438
|
+
velocities: np.ndarray, positions: np.ndarray, takeoff_frame: float, fps: float
|
|
439
|
+
) -> float:
|
|
440
|
+
"""Find lowest point frame before takeoff."""
|
|
441
|
+
lowest_search_start = max(0, int(takeoff_frame) - int(fps * 0.4))
|
|
442
|
+
lowest_search_end = int(takeoff_frame)
|
|
443
|
+
|
|
444
|
+
# Find where velocity crosses from positive to negative
|
|
445
|
+
for i in range(lowest_search_end - 1, lowest_search_start, -1):
|
|
446
|
+
if i > 0 and velocities[i] < 0 and velocities[i - 1] >= 0:
|
|
447
|
+
return float(i)
|
|
448
|
+
|
|
449
|
+
# Fallback: use maximum position
|
|
450
|
+
lowest_positions = positions[lowest_search_start:lowest_search_end]
|
|
451
|
+
if len(lowest_positions) > 0:
|
|
452
|
+
lowest_idx = int(np.argmax(lowest_positions))
|
|
453
|
+
return float(lowest_search_start + lowest_idx)
|
|
454
|
+
else:
|
|
455
|
+
return float(int(takeoff_frame) - int(fps * 0.2))
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def _find_landing_frame(
|
|
459
|
+
accelerations: np.ndarray, peak_height_frame: int, fps: float
|
|
460
|
+
) -> float:
|
|
461
|
+
"""Find landing frame after peak height."""
|
|
462
|
+
landing_search_start = peak_height_frame
|
|
463
|
+
landing_search_end = min(len(accelerations), peak_height_frame + int(fps * 0.5))
|
|
464
|
+
landing_accelerations = accelerations[landing_search_start:landing_search_end]
|
|
465
|
+
|
|
466
|
+
if len(landing_accelerations) > 0:
|
|
467
|
+
landing_idx = int(np.argmin(landing_accelerations))
|
|
468
|
+
return float(landing_search_start + landing_idx)
|
|
469
|
+
else:
|
|
470
|
+
return float(peak_height_frame + int(fps * 0.3))
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def _find_standing_end(velocities: np.ndarray, lowest_point: float) -> float | None:
|
|
474
|
+
"""Find end of standing phase before lowest point."""
|
|
475
|
+
if lowest_point <= 20:
|
|
476
|
+
return None
|
|
477
|
+
|
|
478
|
+
standing_search = velocities[: int(lowest_point)]
|
|
479
|
+
low_vel = np.abs(standing_search) < 0.005
|
|
480
|
+
if np.any(low_vel):
|
|
481
|
+
standing_frames = np.nonzero(low_vel)[0]
|
|
482
|
+
if len(standing_frames) > 10:
|
|
483
|
+
return float(standing_frames[-1])
|
|
484
|
+
|
|
485
|
+
return None
|
|
486
|
+
|
|
487
|
+
|
|
431
488
|
def detect_cmj_phases(
|
|
432
489
|
positions: np.ndarray,
|
|
433
490
|
fps: float,
|
|
434
|
-
velocity_threshold: float = 0.02,
|
|
435
|
-
countermovement_threshold: float = -0.015,
|
|
436
|
-
min_contact_frames: int = 3,
|
|
437
|
-
min_eccentric_frames: int = 3,
|
|
438
|
-
use_curvature: bool = True,
|
|
439
491
|
window_length: int = 5,
|
|
440
492
|
polyorder: int = 2,
|
|
441
493
|
) -> tuple[float | None, float, float, float] | None:
|
|
@@ -451,11 +503,6 @@ def detect_cmj_phases(
|
|
|
451
503
|
Args:
|
|
452
504
|
positions: Array of vertical positions (normalized 0-1)
|
|
453
505
|
fps: Video frame rate
|
|
454
|
-
velocity_threshold: Velocity threshold (not used)
|
|
455
|
-
countermovement_threshold: Velocity threshold (not used)
|
|
456
|
-
min_contact_frames: Minimum frames for ground contact
|
|
457
|
-
min_eccentric_frames: Minimum frames for eccentric phase
|
|
458
|
-
use_curvature: Whether to use trajectory curvature refinement
|
|
459
506
|
window_length: Window size for derivative calculations
|
|
460
507
|
polyorder: Polynomial order for Savitzky-Golay filter
|
|
461
508
|
|
|
@@ -473,76 +520,13 @@ def detect_cmj_phases(
|
|
|
473
520
|
|
|
474
521
|
# Step 1: Find peak height (global minimum y = highest point in frame)
|
|
475
522
|
peak_height_frame = int(np.argmin(positions))
|
|
476
|
-
|
|
477
523
|
if peak_height_frame < 10:
|
|
478
524
|
return None # Peak too early, invalid
|
|
479
525
|
|
|
480
|
-
# Step 2: Find
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
takeoff_velocities = velocities[takeoff_search_start:takeoff_search_end]
|
|
487
|
-
|
|
488
|
-
if len(takeoff_velocities) > 0:
|
|
489
|
-
# Takeoff = peak upward velocity (most negative)
|
|
490
|
-
peak_vel_idx = int(np.argmin(takeoff_velocities))
|
|
491
|
-
takeoff_frame = float(takeoff_search_start + peak_vel_idx)
|
|
492
|
-
else:
|
|
493
|
-
# Fallback
|
|
494
|
-
takeoff_frame = float(peak_height_frame - int(fps * 0.3))
|
|
495
|
-
|
|
496
|
-
# Step 3: Find lowest point (countermovement bottom) before takeoff
|
|
497
|
-
# This is where velocity crosses from positive (squatting) to negative (jumping)
|
|
498
|
-
# Search backward from takeoff for where velocity was last positive/zero
|
|
499
|
-
lowest_search_start = max(0, int(takeoff_frame) - int(fps * 0.4))
|
|
500
|
-
lowest_search_end = int(takeoff_frame)
|
|
501
|
-
|
|
502
|
-
# Find where velocity crosses from positive to negative (transition point)
|
|
503
|
-
lowest_frame_found = None
|
|
504
|
-
for i in range(lowest_search_end - 1, lowest_search_start, -1):
|
|
505
|
-
if i > 0:
|
|
506
|
-
# Look for velocity crossing from positive/zero to negative
|
|
507
|
-
if velocities[i] < 0 and velocities[i - 1] >= 0:
|
|
508
|
-
lowest_frame_found = float(i)
|
|
509
|
-
break
|
|
510
|
-
|
|
511
|
-
# Fallback: use maximum position (lowest point in frame) if no velocity crossing
|
|
512
|
-
if lowest_frame_found is None:
|
|
513
|
-
lowest_positions = positions[lowest_search_start:lowest_search_end]
|
|
514
|
-
if len(lowest_positions) > 0:
|
|
515
|
-
lowest_idx = int(np.argmax(lowest_positions))
|
|
516
|
-
lowest_point = float(lowest_search_start + lowest_idx)
|
|
517
|
-
else:
|
|
518
|
-
lowest_point = float(int(takeoff_frame) - int(fps * 0.2))
|
|
519
|
-
else:
|
|
520
|
-
lowest_point = lowest_frame_found
|
|
521
|
-
|
|
522
|
-
# Step 4: Find landing (impact after peak height)
|
|
523
|
-
# Landing shows as large negative acceleration spike (impact deceleration)
|
|
524
|
-
landing_search_start = peak_height_frame
|
|
525
|
-
landing_search_end = min(len(accelerations), peak_height_frame + int(fps * 0.5))
|
|
526
|
-
landing_accelerations = accelerations[landing_search_start:landing_search_end]
|
|
527
|
-
|
|
528
|
-
if len(landing_accelerations) > 0:
|
|
529
|
-
# Find most negative acceleration (maximum impact deceleration)
|
|
530
|
-
# Landing acceleration should be around -0.008 to -0.010
|
|
531
|
-
landing_idx = int(np.argmin(landing_accelerations)) # Most negative = impact
|
|
532
|
-
landing_frame = float(landing_search_start + landing_idx)
|
|
533
|
-
else:
|
|
534
|
-
landing_frame = float(peak_height_frame + int(fps * 0.3))
|
|
535
|
-
|
|
536
|
-
# Optional: Find standing phase (not critical)
|
|
537
|
-
standing_end = None
|
|
538
|
-
if lowest_point > 20:
|
|
539
|
-
# Look for low-velocity period before lowest point
|
|
540
|
-
standing_search = velocities[: int(lowest_point)]
|
|
541
|
-
low_vel = np.abs(standing_search) < 0.005
|
|
542
|
-
if np.any(low_vel):
|
|
543
|
-
# Find last low-velocity frame before countermovement
|
|
544
|
-
standing_frames = np.where(low_vel)[0]
|
|
545
|
-
if len(standing_frames) > 10:
|
|
546
|
-
standing_end = float(standing_frames[-1])
|
|
526
|
+
# Step 2-4: Find all phases using helper functions
|
|
527
|
+
takeoff_frame = _find_takeoff_frame(velocities, peak_height_frame, fps)
|
|
528
|
+
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)
|
|
547
531
|
|
|
548
532
|
return (standing_end, lowest_point, takeoff_frame, landing_frame)
|
kinemotion/cmj/cli.py
CHANGED
|
@@ -474,11 +474,6 @@ def _process_single(
|
|
|
474
474
|
phases = detect_cmj_phases(
|
|
475
475
|
vertical_positions,
|
|
476
476
|
video.fps,
|
|
477
|
-
velocity_threshold=params.velocity_threshold,
|
|
478
|
-
countermovement_threshold=countermovement_threshold,
|
|
479
|
-
min_contact_frames=params.min_contact_frames,
|
|
480
|
-
min_eccentric_frames=params.min_contact_frames,
|
|
481
|
-
use_curvature=params.use_curvature,
|
|
482
477
|
window_length=params.smoothing_window,
|
|
483
478
|
polyorder=params.polyorder,
|
|
484
479
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kinemotion
|
|
3
|
-
Version: 0.11.
|
|
3
|
+
Version: 0.11.1
|
|
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,9 +1,9 @@
|
|
|
1
1
|
kinemotion/__init__.py,sha256=REBC9wrwYC_grvCS00qEOyign65Zc1sc-5buLpyqQxA,654
|
|
2
|
-
kinemotion/api.py,sha256=
|
|
2
|
+
kinemotion/api.py,sha256=mbI57PFvRK7iNU3p4PNdweuHCCU0HP9nLeUk9fx-b2g,31390
|
|
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=
|
|
6
|
-
kinemotion/cmj/cli.py,sha256=
|
|
5
|
+
kinemotion/cmj/analysis.py,sha256=ciH5f5SC1IJjG0j8nkyZRu7QYR-Al6bWCBIfOW4Q1xQ,18466
|
|
6
|
+
kinemotion/cmj/cli.py,sha256=HUXat4xecCZ7JOiyo4zfcOk5xJLFpUn5ZaTDVAJL6qo,20565
|
|
7
7
|
kinemotion/cmj/debug_overlay.py,sha256=TVDrZ16TJClftM_zhkrCzBLMs87SfYDa8H-eqfzQJ4c,17976
|
|
8
8
|
kinemotion/cmj/joint_angles.py,sha256=8ucpDGPvbt4iX3tx9eVxJEUv0laTm2Y58_--VzJCogE,9113
|
|
9
9
|
kinemotion/cmj/kinematics.py,sha256=Xl_PlC2OqMoA-zOc3SRB_GqI0AgLlJol5FTPe5J_qLc,7573
|
|
@@ -19,8 +19,8 @@ kinemotion/dropjump/cli.py,sha256=zo23qoYSpC_2BcScy-JOilcGcWGM0j3Xv0lpO0_n0wk,27
|
|
|
19
19
|
kinemotion/dropjump/debug_overlay.py,sha256=GMo-jCl5OPIv82uPxDbBVI7CsAMwATTvxZMeWfs8k8M,8701
|
|
20
20
|
kinemotion/dropjump/kinematics.py,sha256=RM_O8Kdc6aEiPIu_99N4cu-4EhYSQxtBGASJF_dmQaU,19081
|
|
21
21
|
kinemotion/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
-
kinemotion-0.11.
|
|
23
|
-
kinemotion-0.11.
|
|
24
|
-
kinemotion-0.11.
|
|
25
|
-
kinemotion-0.11.
|
|
26
|
-
kinemotion-0.11.
|
|
22
|
+
kinemotion-0.11.1.dist-info/METADATA,sha256=XRgrT5bV--WLFpW-pDbZAQpo0N5RekTUvtFMntKcQoA,18990
|
|
23
|
+
kinemotion-0.11.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
24
|
+
kinemotion-0.11.1.dist-info/entry_points.txt,sha256=zaqnAnjLvcdrk1Qvj5nvXZCZ2gp0prS7it1zTJygcIY,50
|
|
25
|
+
kinemotion-0.11.1.dist-info/licenses/LICENSE,sha256=KZajvqsHw0NoOHOi2q0FZ4NBe9HdV6oey-IPYAtHXfg,1088
|
|
26
|
+
kinemotion-0.11.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|