kinemotion 0.27.0__py3-none-any.whl → 0.28.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.

@@ -6,6 +6,8 @@ from typing import TYPE_CHECKING, TypedDict
6
6
  import numpy as np
7
7
  from numpy.typing import NDArray
8
8
 
9
+ from ..core.formatting import format_float_metric
10
+
9
11
  if TYPE_CHECKING:
10
12
  from ..core.metadata import ResultMetadata
11
13
  from ..core.quality import QualityAssessment
@@ -15,14 +17,14 @@ class CMJDataDict(TypedDict, total=False):
15
17
  """Type-safe dictionary for CMJ measurement data."""
16
18
 
17
19
  jump_height_m: float
18
- flight_time_s: float
20
+ flight_time_ms: float
19
21
  countermovement_depth_m: float
20
- eccentric_duration_s: float
21
- concentric_duration_s: float
22
- total_movement_time_s: float
22
+ eccentric_duration_ms: float
23
+ concentric_duration_ms: float
24
+ total_movement_time_ms: float
23
25
  peak_eccentric_velocity_m_s: float
24
26
  peak_concentric_velocity_m_s: float
25
- transition_time_s: float | None
27
+ transition_time_ms: float | None
26
28
  standing_start_frame: float | None
27
29
  lowest_point_frame: float
28
30
  takeoff_frame: float
@@ -43,14 +45,14 @@ class CMJMetrics:
43
45
 
44
46
  Attributes:
45
47
  jump_height: Maximum jump height in meters
46
- flight_time: Time spent in the air in seconds
48
+ flight_time: Time spent in the air in milliseconds
47
49
  countermovement_depth: Vertical distance traveled during eccentric phase in meters
48
- eccentric_duration: Time from countermovement start to lowest point in seconds
49
- concentric_duration: Time from lowest point to takeoff in seconds
50
- total_movement_time: Total time from countermovement start to takeoff in seconds
50
+ eccentric_duration: Time from countermovement start to lowest point in milliseconds
51
+ concentric_duration: Time from lowest point to takeoff in milliseconds
52
+ total_movement_time: Total time from countermovement start to takeoff in milliseconds
51
53
  peak_eccentric_velocity: Maximum downward velocity during countermovement in m/s
52
54
  peak_concentric_velocity: Maximum upward velocity during propulsion in m/s
53
- transition_time: Duration at lowest point (amortization phase) in seconds
55
+ transition_time: Duration at lowest point (amortization phase) in milliseconds
54
56
  standing_start_frame: Frame where standing phase ends (countermovement begins)
55
57
  lowest_point_frame: Frame at lowest point of countermovement
56
58
  takeoff_frame: Frame where athlete leaves ground
@@ -85,19 +87,27 @@ class CMJMetrics:
85
87
  Dictionary with nested data and metadata structure.
86
88
  """
87
89
  data: CMJDataDict = {
88
- "jump_height_m": float(self.jump_height),
89
- "flight_time_s": float(self.flight_time),
90
- "countermovement_depth_m": float(self.countermovement_depth),
91
- "eccentric_duration_s": float(self.eccentric_duration),
92
- "concentric_duration_s": float(self.concentric_duration),
93
- "total_movement_time_s": float(self.total_movement_time),
94
- "peak_eccentric_velocity_m_s": float(self.peak_eccentric_velocity),
95
- "peak_concentric_velocity_m_s": float(self.peak_concentric_velocity),
96
- "transition_time_s": (
97
- float(self.transition_time)
98
- if self.transition_time is not None
99
- else None
100
- ),
90
+ "jump_height_m": format_float_metric(self.jump_height, 1, 3), # type: ignore[typeddict-item]
91
+ "flight_time_ms": format_float_metric(self.flight_time, 1000, 2), # type: ignore[typeddict-item]
92
+ "countermovement_depth_m": format_float_metric(
93
+ self.countermovement_depth, 1, 3
94
+ ), # type: ignore[typeddict-item]
95
+ "eccentric_duration_ms": format_float_metric(
96
+ self.eccentric_duration, 1000, 2
97
+ ), # type: ignore[typeddict-item]
98
+ "concentric_duration_ms": format_float_metric(
99
+ self.concentric_duration, 1000, 2
100
+ ), # type: ignore[typeddict-item]
101
+ "total_movement_time_ms": format_float_metric(
102
+ self.total_movement_time, 1000, 2
103
+ ), # type: ignore[typeddict-item]
104
+ "peak_eccentric_velocity_m_s": format_float_metric(
105
+ self.peak_eccentric_velocity, 1, 4
106
+ ), # type: ignore[typeddict-item]
107
+ "peak_concentric_velocity_m_s": format_float_metric(
108
+ self.peak_concentric_velocity, 1, 4
109
+ ), # type: ignore[typeddict-item]
110
+ "transition_time_ms": format_float_metric(self.transition_time, 1000, 2),
101
111
  "standing_start_frame": (
102
112
  float(self.standing_start_frame)
103
113
  if self.standing_start_frame is not None
@@ -0,0 +1,75 @@
1
+ """Formatting utilities for consistent numeric output across jump analysis types.
2
+
3
+ This module provides shared helpers for formatting numeric values with appropriate
4
+ precision based on measurement type and capabilities of video-based analysis.
5
+ """
6
+
7
+ # Standard precision values for different measurement types
8
+ # These values are chosen based on:
9
+ # - Video analysis capabilities (30-240 fps)
10
+ # - Typical measurement uncertainty in video-based biomechanics
11
+ # - Balance between accuracy and readability
12
+
13
+ PRECISION_TIME_MS = 2 # Time in milliseconds: ±0.01ms (e.g., 534.12)
14
+ PRECISION_DISTANCE_M = 3 # Distance in meters: ±1mm (e.g., 0.352)
15
+ PRECISION_VELOCITY_M_S = 4 # Velocity in m/s: ±0.0001 m/s (e.g., 2.6340)
16
+ PRECISION_FRAME = 3 # Sub-frame interpolation precision (e.g., 154.342)
17
+ PRECISION_NORMALIZED = 4 # Normalized values 0-1 ratios (e.g., 0.0582)
18
+
19
+
20
+ def format_float_metric(
21
+ value: float | None,
22
+ multiplier: float = 1.0,
23
+ decimals: int = 2,
24
+ ) -> float | None:
25
+ """Format a float metric value with optional scaling and rounding.
26
+
27
+ This helper ensures consistent precision across all jump analysis outputs,
28
+ preventing false precision in measurements while maintaining appropriate
29
+ accuracy for the measurement type.
30
+
31
+ Args:
32
+ value: The value to format, or None
33
+ multiplier: Factor to multiply value by (e.g., 1000 for seconds→milliseconds)
34
+ decimals: Number of decimal places to round to
35
+
36
+ Returns:
37
+ Formatted value rounded to specified decimals, or None if input is None
38
+
39
+ Examples:
40
+ >>> format_float_metric(0.534123, 1000, 2) # seconds to ms
41
+ 534.12
42
+ >>> format_float_metric(0.3521234, 1, 3) # meters
43
+ 0.352
44
+ >>> format_float_metric(None, 1, 2)
45
+ None
46
+ >>> format_float_metric(-1.23456, 1, 4) # negative values preserved
47
+ -1.2346
48
+ """
49
+ if value is None:
50
+ return None
51
+ return round(value * multiplier, decimals)
52
+
53
+
54
+ def format_int_metric(value: float | int | None) -> int | None:
55
+ """Format a value as an integer.
56
+
57
+ Used for frame numbers and other integer-valued metrics.
58
+
59
+ Args:
60
+ value: The value to format, or None
61
+
62
+ Returns:
63
+ Value converted to int, or None if input is None
64
+
65
+ Examples:
66
+ >>> format_int_metric(42.7)
67
+ 42
68
+ >>> format_int_metric(None)
69
+ None
70
+ >>> format_int_metric(154)
71
+ 154
72
+ """
73
+ if value is None:
74
+ return None
75
+ return int(value)
@@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, TypedDict
5
5
  import numpy as np
6
6
  from numpy.typing import NDArray
7
7
 
8
+ from ..core.formatting import format_float_metric, format_int_metric
8
9
  from ..core.smoothing import compute_acceleration_from_derivative
9
10
  from .analysis import (
10
11
  ContactState,
@@ -19,38 +20,6 @@ if TYPE_CHECKING:
19
20
  from ..core.quality import QualityAssessment
20
21
 
21
22
 
22
- def _format_float_metric(
23
- value: float | None, multiplier: float = 1, decimals: int = 2
24
- ) -> float | None:
25
- """Format a float metric value with optional scaling and rounding.
26
-
27
- Args:
28
- value: The value to format, or None
29
- multiplier: Factor to multiply value by (default: 1)
30
- decimals: Number of decimal places to round to (default: 2)
31
-
32
- Returns:
33
- Formatted value rounded to specified decimals, or None if input is None
34
- """
35
- if value is None:
36
- return None
37
- return round(value * multiplier, decimals)
38
-
39
-
40
- def _format_int_metric(value: float | int | None) -> int | None:
41
- """Format a value as an integer.
42
-
43
- Args:
44
- value: The value to format, or None
45
-
46
- Returns:
47
- Value converted to int, or None if input is None
48
- """
49
- if value is None:
50
- return None
51
- return int(value)
52
-
53
-
54
23
  class DropJumpDataDict(TypedDict, total=False):
55
24
  """Type-safe dictionary for drop jump measurement data."""
56
25
 
@@ -108,32 +77,32 @@ class DropJumpMetrics:
108
77
  Dictionary containing formatted metric values.
109
78
  """
110
79
  return {
111
- "ground_contact_time_ms": _format_float_metric(
80
+ "ground_contact_time_ms": format_float_metric(
112
81
  self.ground_contact_time, 1000, 2
113
82
  ),
114
- "flight_time_ms": _format_float_metric(self.flight_time, 1000, 2),
115
- "jump_height_m": _format_float_metric(self.jump_height, 1, 3),
116
- "jump_height_kinematic_m": _format_float_metric(
83
+ "flight_time_ms": format_float_metric(self.flight_time, 1000, 2),
84
+ "jump_height_m": format_float_metric(self.jump_height, 1, 3),
85
+ "jump_height_kinematic_m": format_float_metric(
117
86
  self.jump_height_kinematic, 1, 3
118
87
  ),
119
- "jump_height_trajectory_normalized": _format_float_metric(
88
+ "jump_height_trajectory_normalized": format_float_metric(
120
89
  self.jump_height_trajectory, 1, 4
121
90
  ),
122
- "contact_start_frame": _format_int_metric(self.contact_start_frame),
123
- "contact_end_frame": _format_int_metric(self.contact_end_frame),
124
- "flight_start_frame": _format_int_metric(self.flight_start_frame),
125
- "flight_end_frame": _format_int_metric(self.flight_end_frame),
126
- "peak_height_frame": _format_int_metric(self.peak_height_frame),
127
- "contact_start_frame_precise": _format_float_metric(
91
+ "contact_start_frame": format_int_metric(self.contact_start_frame),
92
+ "contact_end_frame": format_int_metric(self.contact_end_frame),
93
+ "flight_start_frame": format_int_metric(self.flight_start_frame),
94
+ "flight_end_frame": format_int_metric(self.flight_end_frame),
95
+ "peak_height_frame": format_int_metric(self.peak_height_frame),
96
+ "contact_start_frame_precise": format_float_metric(
128
97
  self.contact_start_frame_precise, 1, 3
129
98
  ),
130
- "contact_end_frame_precise": _format_float_metric(
99
+ "contact_end_frame_precise": format_float_metric(
131
100
  self.contact_end_frame_precise, 1, 3
132
101
  ),
133
- "flight_start_frame_precise": _format_float_metric(
102
+ "flight_start_frame_precise": format_float_metric(
134
103
  self.flight_start_frame_precise, 1, 3
135
104
  ),
136
- "flight_end_frame_precise": _format_float_metric(
105
+ "flight_end_frame_precise": format_float_metric(
137
106
  self.flight_end_frame_precise, 1, 3
138
107
  ),
139
108
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kinemotion
3
- Version: 0.27.0
3
+ Version: 0.28.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,12 +6,13 @@ kinemotion/cmj/analysis.py,sha256=4HYGn4VDIB6oExAees-VcPfpNgWOltpgwjyNTU7YAb4,18
6
6
  kinemotion/cmj/cli.py,sha256=12FEfWrseG4kCUbgHHdBPkWp6zzVQ0VAzfgNJotArmM,10792
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=4-YDbCq9e7JlyGl_R3W1tvo8iAkXhjNla9J5yevUSRk,9165
9
+ kinemotion/cmj/kinematics.py,sha256=-iBFg2AkQR4LaThCQzO09fx6qJed27ZfMDQJgE7Si4k,9772
10
10
  kinemotion/core/__init__.py,sha256=HsqolRa60cW3vrG8F9Lvr9WvWcs5hCmsTzSgo7imi-4,1278
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
+ kinemotion/core/formatting.py,sha256=G_3eqgOtym9RFOZVEwCxye4A2cyrmgvtQ214vIshowU,2480
15
16
  kinemotion/core/metadata.py,sha256=PyGHL6sx7Hj21lyorg2VsWP9BGTj_y_-wWU6eKCEfJo,6817
16
17
  kinemotion/core/pose.py,sha256=ztemdZ_ysVVK3gbXabm8qS_dr1VfJX9KZjmcO-Z-iNE,8532
17
18
  kinemotion/core/quality.py,sha256=OC9nuf5IrQ9xURf3eA50VoNWOqkGwbjJpS90q2FDQzA,13082
@@ -21,10 +22,10 @@ kinemotion/dropjump/__init__.py,sha256=yc1XiZ9vfo5h_n7PKVSiX2TTgaIfGL7Y7SkQtiDZj
21
22
  kinemotion/dropjump/analysis.py,sha256=BQ5NqSPNJjFQOb-W4bXSLvjCgWd-nvqx5NElyeqZJC4,29067
22
23
  kinemotion/dropjump/cli.py,sha256=ZyroaYPwz8TgfL39Wcaj6m68Awl6lYXC75ttaflU-c0,16236
23
24
  kinemotion/dropjump/debug_overlay.py,sha256=LkPw6ucb7beoYWS4L-Lvjs1KLCm5wAWDAfiznUeV2IQ,5668
24
- kinemotion/dropjump/kinematics.py,sha256=Ig9TqXr-OEUm19gqIvUjQkqrCuw1csYt1f4ZfwG8oGc,17464
25
+ kinemotion/dropjump/kinematics.py,sha256=Yr3G7AQwtYy1dxyeOAYfqqgd4pzoZwWQAhZzxI5RbnE,16658
25
26
  kinemotion/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
- kinemotion-0.27.0.dist-info/METADATA,sha256=YhRgdOUcZ_mIgOgj0wJBdTW_msbUPlyZaCx7ND0dVTI,23244
27
- kinemotion-0.27.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
28
- kinemotion-0.27.0.dist-info/entry_points.txt,sha256=zaqnAnjLvcdrk1Qvj5nvXZCZ2gp0prS7it1zTJygcIY,50
29
- kinemotion-0.27.0.dist-info/licenses/LICENSE,sha256=KZajvqsHw0NoOHOi2q0FZ4NBe9HdV6oey-IPYAtHXfg,1088
30
- kinemotion-0.27.0.dist-info/RECORD,,
27
+ kinemotion-0.28.0.dist-info/METADATA,sha256=RIUN7r__qFVHSNzj2CglERzONmcmLiIYrDZLkpztZu8,23244
28
+ kinemotion-0.28.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
29
+ kinemotion-0.28.0.dist-info/entry_points.txt,sha256=zaqnAnjLvcdrk1Qvj5nvXZCZ2gp0prS7it1zTJygcIY,50
30
+ kinemotion-0.28.0.dist-info/licenses/LICENSE,sha256=KZajvqsHw0NoOHOi2q0FZ4NBe9HdV6oey-IPYAtHXfg,1088
31
+ kinemotion-0.28.0.dist-info/RECORD,,