kinemotion 0.20.1__py3-none-any.whl → 0.21.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.

@@ -1,9 +1,30 @@
1
1
  """Counter Movement Jump (CMJ) metrics calculation."""
2
2
 
3
3
  from dataclasses import dataclass
4
- from typing import Any
4
+ from typing import TypedDict
5
5
 
6
6
  import numpy as np
7
+ from numpy.typing import NDArray
8
+
9
+
10
+ class CMJMetricsDict(TypedDict, total=False):
11
+ """Type-safe dictionary for CMJ metrics JSON output."""
12
+
13
+ jump_height_m: float
14
+ flight_time_s: float
15
+ countermovement_depth_m: float
16
+ eccentric_duration_s: float
17
+ concentric_duration_s: float
18
+ total_movement_time_s: float
19
+ peak_eccentric_velocity_m_s: float
20
+ peak_concentric_velocity_m_s: float
21
+ transition_time_s: float | None
22
+ standing_start_frame: float | None
23
+ lowest_point_frame: float
24
+ takeoff_frame: float
25
+ landing_frame: float
26
+ video_fps: float
27
+ tracking_method: str
7
28
 
8
29
 
9
30
  @dataclass
@@ -44,7 +65,7 @@ class CMJMetrics:
44
65
  video_fps: float
45
66
  tracking_method: str
46
67
 
47
- def to_dict(self) -> dict[str, Any]:
68
+ def to_dict(self) -> CMJMetricsDict:
48
69
  """Convert metrics to JSON-serializable dictionary.
49
70
 
50
71
  Returns:
@@ -78,8 +99,8 @@ class CMJMetrics:
78
99
 
79
100
 
80
101
  def calculate_cmj_metrics(
81
- positions: np.ndarray,
82
- velocities: np.ndarray,
102
+ positions: NDArray[np.float64],
103
+ velocities: NDArray[np.float64],
83
104
  standing_start_frame: float | None,
84
105
  lowest_point_frame: float,
85
106
  takeoff_frame: float,
@@ -1,5 +1,7 @@
1
1
  """Landmark smoothing utilities to reduce jitter in pose tracking."""
2
2
 
3
+ from typing import TypeAlias
4
+
3
5
  import numpy as np
4
6
  from scipy.signal import savgol_filter
5
7
 
@@ -8,9 +10,14 @@ from .filtering import (
8
10
  reject_outliers,
9
11
  )
10
12
 
13
+ # Type aliases for landmark data structures
14
+ LandmarkCoord: TypeAlias = tuple[float, float, float] # (x, y, visibility)
15
+ LandmarkFrame: TypeAlias = dict[str, LandmarkCoord] | None
16
+ LandmarkSequence: TypeAlias = list[LandmarkFrame]
17
+
11
18
 
12
19
  def _extract_landmark_coordinates(
13
- landmark_sequence: list[dict[str, tuple[float, float, float]] | None],
20
+ landmark_sequence: LandmarkSequence,
14
21
  landmark_name: str,
15
22
  ) -> tuple[list[float], list[float], list[int]]:
16
23
  """
@@ -38,7 +45,7 @@ def _extract_landmark_coordinates(
38
45
 
39
46
 
40
47
  def _get_landmark_names(
41
- landmark_sequence: list[dict[str, tuple[float, float, float]] | None],
48
+ landmark_sequence: LandmarkSequence,
42
49
  ) -> list[str] | None:
43
50
  """
44
51
  Extract landmark names from first valid frame.
@@ -56,8 +63,8 @@ def _get_landmark_names(
56
63
 
57
64
 
58
65
  def _fill_missing_frames(
59
- smoothed_sequence: list[dict[str, tuple[float, float, float]] | None],
60
- landmark_sequence: list[dict[str, tuple[float, float, float]] | None],
66
+ smoothed_sequence: LandmarkSequence,
67
+ landmark_sequence: LandmarkSequence,
61
68
  ) -> None:
62
69
  """
63
70
  Fill in any missing frames in smoothed sequence with original data.
@@ -75,8 +82,8 @@ def _fill_missing_frames(
75
82
 
76
83
 
77
84
  def _store_smoothed_landmarks(
78
- smoothed_sequence: list[dict[str, tuple[float, float, float]] | None],
79
- landmark_sequence: list[dict[str, tuple[float, float, float]] | None],
85
+ smoothed_sequence: LandmarkSequence,
86
+ landmark_sequence: LandmarkSequence,
80
87
  landmark_name: str,
81
88
  x_smooth: np.ndarray,
82
89
  y_smooth: np.ndarray,
@@ -118,11 +125,11 @@ def _store_smoothed_landmarks(
118
125
 
119
126
 
120
127
  def _smooth_landmarks_core( # NOSONAR(S1172) - polyorder used via closure capture in smoother_fn
121
- landmark_sequence: list[dict[str, tuple[float, float, float]] | None],
128
+ landmark_sequence: LandmarkSequence,
122
129
  window_length: int,
123
130
  polyorder: int,
124
131
  smoother_fn, # type: ignore[no-untyped-def]
125
- ) -> list[dict[str, tuple[float, float, float]] | None]:
132
+ ) -> LandmarkSequence:
126
133
  """
127
134
  Core smoothing logic shared by both standard and advanced smoothing.
128
135
 
@@ -170,10 +177,10 @@ def _smooth_landmarks_core( # NOSONAR(S1172) - polyorder used via closure captu
170
177
 
171
178
 
172
179
  def smooth_landmarks(
173
- landmark_sequence: list[dict[str, tuple[float, float, float]] | None],
180
+ landmark_sequence: LandmarkSequence,
174
181
  window_length: int = 5,
175
182
  polyorder: int = 2,
176
- ) -> list[dict[str, tuple[float, float, float]] | None]:
183
+ ) -> LandmarkSequence:
177
184
  """
178
185
  Smooth landmark trajectories using Savitzky-Golay filter.
179
186
 
@@ -330,7 +337,7 @@ def compute_acceleration_from_derivative(
330
337
 
331
338
 
332
339
  def smooth_landmarks_advanced(
333
- landmark_sequence: list[dict[str, tuple[float, float, float]] | None],
340
+ landmark_sequence: LandmarkSequence,
334
341
  window_length: int = 5,
335
342
  polyorder: int = 2,
336
343
  use_outlier_rejection: bool = True,
@@ -338,7 +345,7 @@ def smooth_landmarks_advanced(
338
345
  ransac_threshold: float = 0.02,
339
346
  bilateral_sigma_spatial: float = 3.0,
340
347
  bilateral_sigma_intensity: float = 0.02,
341
- ) -> list[dict[str, tuple[float, float, float]] | None]:
348
+ ) -> LandmarkSequence:
342
349
  """
343
350
  Advanced landmark smoothing with outlier rejection and bilateral filtering.
344
351
 
@@ -1,6 +1,9 @@
1
1
  """Kinematic calculations for drop-jump metrics."""
2
2
 
3
+ from typing import TypedDict
4
+
3
5
  import numpy as np
6
+ from numpy.typing import NDArray
4
7
 
5
8
  from ..core.smoothing import compute_acceleration_from_derivative
6
9
  from .analysis import (
@@ -12,6 +15,25 @@ from .analysis import (
12
15
  )
13
16
 
14
17
 
18
+ class DropJumpMetricsDict(TypedDict, total=False):
19
+ """Type-safe dictionary for drop jump metrics JSON output."""
20
+
21
+ ground_contact_time_ms: float | None
22
+ flight_time_ms: float | None
23
+ jump_height_m: float | None
24
+ jump_height_kinematic_m: float | None
25
+ jump_height_trajectory_normalized: float | None
26
+ contact_start_frame: int | None
27
+ contact_end_frame: int | None
28
+ flight_start_frame: int | None
29
+ flight_end_frame: int | None
30
+ peak_height_frame: int | None
31
+ contact_start_frame_precise: float | None
32
+ contact_end_frame_precise: float | None
33
+ flight_start_frame_precise: float | None
34
+ flight_end_frame_precise: float | None
35
+
36
+
15
37
  class DropJumpMetrics:
16
38
  """Container for drop-jump analysis metrics."""
17
39
 
@@ -32,7 +54,7 @@ class DropJumpMetrics:
32
54
  self.flight_start_frame_precise: float | None = None
33
55
  self.flight_end_frame_precise: float | None = None
34
56
 
35
- def to_dict(self) -> dict:
57
+ def to_dict(self) -> DropJumpMetricsDict:
36
58
  """Convert metrics to dictionary for JSON output."""
37
59
  return {
38
60
  "ground_contact_time_ms": (
@@ -108,7 +130,7 @@ class DropJumpMetrics:
108
130
 
109
131
  def _determine_drop_start_frame(
110
132
  drop_start_frame: int | None,
111
- foot_y_positions: np.ndarray,
133
+ foot_y_positions: NDArray[np.float64],
112
134
  fps: float,
113
135
  smoothing_window: int,
114
136
  ) -> int:
@@ -170,7 +192,7 @@ def _identify_main_contact_phase(
170
192
  phases: list[tuple[int, int, ContactState]],
171
193
  ground_phases: list[tuple[int, int, int]],
172
194
  air_phases_indexed: list[tuple[int, int, int]],
173
- foot_y_positions: np.ndarray,
195
+ foot_y_positions: NDArray[np.float64],
174
196
  ) -> tuple[int, int, bool]:
175
197
  """Identify the main contact phase and determine if it's a drop jump.
176
198
 
@@ -260,7 +282,7 @@ def _analyze_flight_phase(
260
282
  phases: list[tuple[int, int, ContactState]],
261
283
  interpolated_phases: list[tuple[float, float, ContactState]],
262
284
  contact_end: int,
263
- foot_y_positions: np.ndarray,
285
+ foot_y_positions: NDArray[np.float64],
264
286
  fps: float,
265
287
  smoothing_window: int,
266
288
  polyorder: int,
@@ -341,7 +363,7 @@ def _analyze_flight_phase(
341
363
 
342
364
  def calculate_drop_jump_metrics(
343
365
  contact_states: list[ContactState],
344
- foot_y_positions: np.ndarray,
366
+ foot_y_positions: NDArray[np.float64],
345
367
  fps: float,
346
368
  drop_start_frame: int | None = None,
347
369
  velocity_threshold: float = 0.02,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kinemotion
3
- Version: 0.20.1
3
+ Version: 0.21.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
@@ -6,23 +6,23 @@ kinemotion/cmj/analysis.py,sha256=4HYGn4VDIB6oExAees-VcPfpNgWOltpgwjyNTU7YAb4,18
6
6
  kinemotion/cmj/cli.py,sha256=bmDvNvL7cu65-R8YkRIZYKD0nuTA0IJnWLcLlH_kFm0,16843
7
7
  kinemotion/cmj/debug_overlay.py,sha256=D-y2FQKI01KY0WXFKTKg6p9Qj3AkXCE7xjau3Ais080,15886
8
8
  kinemotion/cmj/joint_angles.py,sha256=8ucpDGPvbt4iX3tx9eVxJEUv0laTm2Y58_--VzJCogE,9113
9
- kinemotion/cmj/kinematics.py,sha256=Xl_PlC2OqMoA-zOc3SRB_GqI0AgLlJol5FTPe5J_qLc,7573
9
+ kinemotion/cmj/kinematics.py,sha256=bCtAQY2DIX2JMMou1Z8_Wil3a0sJhpw19pl1CsPKnBg,8202
10
10
  kinemotion/core/__init__.py,sha256=3yzDhb5PekDNjydqrs8aWGneUGJBt-lB0SoB_Y2FXqU,1010
11
11
  kinemotion/core/auto_tuning.py,sha256=j6cul_qC6k0XyryCG93C1AWH2MKPj3UBMzuX02xaqfI,11235
12
12
  kinemotion/core/cli_utils.py,sha256=Pq1JF7yvK1YbH0tOUWKjplthCbWsJQt4Lv7esPYH4FM,7254
13
13
  kinemotion/core/debug_overlay_utils.py,sha256=TyUb5okv5qw8oeaX3jsUO_kpwf1NnaHEAOTm-8LwTno,4587
14
14
  kinemotion/core/filtering.py,sha256=f-m-aA59e4WqE6u-9MA51wssu7rI-Y_7n1cG8IWdeRQ,11241
15
15
  kinemotion/core/pose.py,sha256=ztemdZ_ysVVK3gbXabm8qS_dr1VfJX9KZjmcO-Z-iNE,8532
16
- kinemotion/core/smoothing.py,sha256=C9GK3PAN16RpqJw2UWeVslSTJZEvALeVADjtnJnSF88,14240
16
+ kinemotion/core/smoothing.py,sha256=x4o3BnG6k8OaV3emgpoJDF84CE9k5RYR7BeSYH_-8Es,14092
17
17
  kinemotion/core/video_io.py,sha256=0bJTheYidEqxGP5Y2dSO2x6sbOrnBDBu2TEiV8gT23A,7285
18
18
  kinemotion/dropjump/__init__.py,sha256=yc1XiZ9vfo5h_n7PKVSiX2TTgaIfGL7Y7SkQtiDZj_E,838
19
19
  kinemotion/dropjump/analysis.py,sha256=xx5NWy6s0eb9BEyO_FByY1Ahunaoh3TyaTAxjlPrvxg,27153
20
20
  kinemotion/dropjump/cli.py,sha256=J2F8ij-UcybY7YjK_bncQZiHNzrgS3Y7uTBkNo7y_L4,21328
21
21
  kinemotion/dropjump/debug_overlay.py,sha256=LkPw6ucb7beoYWS4L-Lvjs1KLCm5wAWDAfiznUeV2IQ,5668
22
- kinemotion/dropjump/kinematics.py,sha256=txDxpDti3VJVctWGbe3aIrlIx83UY8-ynzlX01TOvTA,15577
22
+ kinemotion/dropjump/kinematics.py,sha256=VZWdytkw58Vk9dsNe8U15sFB84kfZKLo4argvt0CTPM,16361
23
23
  kinemotion/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
- kinemotion-0.20.1.dist-info/METADATA,sha256=pcFiZ7RCzsd1cdmJoaA_lWZaYgnNGPMTxLj5-WPHS60,20249
25
- kinemotion-0.20.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
26
- kinemotion-0.20.1.dist-info/entry_points.txt,sha256=zaqnAnjLvcdrk1Qvj5nvXZCZ2gp0prS7it1zTJygcIY,50
27
- kinemotion-0.20.1.dist-info/licenses/LICENSE,sha256=KZajvqsHw0NoOHOi2q0FZ4NBe9HdV6oey-IPYAtHXfg,1088
28
- kinemotion-0.20.1.dist-info/RECORD,,
24
+ kinemotion-0.21.0.dist-info/METADATA,sha256=obRgyAAsu-63mzynO4130BaLZiLu6duW9bG-dAMV6PU,20249
25
+ kinemotion-0.21.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
26
+ kinemotion-0.21.0.dist-info/entry_points.txt,sha256=zaqnAnjLvcdrk1Qvj5nvXZCZ2gp0prS7it1zTJygcIY,50
27
+ kinemotion-0.21.0.dist-info/licenses/LICENSE,sha256=KZajvqsHw0NoOHOi2q0FZ4NBe9HdV6oey-IPYAtHXfg,1088
28
+ kinemotion-0.21.0.dist-info/RECORD,,