kinemotion 0.26.0__py3-none-any.whl → 0.26.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.
- kinemotion/api.py +15 -7
- kinemotion/dropjump/analysis.py +92 -34
- {kinemotion-0.26.0.dist-info → kinemotion-0.26.1.dist-info}/METADATA +1 -1
- {kinemotion-0.26.0.dist-info → kinemotion-0.26.1.dist-info}/RECORD +7 -7
- {kinemotion-0.26.0.dist-info → kinemotion-0.26.1.dist-info}/WHEEL +0 -0
- {kinemotion-0.26.0.dist-info → kinemotion-0.26.1.dist-info}/entry_points.txt +0 -0
- {kinemotion-0.26.0.dist-info → kinemotion-0.26.1.dist-info}/licenses/LICENSE +0 -0
kinemotion/api.py
CHANGED
|
@@ -244,6 +244,20 @@ def _apply_smoothing(
|
|
|
244
244
|
)
|
|
245
245
|
|
|
246
246
|
|
|
247
|
+
def _calculate_foot_visibility(frame_landmarks: dict) -> float:
|
|
248
|
+
"""Calculate average visibility of foot landmarks.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
frame_landmarks: Dictionary of landmarks for a frame
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
Average visibility value (0-1)
|
|
255
|
+
"""
|
|
256
|
+
foot_keys = ["left_ankle", "right_ankle", "left_heel", "right_heel"]
|
|
257
|
+
foot_vis = [frame_landmarks[key][2] for key in foot_keys if key in frame_landmarks]
|
|
258
|
+
return float(np.mean(foot_vis)) if foot_vis else 0.0
|
|
259
|
+
|
|
260
|
+
|
|
247
261
|
def _extract_vertical_positions(
|
|
248
262
|
smoothed_landmarks: list,
|
|
249
263
|
) -> tuple[np.ndarray, np.ndarray]:
|
|
@@ -262,13 +276,7 @@ def _extract_vertical_positions(
|
|
|
262
276
|
if frame_landmarks:
|
|
263
277
|
_, foot_y = compute_average_foot_position(frame_landmarks)
|
|
264
278
|
position_list.append(foot_y)
|
|
265
|
-
|
|
266
|
-
# Average visibility of foot landmarks
|
|
267
|
-
foot_vis = []
|
|
268
|
-
for key in ["left_ankle", "right_ankle", "left_heel", "right_heel"]:
|
|
269
|
-
if key in frame_landmarks:
|
|
270
|
-
foot_vis.append(frame_landmarks[key][2])
|
|
271
|
-
visibilities_list.append(float(np.mean(foot_vis)) if foot_vis else 0.0)
|
|
279
|
+
visibilities_list.append(_calculate_foot_visibility(frame_landmarks))
|
|
272
280
|
else:
|
|
273
281
|
position_list.append(position_list[-1] if position_list else 0.5)
|
|
274
282
|
visibilities_list.append(0.0)
|
kinemotion/dropjump/analysis.py
CHANGED
|
@@ -235,6 +235,86 @@ def detect_drop_start(
|
|
|
235
235
|
)
|
|
236
236
|
|
|
237
237
|
|
|
238
|
+
def _filter_stationary_with_visibility(
|
|
239
|
+
is_stationary: np.ndarray,
|
|
240
|
+
visibilities: np.ndarray | None,
|
|
241
|
+
visibility_threshold: float,
|
|
242
|
+
) -> np.ndarray:
|
|
243
|
+
"""Apply visibility filter to stationary flags.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
is_stationary: Boolean array indicating stationary frames
|
|
247
|
+
visibilities: Optional visibility scores for each frame
|
|
248
|
+
visibility_threshold: Minimum visibility to trust landmark
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
Filtered is_stationary array
|
|
252
|
+
"""
|
|
253
|
+
if visibilities is not None:
|
|
254
|
+
is_visible = visibilities > visibility_threshold
|
|
255
|
+
return is_stationary & is_visible
|
|
256
|
+
return is_stationary
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _find_contact_frames(
|
|
260
|
+
is_stationary: np.ndarray,
|
|
261
|
+
min_contact_frames: int,
|
|
262
|
+
) -> set[int]:
|
|
263
|
+
"""Find frames with sustained contact using minimum duration filter.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
is_stationary: Boolean array indicating stationary frames
|
|
267
|
+
min_contact_frames: Minimum consecutive frames to confirm contact
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
Set of frame indices that meet minimum contact duration
|
|
271
|
+
"""
|
|
272
|
+
contact_frames: set[int] = set()
|
|
273
|
+
current_run = []
|
|
274
|
+
|
|
275
|
+
for i, stationary in enumerate(is_stationary):
|
|
276
|
+
if stationary:
|
|
277
|
+
current_run.append(i)
|
|
278
|
+
else:
|
|
279
|
+
if len(current_run) >= min_contact_frames:
|
|
280
|
+
contact_frames.update(current_run)
|
|
281
|
+
current_run = []
|
|
282
|
+
|
|
283
|
+
# Handle last run
|
|
284
|
+
if len(current_run) >= min_contact_frames:
|
|
285
|
+
contact_frames.update(current_run)
|
|
286
|
+
|
|
287
|
+
return contact_frames
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _assign_contact_states(
|
|
291
|
+
n_frames: int,
|
|
292
|
+
contact_frames: set[int],
|
|
293
|
+
visibilities: np.ndarray | None,
|
|
294
|
+
visibility_threshold: float,
|
|
295
|
+
) -> list[ContactState]:
|
|
296
|
+
"""Assign contact states based on contact frames and visibility.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
n_frames: Total number of frames
|
|
300
|
+
contact_frames: Set of frames with confirmed contact
|
|
301
|
+
visibilities: Optional visibility scores for each frame
|
|
302
|
+
visibility_threshold: Minimum visibility to trust landmark
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
List of ContactState for each frame
|
|
306
|
+
"""
|
|
307
|
+
states = []
|
|
308
|
+
for i in range(n_frames):
|
|
309
|
+
if visibilities is not None and visibilities[i] < visibility_threshold:
|
|
310
|
+
states.append(ContactState.UNKNOWN)
|
|
311
|
+
elif i in contact_frames:
|
|
312
|
+
states.append(ContactState.ON_GROUND)
|
|
313
|
+
else:
|
|
314
|
+
states.append(ContactState.IN_AIR)
|
|
315
|
+
return states
|
|
316
|
+
|
|
317
|
+
|
|
238
318
|
def detect_ground_contact(
|
|
239
319
|
foot_positions: np.ndarray,
|
|
240
320
|
velocity_threshold: float = 0.02,
|
|
@@ -247,7 +327,7 @@ def detect_ground_contact(
|
|
|
247
327
|
"""
|
|
248
328
|
Detect when feet are in contact with ground based on vertical motion.
|
|
249
329
|
|
|
250
|
-
Uses derivative-based velocity calculation via Savitzky-
|
|
330
|
+
Uses derivative-based velocity calculation via Savitzky-Goyal filter for smooth,
|
|
251
331
|
accurate velocity estimates. This is consistent with the velocity calculation used
|
|
252
332
|
throughout the pipeline for sub-frame interpolation and curvature analysis.
|
|
253
333
|
|
|
@@ -264,52 +344,30 @@ def detect_ground_contact(
|
|
|
264
344
|
List of ContactState for each frame
|
|
265
345
|
"""
|
|
266
346
|
n_frames = len(foot_positions)
|
|
267
|
-
states = [ContactState.UNKNOWN] * n_frames
|
|
268
347
|
|
|
269
348
|
if n_frames < 2:
|
|
270
|
-
return
|
|
349
|
+
return [ContactState.UNKNOWN] * n_frames
|
|
271
350
|
|
|
272
351
|
# Compute vertical velocity using derivative-based method
|
|
273
|
-
# This provides smoother, more accurate velocity estimates than frame-to-frame differences
|
|
274
|
-
# and is consistent with the velocity calculation used for sub-frame interpolation
|
|
275
352
|
velocities = compute_velocity_from_derivative(
|
|
276
353
|
foot_positions, window_length=window_length, polyorder=polyorder
|
|
277
354
|
)
|
|
278
355
|
|
|
279
|
-
# Detect
|
|
356
|
+
# Detect stationary frames based on velocity threshold
|
|
280
357
|
is_stationary = np.abs(velocities) < velocity_threshold
|
|
281
358
|
|
|
282
359
|
# Apply visibility filter
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
# Apply minimum contact duration filter
|
|
288
|
-
contact_frames = []
|
|
289
|
-
current_run = []
|
|
290
|
-
|
|
291
|
-
for i, stationary in enumerate(is_stationary):
|
|
292
|
-
if stationary:
|
|
293
|
-
current_run.append(i)
|
|
294
|
-
else:
|
|
295
|
-
if len(current_run) >= min_contact_frames:
|
|
296
|
-
contact_frames.extend(current_run)
|
|
297
|
-
current_run = []
|
|
298
|
-
|
|
299
|
-
# Don't forget the last run
|
|
300
|
-
if len(current_run) >= min_contact_frames:
|
|
301
|
-
contact_frames.extend(current_run)
|
|
360
|
+
is_stationary = _filter_stationary_with_visibility(
|
|
361
|
+
is_stationary, visibilities, visibility_threshold
|
|
362
|
+
)
|
|
302
363
|
|
|
303
|
-
#
|
|
304
|
-
|
|
305
|
-
if visibilities is not None and visibilities[i] < visibility_threshold:
|
|
306
|
-
states[i] = ContactState.UNKNOWN
|
|
307
|
-
elif i in contact_frames:
|
|
308
|
-
states[i] = ContactState.ON_GROUND
|
|
309
|
-
else:
|
|
310
|
-
states[i] = ContactState.IN_AIR
|
|
364
|
+
# Find frames with sustained contact
|
|
365
|
+
contact_frames = _find_contact_frames(is_stationary, min_contact_frames)
|
|
311
366
|
|
|
312
|
-
|
|
367
|
+
# Assign states
|
|
368
|
+
return _assign_contact_states(
|
|
369
|
+
n_frames, contact_frames, visibilities, visibility_threshold
|
|
370
|
+
)
|
|
313
371
|
|
|
314
372
|
|
|
315
373
|
def find_contact_phases(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kinemotion
|
|
3
|
-
Version: 0.26.
|
|
3
|
+
Version: 0.26.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,5 +1,5 @@
|
|
|
1
1
|
kinemotion/__init__.py,sha256=vAEIg-oDX1ZkQMnWgXd__tekaA5KUcEvdJSAGWS8VUY,722
|
|
2
|
-
kinemotion/api.py,sha256=
|
|
2
|
+
kinemotion/api.py,sha256=ELkAk0xq2MaafVwSAahXIf1KP9am8rpxHibqcnId6pg,38213
|
|
3
3
|
kinemotion/cli.py,sha256=cqYV_7URH0JUDy1VQ_EDLv63FmNO4Ns20m6s1XAjiP4,464
|
|
4
4
|
kinemotion/cmj/__init__.py,sha256=Ynv0-Oco4I3Y1Ubj25m3h9h2XFqeNwpAewXmAYOmwfU,127
|
|
5
5
|
kinemotion/cmj/analysis.py,sha256=4HYGn4VDIB6oExAees-VcPfpNgWOltpgwjyNTU7YAb4,18263
|
|
@@ -18,13 +18,13 @@ kinemotion/core/quality.py,sha256=OC9nuf5IrQ9xURf3eA50VoNWOqkGwbjJpS90q2FDQzA,13
|
|
|
18
18
|
kinemotion/core/smoothing.py,sha256=x4o3BnG6k8OaV3emgpoJDF84CE9k5RYR7BeSYH_-8Es,14092
|
|
19
19
|
kinemotion/core/video_io.py,sha256=0bJTheYidEqxGP5Y2dSO2x6sbOrnBDBu2TEiV8gT23A,7285
|
|
20
20
|
kinemotion/dropjump/__init__.py,sha256=yc1XiZ9vfo5h_n7PKVSiX2TTgaIfGL7Y7SkQtiDZj_E,838
|
|
21
|
-
kinemotion/dropjump/analysis.py,sha256=
|
|
21
|
+
kinemotion/dropjump/analysis.py,sha256=BQ5NqSPNJjFQOb-W4bXSLvjCgWd-nvqx5NElyeqZJC4,29067
|
|
22
22
|
kinemotion/dropjump/cli.py,sha256=ZyroaYPwz8TgfL39Wcaj6m68Awl6lYXC75ttaflU-c0,16236
|
|
23
23
|
kinemotion/dropjump/debug_overlay.py,sha256=LkPw6ucb7beoYWS4L-Lvjs1KLCm5wAWDAfiznUeV2IQ,5668
|
|
24
24
|
kinemotion/dropjump/kinematics.py,sha256=Ig9TqXr-OEUm19gqIvUjQkqrCuw1csYt1f4ZfwG8oGc,17464
|
|
25
25
|
kinemotion/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
-
kinemotion-0.26.
|
|
27
|
-
kinemotion-0.26.
|
|
28
|
-
kinemotion-0.26.
|
|
29
|
-
kinemotion-0.26.
|
|
30
|
-
kinemotion-0.26.
|
|
26
|
+
kinemotion-0.26.1.dist-info/METADATA,sha256=4Ads7Gis9jvPj3qOXQBwyGB6c3wJr8kkVWpX-kKwI1Q,23244
|
|
27
|
+
kinemotion-0.26.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
28
|
+
kinemotion-0.26.1.dist-info/entry_points.txt,sha256=zaqnAnjLvcdrk1Qvj5nvXZCZ2gp0prS7it1zTJygcIY,50
|
|
29
|
+
kinemotion-0.26.1.dist-info/licenses/LICENSE,sha256=KZajvqsHw0NoOHOi2q0FZ4NBe9HdV6oey-IPYAtHXfg,1088
|
|
30
|
+
kinemotion-0.26.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|