kinemotion 0.76.0__py3-none-any.whl → 0.76.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.
@@ -124,8 +124,8 @@ class CMJMetricsValidator(MetricsValidator):
124
124
  self._check_flight_time(data, result, profile)
125
125
  self._check_jump_height(data, result, profile)
126
126
  self._check_countermovement_depth(data, result, profile)
127
- self._check_concentric_duration(data, result, profile)
128
- self._check_eccentric_duration(data, result, profile)
127
+ self._check_concentric_duration(data, result)
128
+ self._check_eccentric_duration(data, result)
129
129
  self._check_peak_velocities(data, result, profile)
130
130
 
131
131
  # CROSS-VALIDATION CHECKS
@@ -220,7 +220,7 @@ class CMJMetricsValidator(MetricsValidator):
220
220
  )
221
221
 
222
222
  def _check_concentric_duration(
223
- self, metrics: MetricsDict, result: CMJValidationResult, profile: AthleteProfile
223
+ self, metrics: MetricsDict, result: CMJValidationResult
224
224
  ) -> None:
225
225
  """Validate concentric duration (contact time)."""
226
226
  duration_raw = self._get_metric_value(
@@ -256,9 +256,7 @@ class CMJMetricsValidator(MetricsValidator):
256
256
  value=duration,
257
257
  )
258
258
 
259
- def _check_eccentric_duration(
260
- self, metrics: MetricsDict, result: CMJValidationResult, profile: AthleteProfile
261
- ) -> None:
259
+ def _check_eccentric_duration(self, metrics: MetricsDict, result: CMJValidationResult) -> None:
262
260
  """Validate eccentric duration."""
263
261
  duration_raw = self._get_metric_value(
264
262
  metrics, "eccentric_duration_ms", "eccentric_duration"
@@ -21,7 +21,6 @@ class SJPhase(Enum):
21
21
  def detect_sj_phases(
22
22
  positions: FloatArray,
23
23
  fps: float,
24
- squat_hold_threshold: float = 0.02,
25
24
  velocity_threshold: float = 0.1,
26
25
  window_length: int = 5,
27
26
  polyorder: int = 2,
@@ -38,7 +37,6 @@ def detect_sj_phases(
38
37
  Args:
39
38
  positions: 1D array of vertical positions (normalized coordinates)
40
39
  fps: Video frames per second
41
- squat_hold_threshold: Threshold for detecting squat hold phase (m)
42
40
  velocity_threshold: Threshold for detecting flight phase (m/s)
43
41
  window_length: Window size for velocity smoothing
44
42
  polyorder: Polynomial order for smoothing
@@ -183,6 +181,26 @@ def detect_squat_start(
183
181
  return None
184
182
 
185
183
 
184
+ def _find_takeoff_threshold_crossing(
185
+ velocities: FloatArray,
186
+ search_start: int,
187
+ search_end: int,
188
+ velocity_threshold: float,
189
+ min_duration_frames: int,
190
+ ) -> int | None:
191
+ """Find first frame where velocity exceeds threshold for minimum duration."""
192
+ above_threshold = velocities[search_start:search_end] <= -velocity_threshold
193
+ if not np.any(above_threshold):
194
+ return None
195
+
196
+ threshold_indices = np.nonzero(above_threshold)[0]
197
+ for idx in threshold_indices:
198
+ if idx + min_duration_frames < len(above_threshold):
199
+ if np.all(above_threshold[idx : idx + min_duration_frames]):
200
+ return search_start + idx
201
+ return None
202
+
203
+
186
204
  def detect_takeoff(
187
205
  positions: FloatArray,
188
206
  velocities: FloatArray,
@@ -234,19 +252,43 @@ def detect_takeoff(
234
252
  # Verify velocity exceeds threshold
235
253
  if velocities[takeoff_frame] > -velocity_threshold:
236
254
  # Velocity not high enough - actual takeoff may be later
237
- # Look for frames where velocity exceeds threshold
238
- above_threshold = velocities[search_start:search_end] <= -velocity_threshold
239
- if np.any(above_threshold):
240
- # Find first frame above threshold with sufficient duration
241
- threshold_indices = np.where(above_threshold)[0]
242
- for idx in threshold_indices:
243
- if idx + min_duration_frames < len(above_threshold):
244
- if np.all(above_threshold[idx : idx + min_duration_frames]):
245
- return search_start + idx
255
+ # Look for frames where velocity exceeds threshold with duration filter
256
+ return _find_takeoff_threshold_crossing(
257
+ velocities, search_start, search_end, velocity_threshold, min_duration_frames
258
+ )
246
259
 
247
260
  return takeoff_frame if velocities[takeoff_frame] <= -velocity_threshold else None
248
261
 
249
262
 
263
+ def _detect_impact_landing(
264
+ accelerations: FloatArray,
265
+ search_start: int,
266
+ search_end: int,
267
+ ) -> int | None:
268
+ """Detect landing by finding the maximum acceleration spike."""
269
+ landing_accelerations = accelerations[search_start:search_end]
270
+ if len(landing_accelerations) == 0:
271
+ return None
272
+
273
+ # Find maximum acceleration spike (impact)
274
+ landing_idx = int(np.argmax(landing_accelerations))
275
+ return search_start + landing_idx
276
+
277
+
278
+ def _refine_landing_by_velocity(
279
+ velocities: FloatArray,
280
+ landing_frame: int,
281
+ ) -> int:
282
+ """Refine landing frame by looking for positive (downward) velocity."""
283
+ if landing_frame < len(velocities) and velocities[landing_frame] < 0:
284
+ # Velocity still upward - landing might not be detected yet
285
+ # Look ahead for where velocity becomes positive
286
+ for i in range(landing_frame, min(landing_frame + 10, len(velocities))):
287
+ if velocities[i] >= 0:
288
+ return i
289
+ return landing_frame
290
+
291
+
250
292
  def detect_landing(
251
293
  positions: FloatArray,
252
294
  velocities: FloatArray,
@@ -270,6 +312,8 @@ def detect_landing(
270
312
  velocity_threshold: Maximum velocity threshold for landing detection
271
313
  min_flight_frames: Minimum frames in flight before landing
272
314
  landing_search_window_s: Time window to search for landing after peak (seconds)
315
+ window_length: Window size for velocity smoothing
316
+ polyorder: Polynomial order for smoothing
273
317
 
274
318
  Returns:
275
319
  Frame index where landing occurs, or None if not detected
@@ -294,8 +338,7 @@ def detect_landing(
294
338
 
295
339
  # Find peak height (minimum position value in normalized coords = highest point)
296
340
  flight_positions = positions[search_start:search_end]
297
- peak_idx = int(np.argmin(flight_positions))
298
- peak_frame = search_start + peak_idx
341
+ peak_frame = search_start + int(np.argmin(flight_positions))
299
342
 
300
343
  # After peak, look for landing using impact detection
301
344
  landing_search_start = peak_frame + min_flight_frames
@@ -312,8 +355,12 @@ def detect_landing(
312
355
  landing_window = window_length
313
356
  if landing_window % 2 == 0:
314
357
  landing_window += 1
358
+ # Use polyorder for smoothing (must be at least 2 for deriv=2)
359
+ eff_polyorder = max(2, polyorder)
315
360
  accelerations = np.abs(
316
- savgol_filter(positions, landing_window, 2, deriv=2, delta=1.0, mode="interp")
361
+ savgol_filter(
362
+ positions, landing_window, eff_polyorder, deriv=2, delta=1.0, mode="interp"
363
+ )
317
364
  )
318
365
  else:
319
366
  # Fallback for short sequences
@@ -321,22 +368,10 @@ def detect_landing(
321
368
  accelerations = np.abs(np.diff(velocities_abs, prepend=velocities_abs[0]))
322
369
 
323
370
  # Find impact: maximum positive acceleration (deceleration spike)
324
- landing_accelerations = accelerations[landing_search_start:landing_search_end]
371
+ landing_frame = _detect_impact_landing(accelerations, landing_search_start, landing_search_end)
325
372
 
326
- if len(landing_accelerations) == 0:
373
+ if landing_frame is None:
327
374
  return None
328
375
 
329
- # Find maximum acceleration spike (impact)
330
- landing_idx = int(np.argmax(landing_accelerations))
331
- landing_frame = landing_search_start + landing_idx
332
-
333
376
  # Additional verification: velocity should be positive (downward) at landing
334
- if landing_frame < len(velocities) and velocities[landing_frame] < 0:
335
- # Velocity still upward - landing might not be detected yet
336
- # Look ahead for where velocity becomes positive
337
- for i in range(landing_frame, min(landing_frame + 10, len(velocities))):
338
- if velocities[i] >= 0:
339
- landing_frame = i
340
- break
341
-
342
- return landing_frame
377
+ return _refine_landing_by_velocity(velocities, landing_frame)
@@ -1,83 +1,43 @@
1
1
  """Debug overlay visualization for Squat Jump analysis."""
2
2
 
3
- from typing import Any
4
-
5
3
  import cv2
6
4
  import numpy as np
7
5
 
8
-
9
- class SquatJumpDebugOverlayRenderer:
6
+ from ..core.debug_overlay_utils import BaseDebugOverlayRenderer
7
+ from ..core.overlay_constants import (
8
+ CYAN,
9
+ GREEN,
10
+ PHASE_LABEL_LINE_HEIGHT,
11
+ PHASE_LABEL_START_Y,
12
+ RED,
13
+ WHITE,
14
+ Color,
15
+ LandmarkDict,
16
+ )
17
+ from .analysis import SJPhase
18
+ from .kinematics import SJMetrics
19
+
20
+
21
+ class SquatJumpDebugOverlayRenderer(BaseDebugOverlayRenderer):
10
22
  """Debug overlay renderer for Squat Jump analysis results."""
11
23
 
12
- def __init__(
13
- self,
14
- output_path: str,
15
- input_width: int,
16
- input_height: int,
17
- output_width: int,
18
- output_height: int,
19
- fps: float,
20
- timer: Any = None,
21
- ):
22
- """Initialize debug overlay renderer.
23
-
24
- Args:
25
- output_path: Path to output video file
26
- input_width: Width of input frames
27
- input_height: Height of input frames
28
- output_width: Width of output video
29
- output_height: Height of output video
30
- fps: Frames per second for output video
31
- timer: Optional timer for performance profiling
32
- """
33
- self.output_path = output_path
34
- self.input_width = input_width
35
- self.input_height = input_height
36
- self.output_width = output_width
37
- self.output_height = output_height
38
- self.fps = fps
39
- self.timer = timer
40
-
41
- self.writer = None
42
- self.frame_count = 0
43
-
44
- def __enter__(self):
45
- """Enter context manager and initialize video writer."""
46
- fourcc = cv2.VideoWriter_fourcc(*"mp4v") # type: ignore[attr-defined]
47
- self.writer = cv2.VideoWriter(
48
- self.output_path,
49
- fourcc,
50
- self.fps,
51
- (self.output_width, self.output_height),
52
- )
53
- return self
54
-
55
- def __exit__(
56
- self,
57
- exc_type: type[BaseException] | None,
58
- exc_val: BaseException | None,
59
- exc_tb: Any,
60
- ) -> None:
61
- """Exit context manager and release video writer."""
62
- if self.writer:
63
- self.writer.release()
64
-
65
- def write_frame(self, frame: np.ndarray) -> None:
66
- """Write a frame to the output video.
67
-
68
- Args:
69
- frame: Annotated frame to write
70
- """
71
- if self.writer:
72
- self.writer.write(frame)
73
- self.frame_count += 1
24
+ def _get_phase_color(self, phase: SJPhase) -> Color:
25
+ """Get color based on jump phase."""
26
+ phase_colors = {
27
+ SJPhase.SQUAT_HOLD: (255, 255, 0), # Yellow
28
+ SJPhase.CONCENTRIC: (0, 165, 255), # Orange
29
+ SJPhase.FLIGHT: RED,
30
+ SJPhase.LANDING: GREEN,
31
+ SJPhase.UNKNOWN: WHITE,
32
+ }
33
+ return phase_colors.get(phase, WHITE)
74
34
 
75
35
  def render_frame(
76
36
  self,
77
37
  frame: np.ndarray,
78
- landmarks: list | None,
38
+ landmarks: LandmarkDict | None,
79
39
  frame_index: int,
80
- metrics: Any = None,
40
+ metrics: SJMetrics | None = None,
81
41
  ) -> np.ndarray:
82
42
  """Render debug overlay on a single frame.
83
43
 
@@ -93,36 +53,53 @@ class SquatJumpDebugOverlayRenderer:
93
53
  # Create a copy to avoid modifying the original
94
54
  annotated_frame = frame.copy()
95
55
 
96
- # Resize if needed
97
- if self.input_width != self.output_width or self.input_height != self.output_height:
98
- annotated_frame = cv2.resize(
99
- annotated_frame,
100
- (self.output_width, self.output_height),
101
- interpolation=cv2.INTER_LINEAR,
102
- )
103
-
104
- # TODO: Implement by Computer Vision Engineer
105
- # This is a placeholder function that needs to be implemented
106
-
107
- # Placeholder: Just draw basic info
108
- self._draw_frame_info(annotated_frame, frame_index)
109
-
110
- # Placeholder: Draw landmarks if available
56
+ # Determine current phase
57
+ current_phase = SJPhase.UNKNOWN
58
+ if metrics:
59
+ if (
60
+ metrics.squat_hold_start_frame is not None
61
+ and metrics.concentric_start_frame is not None
62
+ and metrics.squat_hold_start_frame <= frame_index < metrics.concentric_start_frame
63
+ ):
64
+ current_phase = SJPhase.SQUAT_HOLD
65
+ elif (
66
+ metrics.concentric_start_frame is not None
67
+ and metrics.takeoff_frame is not None
68
+ and metrics.concentric_start_frame <= frame_index < metrics.takeoff_frame
69
+ ):
70
+ current_phase = SJPhase.CONCENTRIC
71
+ elif (
72
+ metrics.takeoff_frame is not None
73
+ and metrics.landing_frame is not None
74
+ and metrics.takeoff_frame <= frame_index < metrics.landing_frame
75
+ ):
76
+ current_phase = SJPhase.FLIGHT
77
+ elif (
78
+ metrics.landing_frame is not None
79
+ and metrics.landing_frame <= frame_index < metrics.landing_frame + 15
80
+ ):
81
+ current_phase = SJPhase.LANDING
82
+
83
+ # Draw skeleton and landmarks
111
84
  if landmarks:
112
- self._draw_landmarks(annotated_frame, landmarks)
85
+ self._draw_skeleton(annotated_frame, landmarks)
113
86
 
114
- # Placeholder: Draw metrics if available
87
+ # Draw frame information
88
+ self._draw_frame_info(annotated_frame, frame_index, current_phase)
89
+
90
+ # Draw metrics if available
115
91
  if metrics:
116
- self._draw_metrics(annotated_frame, metrics)
92
+ self._draw_metrics(annotated_frame, metrics, frame_index)
117
93
 
118
94
  return annotated_frame
119
95
 
120
- def _draw_frame_info(self, frame: np.ndarray, frame_index: int) -> None:
96
+ def _draw_frame_info(self, frame: np.ndarray, frame_index: int, phase: SJPhase) -> None:
121
97
  """Draw frame information overlay.
122
98
 
123
99
  Args:
124
100
  frame: Frame to draw on
125
101
  frame_index: Current frame index
102
+ phase: Current jump phase
126
103
  """
127
104
  # Draw frame counter
128
105
  cv2.putText(
@@ -131,85 +108,56 @@ class SquatJumpDebugOverlayRenderer:
131
108
  (10, 30),
132
109
  cv2.FONT_HERSHEY_SIMPLEX,
133
110
  0.7,
134
- (0, 255, 0),
111
+ WHITE,
135
112
  2,
136
113
  cv2.LINE_AA,
137
114
  )
138
115
 
139
- def _draw_landmarks(self, frame: np.ndarray, landmarks: list) -> None:
140
- """Draw pose landmarks on frame.
141
-
142
- Args:
143
- frame: Frame to draw on
144
- landmarks: Pose landmarks data
145
- """
146
- # TODO: Implement by Computer Vision Engineer
147
- # This should draw key joints and connections based on landmarks
148
- pass
116
+ # Draw phase label
117
+ phase_color = self._get_phase_color(phase)
118
+ cv2.putText(
119
+ frame,
120
+ f"Phase: {phase.value.replace('_', ' ').upper()}",
121
+ (10, 70),
122
+ cv2.FONT_HERSHEY_SIMPLEX,
123
+ 0.8,
124
+ phase_color,
125
+ 2,
126
+ cv2.LINE_AA,
127
+ )
149
128
 
150
- def _draw_metrics(self, frame: np.ndarray, metrics: Any) -> None:
129
+ def _draw_metrics(self, frame: np.ndarray, metrics: SJMetrics, frame_index: int) -> None:
151
130
  """Draw metrics information on frame.
152
131
 
153
132
  Args:
154
133
  frame: Frame to draw on
155
134
  metrics: Metrics object with analysis results
135
+ frame_index: Current frame index
156
136
  """
157
- # TODO: Implement by Computer Vision Engineer
158
- # This should display current phase, key metrics, and highlights
159
- y_offset = 60
160
-
161
- # Placeholder: Display some basic info
162
- if hasattr(metrics, "jump_height"):
163
- text = f"Jump Height: {metrics.jump_height:.3f} m"
164
- cv2.putText(
165
- frame,
166
- text,
167
- (10, y_offset),
168
- cv2.FONT_HERSHEY_SIMPLEX,
169
- 0.6,
170
- (255, 255, 255),
171
- 2,
172
- cv2.LINE_AA,
173
- )
174
- y_offset += 30
175
-
176
- if hasattr(metrics, "flight_time"):
177
- text = f"Flight Time: {metrics.flight_time * 1000:.1f} ms"
178
- cv2.putText(
179
- frame,
180
- text,
181
- (10, y_offset),
182
- cv2.FONT_HERSHEY_SIMPLEX,
183
- 0.6,
184
- (255, 255, 255),
185
- 2,
186
- cv2.LINE_AA,
187
- )
188
- y_offset += 30
189
-
190
- if hasattr(metrics, "squat_hold_duration"):
191
- text = f"Squat Hold: {metrics.squat_hold_duration * 1000:.1f} ms"
192
- cv2.putText(
193
- frame,
194
- text,
195
- (10, y_offset),
196
- cv2.FONT_HERSHEY_SIMPLEX,
197
- 0.6,
198
- (255, 255, 255),
199
- 2,
200
- cv2.LINE_AA,
201
- )
202
- y_offset += 30
203
-
204
- if hasattr(metrics, "concentric_duration"):
205
- text = f"Concentric: {metrics.concentric_duration * 1000:.1f} ms"
137
+ # Only show summary metrics after takeoff or at the end
138
+ if metrics.takeoff_frame is None or frame_index < metrics.takeoff_frame:
139
+ return
140
+
141
+ y_offset = PHASE_LABEL_START_Y + 100
142
+
143
+ # Display key metrics
144
+ metric_items: list[tuple[str, Color]] = [
145
+ (f"Jump Height: {metrics.jump_height:.3f} m", WHITE),
146
+ (f"Flight Time: {metrics.flight_time * 1000:.1f} ms", RED),
147
+ (f"Concentric: {metrics.concentric_duration * 1000:.1f} ms", CYAN),
148
+ ]
149
+ if metrics.peak_power is not None:
150
+ metric_items.append((f"Peak Power: {metrics.peak_power:.0f} W", GREEN))
151
+
152
+ for text, color in metric_items:
206
153
  cv2.putText(
207
154
  frame,
208
155
  text,
209
156
  (10, y_offset),
210
157
  cv2.FONT_HERSHEY_SIMPLEX,
211
158
  0.6,
212
- (255, 255, 255),
159
+ color,
213
160
  2,
214
161
  cv2.LINE_AA,
215
162
  )
163
+ y_offset += PHASE_LABEL_LINE_HEIGHT
@@ -165,13 +165,11 @@ def calculate_sj_metrics(
165
165
  peak_concentric_velocity = 0.0
166
166
 
167
167
  # Calculate power and force if mass is provided
168
- peak_power = calculate_peak_power(
168
+ peak_power = _calculate_peak_power(velocities, concentric_start, takeoff_frame, mass_kg)
169
+ mean_power = _calculate_mean_power(
169
170
  positions, velocities, concentric_start, takeoff_frame, fps, mass_kg
170
171
  )
171
- mean_power = calculate_mean_power(
172
- positions, velocities, concentric_start, takeoff_frame, fps, mass_kg
173
- )
174
- peak_force = calculate_peak_force(
172
+ peak_force = _calculate_peak_force(
175
173
  positions, velocities, concentric_start, takeoff_frame, fps, mass_kg
176
174
  )
177
175
 
@@ -193,12 +191,10 @@ def calculate_sj_metrics(
193
191
  )
194
192
 
195
193
 
196
- def calculate_peak_power(
197
- positions: FloatArray,
194
+ def _calculate_peak_power(
198
195
  velocities: FloatArray,
199
196
  concentric_start: int,
200
197
  takeoff_frame: int,
201
- fps: float,
202
198
  mass_kg: float | None,
203
199
  ) -> float | None:
204
200
  """Calculate peak power using Sayers et al. (1999) regression equation.
@@ -212,11 +208,9 @@ def calculate_peak_power(
212
208
  - Superior to Lewis formula (73% error) and Harman equation
213
209
 
214
210
  Args:
215
- positions: 1D array of vertical positions (not used in regression)
216
211
  velocities: 1D array of vertical velocities
217
212
  concentric_start: Frame index where concentric phase begins
218
213
  takeoff_frame: Frame index where takeoff occurs
219
- fps: Video frames per second
220
214
  mass_kg: Athlete mass in kilograms
221
215
 
222
216
  Returns:
@@ -246,7 +240,7 @@ def calculate_peak_power(
246
240
  return float(peak_power)
247
241
 
248
242
 
249
- def calculate_mean_power(
243
+ def _calculate_mean_power(
250
244
  positions: FloatArray,
251
245
  velocities: FloatArray,
252
246
  concentric_start: int,
@@ -297,7 +291,7 @@ def calculate_mean_power(
297
291
  return float(mean_power)
298
292
 
299
293
 
300
- def calculate_peak_force(
294
+ def _calculate_peak_force(
301
295
  positions: FloatArray,
302
296
  velocities: FloatArray,
303
297
  concentric_start: int,
@@ -114,11 +114,11 @@ class SJMetricsValidator(MetricsValidator):
114
114
  # PRIMARY BOUNDS CHECKS
115
115
  self._check_flight_time(data, result, profile)
116
116
  self._check_jump_height(data, result, profile)
117
- self._check_squat_hold_duration(data, result, profile)
118
- self._check_concentric_duration(data, result, profile)
117
+ self._check_squat_hold_duration(data, result)
118
+ self._check_concentric_duration(data, result)
119
119
  self._check_peak_concentric_velocity(data, result, profile)
120
- self._check_power_metrics(data, result, profile)
121
- self._check_force_metrics(data, result, profile)
120
+ self._check_power_metrics(data, result)
121
+ self._check_force_metrics(data, result)
122
122
 
123
123
  # CROSS-VALIDATION CHECKS
124
124
  self._check_flight_time_height_consistency(data, result)
@@ -183,9 +183,7 @@ class SJMetricsValidator(MetricsValidator):
183
183
  format_str="{value:.3f}m",
184
184
  )
185
185
 
186
- def _check_squat_hold_duration(
187
- self, metrics: MetricsDict, result: SJValidationResult, profile: AthleteProfile
188
- ) -> None:
186
+ def _check_squat_hold_duration(self, metrics: MetricsDict, result: SJValidationResult) -> None:
189
187
  """Validate squat hold duration."""
190
188
  duration_raw = self._get_metric_value(
191
189
  metrics, "squat_hold_duration_ms", "squat_hold_duration"
@@ -210,9 +208,7 @@ class SJMetricsValidator(MetricsValidator):
210
208
  value=duration,
211
209
  )
212
210
 
213
- def _check_concentric_duration(
214
- self, metrics: MetricsDict, result: SJValidationResult, profile: AthleteProfile
215
- ) -> None:
211
+ def _check_concentric_duration(self, metrics: MetricsDict, result: SJValidationResult) -> None:
216
212
  """Validate concentric duration."""
217
213
  duration_raw = self._get_metric_value(
218
214
  metrics, "concentric_duration_ms", "concentric_duration"
@@ -268,9 +264,7 @@ class SJMetricsValidator(MetricsValidator):
268
264
  format_str="{value:.3f} m/s",
269
265
  )
270
266
 
271
- def _check_power_metrics(
272
- self, metrics: MetricsDict, result: SJValidationResult, profile: AthleteProfile
273
- ) -> None:
267
+ def _check_power_metrics(self, metrics: MetricsDict, result: SJValidationResult) -> None:
274
268
  """Validate power metrics (peak and mean)."""
275
269
  power_checks = [
276
270
  ("peak_power", "peak_power_w", SJBounds.PEAK_POWER, ""),
@@ -305,9 +299,7 @@ class SJMetricsValidator(MetricsValidator):
305
299
  value=power,
306
300
  )
307
301
 
308
- def _check_force_metrics(
309
- self, metrics: MetricsDict, result: SJValidationResult, profile: AthleteProfile
310
- ) -> None:
302
+ def _check_force_metrics(self, metrics: MetricsDict, result: SJValidationResult) -> None:
311
303
  """Validate force metrics."""
312
304
  peak_force = self._get_metric_value(metrics, "peak_force_n", "peak_force")
313
305
  if peak_force is None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kinemotion
3
- Version: 0.76.0
3
+ Version: 0.76.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
@@ -29,7 +29,7 @@ kinemotion/countermovement_jump/cli.py,sha256=m727IOg5BuixgNraCXc2sjW5jGrxrg7RKv
29
29
  kinemotion/countermovement_jump/debug_overlay.py,sha256=vF5Apiz8zDRpgrVzf52manLW99m1kHQAPSdUkar5rPs,11474
30
30
  kinemotion/countermovement_jump/joint_angles.py,sha256=by5M4LDtUfd2_Z9DmcgUl0nsvarsBYjgsE8KWWYcn08,11255
31
31
  kinemotion/countermovement_jump/kinematics.py,sha256=KwA8uSj3g1SeNf0NXMSHsp3gIw6Gfa-6QWIwdYdRXYw,13362
32
- kinemotion/countermovement_jump/metrics_validator.py,sha256=Gozn88jBpe77GhLIMYZfcAlfAmu4_k9R73bCfcwUsTI,24691
32
+ kinemotion/countermovement_jump/metrics_validator.py,sha256=ma1XSLT-RIDrcjYmgfixf244TwbiosRzN7oFr4yWCXg,24609
33
33
  kinemotion/countermovement_jump/validation_bounds.py,sha256=-0iXDhH-RntiGZi_Co22V6qtA5D-hLzkrPkVcfoNd2U,11343
34
34
  kinemotion/drop_jump/__init__.py,sha256=yBbEbPdY6sqozWtTvfbvuUZnrVWSSjBp61xK34M29F4,878
35
35
  kinemotion/drop_jump/analysis.py,sha256=5jlRAjS1kN091FfxNcrkh2gh3PnDqAbnJHFXZrm3hro,34870
@@ -44,16 +44,16 @@ kinemotion/models/pose_landmarker_lite.task,sha256=WZKeHR7pUodzXd2DOxnPSsRtKbx6_
44
44
  kinemotion/models/rtmpose-s_simcc-body7_pt-body7-halpe26_700e-256x192-7f134165_20230605.onnx,sha256=dfZTq8kbhv8RxWiXS0HUIJNCUpxYTBN45dFIorPflEs,133
45
45
  kinemotion/models/yolox_tiny_8xb8-300e_humanart-6f3252f9.onnx,sha256=UsutHVQ6GP3X5pCcp52EN8q7o2J3d-TnxZqlF48kY6I,133
46
46
  kinemotion/squat_jump/__init__.py,sha256=h6ubO3BUANxqjKMdN-KtlN6m77HARAP25PLzHw9k-Lk,99
47
- kinemotion/squat_jump/analysis.py,sha256=lr8C4UDVRI1fHmzMhtcsPXWlt7bNjAKS6EytIgayzTE,12497
47
+ kinemotion/squat_jump/analysis.py,sha256=Iwmss8EHVylN6Hace53fmGTDSxpUiEBh4AiO5YYIWc8,13500
48
48
  kinemotion/squat_jump/api.py,sha256=YMbq2BQzB_SnZ_Z-2KnR_OO2xuua-Zg0hP29Ghbk_d4,20111
49
49
  kinemotion/squat_jump/cli.py,sha256=09Q9O4_sHxw6QWDyPiynDQZSMixTO32NrJ5PTXTJNIk,9806
50
- kinemotion/squat_jump/debug_overlay.py,sha256=tCIeE9BqCfK-RCURCOM2oqF5qgYGMWD27QWdREpeuSc,6429
51
- kinemotion/squat_jump/kinematics.py,sha256=deJV1qDEn6-CLA3SxAOuuI-YDWIbEe9N8lWeZvswmgA,12231
52
- kinemotion/squat_jump/metrics_validator.py,sha256=F0jycY7waDxf6RqUwBulcaKD3frD7EobykgQB-Ayluk,16106
50
+ kinemotion/squat_jump/debug_overlay.py,sha256=IZij8XQvWnmxfDhOZZiLIQ-0xuICx6lDYqcdS7TA3Kw,5280
51
+ kinemotion/squat_jump/kinematics.py,sha256=RU5JobjkSV6Bxs3ope-4fvbeLBITFb_dv9uvHQY3fAE,12052
52
+ kinemotion/squat_jump/metrics_validator.py,sha256=euqd3dYrDCqdifVhs0RPc-UUprkyR-PzTg-D_rIfmI4,15914
53
53
  kinemotion/squat_jump/validation_bounds.py,sha256=q01eQ8Eg01Y5UV3KlvZS1S9iY628OVPUwLoukHZvQOs,7276
54
54
  kinemotion/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
- kinemotion-0.76.0.dist-info/METADATA,sha256=S4_rnobPn-X8PmMBwe7jOoj-voa_dk0xBJsuhw15au4,27690
56
- kinemotion-0.76.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
57
- kinemotion-0.76.0.dist-info/entry_points.txt,sha256=zaqnAnjLvcdrk1Qvj5nvXZCZ2gp0prS7it1zTJygcIY,50
58
- kinemotion-0.76.0.dist-info/licenses/LICENSE,sha256=KZajvqsHw0NoOHOi2q0FZ4NBe9HdV6oey-IPYAtHXfg,1088
59
- kinemotion-0.76.0.dist-info/RECORD,,
55
+ kinemotion-0.76.1.dist-info/METADATA,sha256=ddJE2glHyWt9apLoYSBLt7Lpq9PZIDEjgjVVzxc5XBY,27690
56
+ kinemotion-0.76.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
57
+ kinemotion-0.76.1.dist-info/entry_points.txt,sha256=zaqnAnjLvcdrk1Qvj5nvXZCZ2gp0prS7it1zTJygcIY,50
58
+ kinemotion-0.76.1.dist-info/licenses/LICENSE,sha256=KZajvqsHw0NoOHOi2q0FZ4NBe9HdV6oey-IPYAtHXfg,1088
59
+ kinemotion-0.76.1.dist-info/RECORD,,