kinemotion 0.26.1__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.

kinemotion/api.py CHANGED
@@ -532,7 +532,7 @@ def process_dropjump_video(
532
532
  height=video.height,
533
533
  duration_s=video.frame_count / video.fps,
534
534
  frame_count=video.frame_count,
535
- codec=None,
535
+ codec=video.codec,
536
536
  )
537
537
 
538
538
  processing_info = ProcessingInfo(
@@ -973,7 +973,7 @@ def process_cmj_video(
973
973
  height=video.height,
974
974
  duration_s=video.frame_count / video.fps,
975
975
  frame_count=video.frame_count,
976
- codec=None, # TODO: Extract from video metadata if available
976
+ codec=video.codec,
977
977
  )
978
978
 
979
979
  processing_info = ProcessingInfo(
@@ -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)
@@ -50,6 +50,9 @@ class VideoProcessor:
50
50
  # OpenCV ignores rotation metadata, so we need to extract and apply it manually
51
51
  self.rotation = 0 # Will be set by _extract_video_metadata()
52
52
 
53
+ # Extract codec information from video metadata
54
+ self.codec: str | None = None # Will be set by _extract_video_metadata()
55
+
53
56
  # Calculate display dimensions considering SAR (Sample Aspect Ratio)
54
57
  # Mobile videos often have non-square pixels encoded in SAR metadata
55
58
  # OpenCV doesn't directly expose SAR, but we need to handle display correctly
@@ -141,6 +144,9 @@ class VideoProcessor:
141
144
 
142
145
  stream = data["streams"][0]
143
146
 
147
+ # Extract codec name (e.g., "h264", "hevc", "vp9")
148
+ self.codec = stream.get("codec_name")
149
+
144
150
  # Extract and parse SAR (Sample Aspect Ratio)
145
151
  sar_str = stream.get("sample_aspect_ratio", "1:1")
146
152
  self._parse_sample_aspect_ratio(sar_str)
@@ -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.26.1
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
@@ -1,30 +1,31 @@
1
1
  kinemotion/__init__.py,sha256=vAEIg-oDX1ZkQMnWgXd__tekaA5KUcEvdJSAGWS8VUY,722
2
- kinemotion/api.py,sha256=ELkAk0xq2MaafVwSAahXIf1KP9am8rpxHibqcnId6pg,38213
2
+ kinemotion/api.py,sha256=tbkjXsfe0N0Bmik6XRIOYM7Nom4QqeQJSpDx7IoiuSA,38177
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
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
18
19
  kinemotion/core/smoothing.py,sha256=x4o3BnG6k8OaV3emgpoJDF84CE9k5RYR7BeSYH_-8Es,14092
19
- kinemotion/core/video_io.py,sha256=0bJTheYidEqxGP5Y2dSO2x6sbOrnBDBu2TEiV8gT23A,7285
20
+ kinemotion/core/video_io.py,sha256=SQBJSgAV8uOkAh96gNZRjd6XJG1G9dZzDc8kAZ_twy0,7538
20
21
  kinemotion/dropjump/__init__.py,sha256=yc1XiZ9vfo5h_n7PKVSiX2TTgaIfGL7Y7SkQtiDZj_E,838
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.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,,
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,,