kinemotion 0.71.0__py3-none-any.whl → 0.72.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of kinemotion might be problematic. Click here for more details.
- kinemotion/__init__.py +1 -1
- kinemotion/api.py +2 -2
- kinemotion/cli.py +1 -1
- kinemotion/cmj/analysis.py +2 -4
- kinemotion/cmj/api.py +9 -7
- kinemotion/cmj/debug_overlay.py +154 -286
- kinemotion/cmj/joint_angles.py +96 -31
- kinemotion/cmj/metrics_validator.py +22 -29
- kinemotion/cmj/validation_bounds.py +1 -18
- kinemotion/core/__init__.py +0 -2
- kinemotion/core/auto_tuning.py +95 -100
- kinemotion/core/debug_overlay_utils.py +142 -15
- kinemotion/core/experimental.py +55 -51
- kinemotion/core/filtering.py +15 -11
- kinemotion/core/overlay_constants.py +61 -0
- kinemotion/core/pipeline_utils.py +1 -1
- kinemotion/core/pose.py +47 -98
- kinemotion/core/smoothing.py +65 -51
- kinemotion/core/types.py +15 -0
- kinemotion/core/validation.py +6 -7
- kinemotion/core/video_io.py +14 -9
- kinemotion/{dropjump → dj}/__init__.py +2 -2
- kinemotion/{dropjump → dj}/analysis.py +192 -75
- kinemotion/{dropjump → dj}/api.py +13 -17
- kinemotion/{dropjump → dj}/cli.py +62 -78
- kinemotion/dj/debug_overlay.py +241 -0
- kinemotion/{dropjump → dj}/kinematics.py +106 -44
- kinemotion/{dropjump → dj}/metrics_validator.py +1 -1
- kinemotion/{dropjump → dj}/validation_bounds.py +1 -1
- {kinemotion-0.71.0.dist-info → kinemotion-0.72.0.dist-info}/METADATA +1 -1
- kinemotion-0.72.0.dist-info/RECORD +50 -0
- kinemotion/dropjump/debug_overlay.py +0 -182
- kinemotion-0.71.0.dist-info/RECORD +0 -49
- {kinemotion-0.71.0.dist-info → kinemotion-0.72.0.dist-info}/WHEEL +0 -0
- {kinemotion-0.71.0.dist-info → kinemotion-0.72.0.dist-info}/entry_points.txt +0 -0
- {kinemotion-0.71.0.dist-info → kinemotion-0.72.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -234,6 +234,16 @@ def _identify_main_contact_phase(
|
|
|
234
234
|
) -> tuple[int, int, bool]:
|
|
235
235
|
"""Identify the main contact phase and determine if it's a drop jump.
|
|
236
236
|
|
|
237
|
+
Drop jump detection strategy:
|
|
238
|
+
1. With position-based filtering, box period is classified as IN_AIR
|
|
239
|
+
2. Pattern: IN_AIR(box+drop) → ON_GROUND(contact) → IN_AIR(flight) → ON_GROUND(land)
|
|
240
|
+
3. The FIRST ground phase is the contact phase (before the flight)
|
|
241
|
+
4. The LAST ground phase is the landing (after the flight)
|
|
242
|
+
|
|
243
|
+
The key differentiator from regular jump:
|
|
244
|
+
- Drop jump: starts with IN_AIR, has 2+ ground phases with air between them
|
|
245
|
+
- Regular jump: starts with ON_GROUND, may have multiple phases
|
|
246
|
+
|
|
237
247
|
Args:
|
|
238
248
|
phases: All phase tuples
|
|
239
249
|
ground_phases: Ground phases with indices
|
|
@@ -247,34 +257,43 @@ def _identify_main_contact_phase(
|
|
|
247
257
|
contact_start, contact_end = ground_phases[0][0], ground_phases[0][1]
|
|
248
258
|
is_drop_jump = False
|
|
249
259
|
|
|
250
|
-
#
|
|
260
|
+
# Check if this looks like a drop jump pattern:
|
|
261
|
+
# Pattern: starts with IN_AIR → ON_GROUND → IN_AIR → ON_GROUND
|
|
251
262
|
if air_phases_indexed and len(ground_phases) >= 2:
|
|
263
|
+
_, _, first_air_idx = air_phases_indexed[0]
|
|
252
264
|
first_ground_start, first_ground_end, first_ground_idx = ground_phases[0]
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
#
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
foot_y_positions, first_ground_start, first_ground_end
|
|
265
|
-
)
|
|
266
|
-
second_ground_start, second_ground_end, _ = ground_after_air[0]
|
|
267
|
-
second_ground_y = _compute_robust_phase_position(
|
|
268
|
-
foot_y_positions, second_ground_start, second_ground_end
|
|
269
|
-
)
|
|
270
|
-
|
|
271
|
-
# If first ground is significantly higher (>7% of frame), it's a drop jump
|
|
272
|
-
# Increased from 0.05 to 0.07 with 11-frame temporal averaging
|
|
273
|
-
# for reproducibility (balances detection sensitivity with noise robustness)
|
|
274
|
-
# Note: MediaPipe has inherent non-determinism (Google issue #3945)
|
|
275
|
-
if second_ground_y - first_ground_y > 0.07:
|
|
265
|
+
|
|
266
|
+
# Drop jump pattern: first phase is IN_AIR (athlete on box/dropping)
|
|
267
|
+
# followed by ground contact, then flight, then landing
|
|
268
|
+
if first_air_idx == 0 and first_ground_idx == 1:
|
|
269
|
+
# First phase is air (box + drop), second phase is ground (contact)
|
|
270
|
+
# Check if there's a flight phase after contact
|
|
271
|
+
air_after_contact = [
|
|
272
|
+
(s, e, i) for s, e, i in air_phases_indexed if i > first_ground_idx
|
|
273
|
+
]
|
|
274
|
+
if air_after_contact:
|
|
275
|
+
# This is a drop jump: first ground = contact, last ground = landing
|
|
276
276
|
is_drop_jump = True
|
|
277
|
-
contact_start, contact_end =
|
|
277
|
+
contact_start, contact_end = first_ground_start, first_ground_end
|
|
278
|
+
|
|
279
|
+
# Legacy detection: first ground is on elevated box (lower y)
|
|
280
|
+
# This handles cases where box level IS detected as ground
|
|
281
|
+
if not is_drop_jump and first_ground_idx < first_air_idx:
|
|
282
|
+
ground_after_air = [
|
|
283
|
+
(start, end, idx) for start, end, idx in ground_phases if idx > first_air_idx
|
|
284
|
+
]
|
|
285
|
+
if ground_after_air:
|
|
286
|
+
first_ground_y = _compute_robust_phase_position(
|
|
287
|
+
foot_y_positions, first_ground_start, first_ground_end
|
|
288
|
+
)
|
|
289
|
+
second_ground_start, second_ground_end, _ = ground_after_air[0]
|
|
290
|
+
second_ground_y = _compute_robust_phase_position(
|
|
291
|
+
foot_y_positions, second_ground_start, second_ground_end
|
|
292
|
+
)
|
|
293
|
+
# If first ground is significantly higher (>7% of frame), it's a drop jump
|
|
294
|
+
if second_ground_y - first_ground_y > 0.07:
|
|
295
|
+
is_drop_jump = True
|
|
296
|
+
contact_start, contact_end = second_ground_start, second_ground_end
|
|
278
297
|
|
|
279
298
|
if not is_drop_jump:
|
|
280
299
|
# Regular jump: use longest ground contact phase
|
|
@@ -317,6 +336,30 @@ def _find_precise_phase_timing(
|
|
|
317
336
|
return contact_start_frac, contact_end_frac
|
|
318
337
|
|
|
319
338
|
|
|
339
|
+
def _find_landing_from_phases(
|
|
340
|
+
phases: list[tuple[int, int, ContactState]],
|
|
341
|
+
flight_start: int,
|
|
342
|
+
) -> int | None:
|
|
343
|
+
"""Find landing frame from phase detection.
|
|
344
|
+
|
|
345
|
+
Looks for the first ON_GROUND phase that starts after the flight_start frame.
|
|
346
|
+
This represents the first ground contact after the reactive jump.
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
phases: List of (start, end, state) phase tuples
|
|
350
|
+
flight_start: Frame where flight begins (takeoff)
|
|
351
|
+
|
|
352
|
+
Returns:
|
|
353
|
+
Landing frame (start of landing phase), or None if not found
|
|
354
|
+
"""
|
|
355
|
+
for start, _, state in phases:
|
|
356
|
+
if state == ContactState.ON_GROUND and start > flight_start:
|
|
357
|
+
# Found the landing phase - return its start frame
|
|
358
|
+
return start
|
|
359
|
+
|
|
360
|
+
return None
|
|
361
|
+
|
|
362
|
+
|
|
320
363
|
def _analyze_flight_phase(
|
|
321
364
|
metrics: DropJumpMetrics,
|
|
322
365
|
phases: list[tuple[int, int, ContactState]],
|
|
@@ -345,22 +388,20 @@ def _analyze_flight_phase(
|
|
|
345
388
|
# Find takeoff frame (end of ground contact)
|
|
346
389
|
flight_start = contact_end
|
|
347
390
|
|
|
348
|
-
#
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
)
|
|
391
|
+
# Use phase detection for landing (more accurate than position-based)
|
|
392
|
+
# Find the next ON_GROUND phase after the flight phase
|
|
393
|
+
flight_end = _find_landing_from_phases(phases, flight_start)
|
|
352
394
|
|
|
353
|
-
#
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
metrics.flight_end_frame = flight_end
|
|
395
|
+
# If phase detection fails, fall back to position-based detection
|
|
396
|
+
if flight_end is None:
|
|
397
|
+
accelerations = compute_acceleration_from_derivative(
|
|
398
|
+
foot_y_positions, window_length=smoothing_window, polyorder=polyorder
|
|
399
|
+
)
|
|
400
|
+
flight_end = find_landing_from_acceleration(
|
|
401
|
+
foot_y_positions, accelerations, flight_start, fps
|
|
402
|
+
)
|
|
362
403
|
|
|
363
|
-
# Find precise sub-frame timing for takeoff
|
|
404
|
+
# Find precise sub-frame timing for takeoff and landing
|
|
364
405
|
flight_start_frac = float(flight_start)
|
|
365
406
|
flight_end_frac = float(flight_end)
|
|
366
407
|
|
|
@@ -373,6 +414,20 @@ def _analyze_flight_phase(
|
|
|
373
414
|
flight_start_frac = end_frac
|
|
374
415
|
break
|
|
375
416
|
|
|
417
|
+
# Find interpolated landing (start of landing ON_GROUND phase)
|
|
418
|
+
for start_frac, _, state in interpolated_phases:
|
|
419
|
+
if state == ContactState.ON_GROUND and int(start_frac) >= flight_end - 2:
|
|
420
|
+
flight_end_frac = start_frac
|
|
421
|
+
break
|
|
422
|
+
|
|
423
|
+
# Refine landing frame using floor of interpolated value
|
|
424
|
+
# This compensates for velocity-based detection being ~1-2 frames late
|
|
425
|
+
refined_flight_end = int(np.floor(flight_end_frac))
|
|
426
|
+
|
|
427
|
+
# Store integer frame indices (refined using interpolated values)
|
|
428
|
+
metrics.flight_start_frame = flight_start
|
|
429
|
+
metrics.flight_end_frame = refined_flight_end
|
|
430
|
+
|
|
376
431
|
# Calculate flight time
|
|
377
432
|
flight_frames_precise = flight_end_frac - flight_start_frac
|
|
378
433
|
metrics.flight_time = flight_frames_precise / fps
|
|
@@ -497,15 +552,22 @@ def calculate_drop_jump_metrics(
|
|
|
497
552
|
phases, ground_phases, air_phases_indexed, foot_y_positions
|
|
498
553
|
)
|
|
499
554
|
|
|
500
|
-
#
|
|
501
|
-
metrics.contact_start_frame = contact_start
|
|
502
|
-
metrics.contact_end_frame = contact_end
|
|
503
|
-
|
|
504
|
-
# Find precise timing for contact phase
|
|
555
|
+
# Find precise timing for contact phase (uses curvature refinement)
|
|
505
556
|
contact_start_frac, contact_end_frac = _find_precise_phase_timing(
|
|
506
557
|
contact_start, contact_end, interpolated_phases
|
|
507
558
|
)
|
|
508
559
|
|
|
560
|
+
# Refine contact_start using floor of interpolated value
|
|
561
|
+
# This compensates for velocity-based detection being ~1-2 frames late
|
|
562
|
+
# because velocity settles AFTER initial impact. Using floor() biases
|
|
563
|
+
# toward earlier detection, matching the moment of first ground contact.
|
|
564
|
+
refined_contact_start = int(np.floor(contact_start_frac))
|
|
565
|
+
|
|
566
|
+
# Store integer frame indices (refined start, raw end)
|
|
567
|
+
# Contact end (takeoff) uses raw value as velocity-based detection is accurate
|
|
568
|
+
metrics.contact_start_frame = refined_contact_start
|
|
569
|
+
metrics.contact_end_frame = contact_end
|
|
570
|
+
|
|
509
571
|
# Calculate ground contact time
|
|
510
572
|
contact_frames_precise = contact_end_frac - contact_start_frac
|
|
511
573
|
metrics.ground_contact_time = contact_frames_precise / fps
|
|
@@ -124,7 +124,7 @@ def _classify_combined_score(combined_score: float) -> AthleteProfile:
|
|
|
124
124
|
return AthleteProfile.ELITE
|
|
125
125
|
|
|
126
126
|
|
|
127
|
-
def estimate_athlete_profile(metrics: MetricsDict,
|
|
127
|
+
def estimate_athlete_profile(metrics: MetricsDict, _gender: str | None = None) -> AthleteProfile:
|
|
128
128
|
"""Estimate athlete profile from drop jump metrics.
|
|
129
129
|
|
|
130
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.72.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
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
kinemotion/__init__.py,sha256=jlhHJlxZJbp7CPfRWMlxwfDISBCdHadjigmXvcahXxU,1055
|
|
2
|
+
kinemotion/api.py,sha256=1GMi_7SbU3EyDdDRtnF55roB8iMR0t91qGOkqa4E6sI,1028
|
|
3
|
+
kinemotion/cli.py,sha256=ugrc1Dpx7abYGEyIDcslzNu8KR0VoOoGGUGUm87E_kQ,620
|
|
4
|
+
kinemotion/cmj/__init__.py,sha256=SkAw9ka8Yd1Qfv9hcvk22m3EfucROzYrSNGNF5kDzho,113
|
|
5
|
+
kinemotion/cmj/analysis.py,sha256=EQydClIbNkIj-FmCZGaPQe-COVW8fbO3139i9z1vomA,23643
|
|
6
|
+
kinemotion/cmj/api.py,sha256=P_lbqEqAKPO5n1Xn4IQZKNj9nLaO3ljkN2PgqvExGXU,18435
|
|
7
|
+
kinemotion/cmj/cli.py,sha256=P2b77IIw6kqTSIkncxlShzhmjIwqMFBNd-pZxYP-TsI,9918
|
|
8
|
+
kinemotion/cmj/debug_overlay.py,sha256=vF5Apiz8zDRpgrVzf52manLW99m1kHQAPSdUkar5rPs,11474
|
|
9
|
+
kinemotion/cmj/joint_angles.py,sha256=by5M4LDtUfd2_Z9DmcgUl0nsvarsBYjgsE8KWWYcn08,11255
|
|
10
|
+
kinemotion/cmj/kinematics.py,sha256=KwA8uSj3g1SeNf0NXMSHsp3gIw6Gfa-6QWIwdYdRXYw,13362
|
|
11
|
+
kinemotion/cmj/metrics_validator.py,sha256=IQofafpwLCXER3ucZXNfiJKFFKPOVxXnC4BNLHOMnNY,30013
|
|
12
|
+
kinemotion/cmj/validation_bounds.py,sha256=-0iXDhH-RntiGZi_Co22V6qtA5D-hLzkrPkVcfoNd2U,11343
|
|
13
|
+
kinemotion/core/__init__.py,sha256=8hMvfNK7v_eqswuk_J5s5FRGvPtp2-R4kasVMGchFkM,1766
|
|
14
|
+
kinemotion/core/auto_tuning.py,sha256=rliPTLueMbOjYRb4hjb0af7DVMtxLT92wpnVve75GvA,10478
|
|
15
|
+
kinemotion/core/cli_utils.py,sha256=sQPbT6XWWau-sm9yuN5c3eS5xNzoQGGXwSz6hQXtRvM,1859
|
|
16
|
+
kinemotion/core/debug_overlay_utils.py,sha256=QaVkHuFZpXUrdiMlm8ylQn6baJOj8jcZeiV4kDqODt0,17441
|
|
17
|
+
kinemotion/core/determinism.py,sha256=Frw-KAOvAxTL_XtxoWpXCjMbQPUKEAusK6JctlkeuRo,2509
|
|
18
|
+
kinemotion/core/experimental.py,sha256=G1EpkmWQ8d-rPaN1n0P7mF6XUzrbW0Br3nVkIzJ1D9M,3694
|
|
19
|
+
kinemotion/core/filtering.py,sha256=7KUeclXqZpNQA8WKNocDwhCxZpwwtizI3wvAEyq9SBo,11603
|
|
20
|
+
kinemotion/core/formatting.py,sha256=G_3eqgOtym9RFOZVEwCxye4A2cyrmgvtQ214vIshowU,2480
|
|
21
|
+
kinemotion/core/metadata.py,sha256=bJAVa4nym__zx1hNowSZduMGKBSGOPxTbBQkjm6N0D0,7207
|
|
22
|
+
kinemotion/core/model_downloader.py,sha256=mqhJBHGaNe0aN9qbcBqvcTk9FDd7xaHqEcwD-fyP89c,5205
|
|
23
|
+
kinemotion/core/overlay_constants.py,sha256=zZreHHWe00p2XuCJsbRFqN6g-AAUAnx53LwKqHm1Bl8,1438
|
|
24
|
+
kinemotion/core/pipeline_utils.py,sha256=FzfdKNhM0eK9Y5wbNP9Jab_nmrZxcJfL3cstpO4yfxc,15155
|
|
25
|
+
kinemotion/core/pose.py,sha256=Z795p0EnaTUeWHO8FuApFcMGTLwZ47JOjs5f5TzRvdk,14224
|
|
26
|
+
kinemotion/core/pose_landmarks.py,sha256=LcEbL5K5xKia6dCzWf6Ft18UIE1CLMMqCZ3KUjwUDzM,1558
|
|
27
|
+
kinemotion/core/quality.py,sha256=VUkRL2N6B7lfIZ2pE9han_U68JwarmZz1U0ygHkgkhE,13022
|
|
28
|
+
kinemotion/core/smoothing.py,sha256=F1DCsnvPBi62XJLygOJ5MkNlRa7BCLg_E9ORtCWcoKk,16562
|
|
29
|
+
kinemotion/core/timing.py,sha256=ITX77q4hbtajRuWfgwYhws8nCvOeKFlEdKjCu8lD9_w,7938
|
|
30
|
+
kinemotion/core/types.py,sha256=m141buSkEsqflt5VFaTHtRq_IcimjI3_T_EfaNpIVxY,1652
|
|
31
|
+
kinemotion/core/validation.py,sha256=rrhpI24Iq8WGtNaMg0beTWMbEGccdKF-f-pk-FCKJzI,6749
|
|
32
|
+
kinemotion/core/video_io.py,sha256=84IxC1n3HvYK28MSa5fqumdzlPDhP8k9IPB3OCvWku0,9198
|
|
33
|
+
kinemotion/dj/__init__.py,sha256=yBbEbPdY6sqozWtTvfbvuUZnrVWSSjBp61xK34M29F4,878
|
|
34
|
+
kinemotion/dj/analysis.py,sha256=dR5Dqxo_ub9EAOR95oPI4oJKtIofSH0EodopuoywsO8,33339
|
|
35
|
+
kinemotion/dj/api.py,sha256=v-T-VurOoOIAWVyfR5IUCnUc4bHjBuxB2pP8qJG7TLs,20799
|
|
36
|
+
kinemotion/dj/cli.py,sha256=FaBX637x7VcLcB8HupaZCkVS7sp8C0YuaKM0h-DBNIA,15906
|
|
37
|
+
kinemotion/dj/debug_overlay.py,sha256=X4mvCi5Qi1gnvSZZAsUs-0ZRUx9mVBbEUznOFO21HO8,8470
|
|
38
|
+
kinemotion/dj/kinematics.py,sha256=1K291z-PeJTqJbJDeIKWat90mVbxxh4B4hjz-nTFk88,22618
|
|
39
|
+
kinemotion/dj/metrics_validator.py,sha256=BZbqareRaIfCcehTUvNPO3xzkq4X27xDD867e_w7Fmo,9237
|
|
40
|
+
kinemotion/dj/validation_bounds.py,sha256=k31qy-kCXTiCTx0RPo2t8yZ-faLxqGO-AeF05QfBFb0,5125
|
|
41
|
+
kinemotion/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
42
|
+
kinemotion/models/pose_landmarker_lite.task,sha256=WZKeHR7pUodzXd2DOxnPSsRtKbx6_du_Z1PEWWkNV0o,5777746
|
|
43
|
+
kinemotion/models/rtmpose-s_simcc-body7_pt-body7-halpe26_700e-256x192-7f134165_20230605.onnx,sha256=dfZTq8kbhv8RxWiXS0HUIJNCUpxYTBN45dFIorPflEs,133
|
|
44
|
+
kinemotion/models/yolox_tiny_8xb8-300e_humanart-6f3252f9.onnx,sha256=UsutHVQ6GP3X5pCcp52EN8q7o2J3d-TnxZqlF48kY6I,133
|
|
45
|
+
kinemotion/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
|
+
kinemotion-0.72.0.dist-info/METADATA,sha256=_OdCWsVJMVu8s2tebWVeW5XnFTi_aMZYVTw01XWN_p0,26125
|
|
47
|
+
kinemotion-0.72.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
48
|
+
kinemotion-0.72.0.dist-info/entry_points.txt,sha256=zaqnAnjLvcdrk1Qvj5nvXZCZ2gp0prS7it1zTJygcIY,50
|
|
49
|
+
kinemotion-0.72.0.dist-info/licenses/LICENSE,sha256=KZajvqsHw0NoOHOi2q0FZ4NBe9HdV6oey-IPYAtHXfg,1088
|
|
50
|
+
kinemotion-0.72.0.dist-info/RECORD,,
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
"""Debug overlay rendering for drop jump analysis."""
|
|
2
|
-
|
|
3
|
-
import cv2
|
|
4
|
-
import numpy as np
|
|
5
|
-
|
|
6
|
-
from ..core.debug_overlay_utils import BaseDebugOverlayRenderer
|
|
7
|
-
from ..core.pose import compute_center_of_mass
|
|
8
|
-
from .analysis import ContactState, compute_average_foot_position
|
|
9
|
-
from .kinematics import DropJumpMetrics
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class DebugOverlayRenderer(BaseDebugOverlayRenderer):
|
|
13
|
-
"""Renders debug information on video frames."""
|
|
14
|
-
|
|
15
|
-
def _draw_com_visualization(
|
|
16
|
-
self,
|
|
17
|
-
frame: np.ndarray,
|
|
18
|
-
landmarks: dict[str, tuple[float, float, float]],
|
|
19
|
-
contact_state: ContactState,
|
|
20
|
-
) -> None:
|
|
21
|
-
"""Draw center of mass visualization on frame."""
|
|
22
|
-
com_x, com_y, _ = compute_center_of_mass(landmarks)
|
|
23
|
-
px = int(com_x * self.width)
|
|
24
|
-
py = int(com_y * self.height)
|
|
25
|
-
|
|
26
|
-
color = (0, 255, 0) if contact_state == ContactState.ON_GROUND else (0, 0, 255)
|
|
27
|
-
cv2.circle(frame, (px, py), 15, color, -1)
|
|
28
|
-
cv2.circle(frame, (px, py), 17, (255, 255, 255), 2)
|
|
29
|
-
|
|
30
|
-
# Draw hip midpoint reference
|
|
31
|
-
if "left_hip" in landmarks and "right_hip" in landmarks:
|
|
32
|
-
lh_x, lh_y, _ = landmarks["left_hip"]
|
|
33
|
-
rh_x, rh_y, _ = landmarks["right_hip"]
|
|
34
|
-
hip_x = int((lh_x + rh_x) / 2 * self.width)
|
|
35
|
-
hip_y = int((lh_y + rh_y) / 2 * self.height)
|
|
36
|
-
cv2.circle(frame, (hip_x, hip_y), 8, (255, 165, 0), -1)
|
|
37
|
-
cv2.line(frame, (hip_x, hip_y), (px, py), (255, 165, 0), 2)
|
|
38
|
-
|
|
39
|
-
def _draw_foot_visualization(
|
|
40
|
-
self,
|
|
41
|
-
frame: np.ndarray,
|
|
42
|
-
landmarks: dict[str, tuple[float, float, float]],
|
|
43
|
-
contact_state: ContactState,
|
|
44
|
-
) -> None:
|
|
45
|
-
"""Draw foot position visualization on frame."""
|
|
46
|
-
foot_x, foot_y = compute_average_foot_position(landmarks)
|
|
47
|
-
px = int(foot_x * self.width)
|
|
48
|
-
py = int(foot_y * self.height)
|
|
49
|
-
|
|
50
|
-
color = (0, 255, 0) if contact_state == ContactState.ON_GROUND else (0, 0, 255)
|
|
51
|
-
cv2.circle(frame, (px, py), 10, color, -1)
|
|
52
|
-
|
|
53
|
-
# Draw individual foot landmarks
|
|
54
|
-
foot_keys = ["left_ankle", "right_ankle", "left_heel", "right_heel"]
|
|
55
|
-
for key in foot_keys:
|
|
56
|
-
if key in landmarks:
|
|
57
|
-
x, y, vis = landmarks[key]
|
|
58
|
-
if vis > 0.5:
|
|
59
|
-
lx = int(x * self.width)
|
|
60
|
-
ly = int(y * self.height)
|
|
61
|
-
cv2.circle(frame, (lx, ly), 5, (255, 255, 0), -1)
|
|
62
|
-
|
|
63
|
-
def _draw_phase_labels(
|
|
64
|
-
self,
|
|
65
|
-
frame: np.ndarray,
|
|
66
|
-
frame_idx: int,
|
|
67
|
-
metrics: DropJumpMetrics,
|
|
68
|
-
) -> None:
|
|
69
|
-
"""Draw phase labels (ground contact, flight, peak) on frame."""
|
|
70
|
-
y_offset = 110
|
|
71
|
-
|
|
72
|
-
# Ground contact phase
|
|
73
|
-
if (
|
|
74
|
-
metrics.contact_start_frame
|
|
75
|
-
and metrics.contact_end_frame
|
|
76
|
-
and metrics.contact_start_frame <= frame_idx <= metrics.contact_end_frame
|
|
77
|
-
):
|
|
78
|
-
cv2.putText(
|
|
79
|
-
frame,
|
|
80
|
-
"GROUND CONTACT",
|
|
81
|
-
(10, y_offset),
|
|
82
|
-
cv2.FONT_HERSHEY_SIMPLEX,
|
|
83
|
-
0.7,
|
|
84
|
-
(0, 255, 0),
|
|
85
|
-
2,
|
|
86
|
-
)
|
|
87
|
-
y_offset += 40
|
|
88
|
-
|
|
89
|
-
# Flight phase
|
|
90
|
-
if (
|
|
91
|
-
metrics.flight_start_frame
|
|
92
|
-
and metrics.flight_end_frame
|
|
93
|
-
and metrics.flight_start_frame <= frame_idx <= metrics.flight_end_frame
|
|
94
|
-
):
|
|
95
|
-
cv2.putText(
|
|
96
|
-
frame,
|
|
97
|
-
"FLIGHT PHASE",
|
|
98
|
-
(10, y_offset),
|
|
99
|
-
cv2.FONT_HERSHEY_SIMPLEX,
|
|
100
|
-
0.7,
|
|
101
|
-
(0, 0, 255),
|
|
102
|
-
2,
|
|
103
|
-
)
|
|
104
|
-
y_offset += 40
|
|
105
|
-
|
|
106
|
-
# Peak height
|
|
107
|
-
if metrics.peak_height_frame == frame_idx:
|
|
108
|
-
cv2.putText(
|
|
109
|
-
frame,
|
|
110
|
-
"PEAK HEIGHT",
|
|
111
|
-
(10, y_offset),
|
|
112
|
-
cv2.FONT_HERSHEY_SIMPLEX,
|
|
113
|
-
0.7,
|
|
114
|
-
(255, 0, 255),
|
|
115
|
-
2,
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
def render_frame(
|
|
119
|
-
self,
|
|
120
|
-
frame: np.ndarray,
|
|
121
|
-
landmarks: dict[str, tuple[float, float, float]] | None,
|
|
122
|
-
contact_state: ContactState,
|
|
123
|
-
frame_idx: int,
|
|
124
|
-
metrics: DropJumpMetrics | None = None,
|
|
125
|
-
use_com: bool = False,
|
|
126
|
-
) -> np.ndarray:
|
|
127
|
-
"""
|
|
128
|
-
Render debug overlay on frame.
|
|
129
|
-
|
|
130
|
-
Args:
|
|
131
|
-
frame: Original video frame
|
|
132
|
-
landmarks: Pose landmarks for this frame
|
|
133
|
-
contact_state: Ground contact state
|
|
134
|
-
frame_idx: Current frame index
|
|
135
|
-
metrics: Drop-jump metrics (optional)
|
|
136
|
-
use_com: Whether to visualize CoM instead of feet (optional)
|
|
137
|
-
|
|
138
|
-
Returns:
|
|
139
|
-
Frame with debug overlay
|
|
140
|
-
"""
|
|
141
|
-
with self.timer.measure("debug_video_copy"):
|
|
142
|
-
annotated = frame.copy()
|
|
143
|
-
|
|
144
|
-
def _draw_overlays() -> None:
|
|
145
|
-
# Draw landmarks
|
|
146
|
-
if landmarks:
|
|
147
|
-
if use_com:
|
|
148
|
-
self._draw_com_visualization(annotated, landmarks, contact_state)
|
|
149
|
-
else:
|
|
150
|
-
self._draw_foot_visualization(annotated, landmarks, contact_state)
|
|
151
|
-
|
|
152
|
-
# Draw contact state
|
|
153
|
-
state_color = (0, 255, 0) if contact_state == ContactState.ON_GROUND else (0, 0, 255)
|
|
154
|
-
cv2.putText(
|
|
155
|
-
annotated,
|
|
156
|
-
f"State: {contact_state.value}",
|
|
157
|
-
(10, 30),
|
|
158
|
-
cv2.FONT_HERSHEY_SIMPLEX,
|
|
159
|
-
1,
|
|
160
|
-
state_color,
|
|
161
|
-
2,
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
# Draw frame number
|
|
165
|
-
cv2.putText(
|
|
166
|
-
annotated,
|
|
167
|
-
f"Frame: {frame_idx}",
|
|
168
|
-
(10, 70),
|
|
169
|
-
cv2.FONT_HERSHEY_SIMPLEX,
|
|
170
|
-
0.7,
|
|
171
|
-
(255, 255, 255),
|
|
172
|
-
2,
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
# Draw phase labels
|
|
176
|
-
if metrics:
|
|
177
|
-
self._draw_phase_labels(annotated, frame_idx, metrics)
|
|
178
|
-
|
|
179
|
-
with self.timer.measure("debug_video_draw"):
|
|
180
|
-
_draw_overlays()
|
|
181
|
-
|
|
182
|
-
return annotated
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
kinemotion/__init__.py,sha256=HkD8habCcfxGobxZcACOStla-L1nYHMIZp0th00Q3E8,1061
|
|
2
|
-
kinemotion/api.py,sha256=uG1e4bTnj2c-6cbZJEZ_LjMwFdaG32ba2KcK_XjE_NI,1040
|
|
3
|
-
kinemotion/cli.py,sha256=_Us9krSce4GUKtlLIPrFUhKmPWURzeJ1-ydR_YU2VGw,626
|
|
4
|
-
kinemotion/cmj/__init__.py,sha256=SkAw9ka8Yd1Qfv9hcvk22m3EfucROzYrSNGNF5kDzho,113
|
|
5
|
-
kinemotion/cmj/analysis.py,sha256=WcFNJVd9zpwvDrbe41VshXqP9MFfptlgYD4Nph5mHLA,23675
|
|
6
|
-
kinemotion/cmj/api.py,sha256=5PDV_vX3k63Ko4OEttyUkV4fWyklyy-CG_UyNKgNoyY,18476
|
|
7
|
-
kinemotion/cmj/cli.py,sha256=P2b77IIw6kqTSIkncxlShzhmjIwqMFBNd-pZxYP-TsI,9918
|
|
8
|
-
kinemotion/cmj/debug_overlay.py,sha256=bX9aPLhXiLCCMZW9v8Y4OiOAaZO0i-UGr-Pl8HCsmbI,15810
|
|
9
|
-
kinemotion/cmj/joint_angles.py,sha256=HmheIEiKcQz39cRezk4h-htorOhGNPsqKIR9RsAEKts,9960
|
|
10
|
-
kinemotion/cmj/kinematics.py,sha256=KwA8uSj3g1SeNf0NXMSHsp3gIw6Gfa-6QWIwdYdRXYw,13362
|
|
11
|
-
kinemotion/cmj/metrics_validator.py,sha256=JWuWFfDXyZMTHXFWVdI0MhaQjMR3cjd01tTrDy_if2U,30290
|
|
12
|
-
kinemotion/cmj/validation_bounds.py,sha256=Ry915JdInPXbqjaVGNY_urnDO1PAkCSJqHwNKRq-VkU,12048
|
|
13
|
-
kinemotion/core/__init__.py,sha256=8WB7tAJPKOxgNzbhIEOnGnkRr0CcdNeTnz91Jsiyafo,1812
|
|
14
|
-
kinemotion/core/auto_tuning.py,sha256=lhAqPc-eLjMYx9BCvKdECE7TD2Dweb9KcifV6JHaXOE,11278
|
|
15
|
-
kinemotion/core/cli_utils.py,sha256=sQPbT6XWWau-sm9yuN5c3eS5xNzoQGGXwSz6hQXtRvM,1859
|
|
16
|
-
kinemotion/core/debug_overlay_utils.py,sha256=D4aT8xstThPcV2i5D4KJZJEttW6E_4GE5QiERqe1MwI,13049
|
|
17
|
-
kinemotion/core/determinism.py,sha256=Frw-KAOvAxTL_XtxoWpXCjMbQPUKEAusK6JctlkeuRo,2509
|
|
18
|
-
kinemotion/core/experimental.py,sha256=IK05AF4aZS15ke85hF3TWCqRIXU1AlD_XKzFz735Ua8,3640
|
|
19
|
-
kinemotion/core/filtering.py,sha256=Oc__pV6iHEGyyovbqa5SUi-6v8QyvaRVwA0LRayM884,11355
|
|
20
|
-
kinemotion/core/formatting.py,sha256=G_3eqgOtym9RFOZVEwCxye4A2cyrmgvtQ214vIshowU,2480
|
|
21
|
-
kinemotion/core/metadata.py,sha256=bJAVa4nym__zx1hNowSZduMGKBSGOPxTbBQkjm6N0D0,7207
|
|
22
|
-
kinemotion/core/model_downloader.py,sha256=mqhJBHGaNe0aN9qbcBqvcTk9FDd7xaHqEcwD-fyP89c,5205
|
|
23
|
-
kinemotion/core/pipeline_utils.py,sha256=B5jMXoiLaTh02uGA2MIe1uZLVSRGZ5nxbARuvdrjDrQ,15161
|
|
24
|
-
kinemotion/core/pose.py,sha256=_qC4cbFD0Mp2JAGftZcY5AEDLgD2yRnTyRKD9bkqLI8,15306
|
|
25
|
-
kinemotion/core/pose_landmarks.py,sha256=LcEbL5K5xKia6dCzWf6Ft18UIE1CLMMqCZ3KUjwUDzM,1558
|
|
26
|
-
kinemotion/core/quality.py,sha256=VUkRL2N6B7lfIZ2pE9han_U68JwarmZz1U0ygHkgkhE,13022
|
|
27
|
-
kinemotion/core/smoothing.py,sha256=ELMHL7pzSqYffjnLDBUMBJIgt1AwOssDInE8IiXBbig,15942
|
|
28
|
-
kinemotion/core/timing.py,sha256=ITX77q4hbtajRuWfgwYhws8nCvOeKFlEdKjCu8lD9_w,7938
|
|
29
|
-
kinemotion/core/types.py,sha256=A_HclzKpf3By5DiJ0wY9B-dQJrIVAAhUfGab7qTSIL8,1279
|
|
30
|
-
kinemotion/core/validation.py,sha256=0xVv-ftWveV60fJ97kmZMuy2Qqqb5aZLR50dDIrjnhg,6773
|
|
31
|
-
kinemotion/core/video_io.py,sha256=TxdLUEpekGytesL3X3k78WWgZTOd5fuge30hU4Uy48Y,9198
|
|
32
|
-
kinemotion/dropjump/__init__.py,sha256=tC3H3BrCg8Oj-db-Vrtx4PH_llR1Ppkd5jwaOjhQcLg,862
|
|
33
|
-
kinemotion/dropjump/analysis.py,sha256=YomuoJF_peyrBSpeT89Q5_sBgY0kEDyq7TFrtEnRLjs,28049
|
|
34
|
-
kinemotion/dropjump/api.py,sha256=QlZxCrjOg78PXXip6Krb91RSYqH37x1AbBUy6U8uFt8,20833
|
|
35
|
-
kinemotion/dropjump/cli.py,sha256=gUef9nmyR5952h1WnfBGyCdFXQvzVTlCKYAjJGcO4sE,16819
|
|
36
|
-
kinemotion/dropjump/debug_overlay.py,sha256=9RQYXPRf0q2wdy6y2Ak2R4tpRceDwC8aJrXZzkmh3Wo,5942
|
|
37
|
-
kinemotion/dropjump/kinematics.py,sha256=dx4PuXKfKMKcsc_HX6sXj8rHXf9ksiZIOAIkJ4vBlY4,19637
|
|
38
|
-
kinemotion/dropjump/metrics_validator.py,sha256=lSfo4Lm5FHccl8ijUP6SA-kcSh50LS9hF8UIyWxcnW8,9243
|
|
39
|
-
kinemotion/dropjump/validation_bounds.py,sha256=x4yjcFxyvdMp5e7MkcoUosGLeGsxBh1Lft6h__AQ2G8,5124
|
|
40
|
-
kinemotion/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
|
-
kinemotion/models/pose_landmarker_lite.task,sha256=WZKeHR7pUodzXd2DOxnPSsRtKbx6_du_Z1PEWWkNV0o,5777746
|
|
42
|
-
kinemotion/models/rtmpose-s_simcc-body7_pt-body7-halpe26_700e-256x192-7f134165_20230605.onnx,sha256=dfZTq8kbhv8RxWiXS0HUIJNCUpxYTBN45dFIorPflEs,133
|
|
43
|
-
kinemotion/models/yolox_tiny_8xb8-300e_humanart-6f3252f9.onnx,sha256=UsutHVQ6GP3X5pCcp52EN8q7o2J3d-TnxZqlF48kY6I,133
|
|
44
|
-
kinemotion/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
45
|
-
kinemotion-0.71.0.dist-info/METADATA,sha256=sKhmkomkzrmp1YnX0SC9dzsRvDNlrVtVQ5npxSPWVeI,26125
|
|
46
|
-
kinemotion-0.71.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
47
|
-
kinemotion-0.71.0.dist-info/entry_points.txt,sha256=zaqnAnjLvcdrk1Qvj5nvXZCZ2gp0prS7it1zTJygcIY,50
|
|
48
|
-
kinemotion-0.71.0.dist-info/licenses/LICENSE,sha256=KZajvqsHw0NoOHOi2q0FZ4NBe9HdV6oey-IPYAtHXfg,1088
|
|
49
|
-
kinemotion-0.71.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|