kinemotion 0.35.0__py3-none-any.whl → 0.35.2__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 CHANGED
@@ -548,9 +548,12 @@ def process_dropjump_video(
548
548
 
549
549
  # Check if drop start was auto-detected
550
550
  drop_frame = None
551
- if drop_start_frame is None and metrics.contact_start_frame is not None:
552
- # Auto-detected
553
- drop_frame = metrics.contact_start_frame
551
+ if drop_start_frame is None and metrics.drop_start_frame is not None:
552
+ # Auto-detected drop start from box
553
+ drop_frame = metrics.drop_start_frame
554
+ elif drop_start_frame is not None:
555
+ # Manual drop start provided
556
+ drop_frame = drop_start_frame
554
557
 
555
558
  algorithm_config = AlgorithmConfig(
556
559
  detection_method="forward_search",
@@ -1057,7 +1060,7 @@ def process_cmj_video(
1057
1060
 
1058
1061
  # Validate metrics against physiological bounds
1059
1062
  validator = CMJMetricsValidator()
1060
- validation_result = validator.validate(metrics.to_dict()["data"]) # type: ignore[arg-type]
1063
+ validation_result = validator.validate(metrics.to_dict()) # type: ignore[arg-type]
1061
1064
  metrics.validation_result = validation_result
1062
1065
 
1063
1066
  if verbose and validation_result.issues:
@@ -422,11 +422,59 @@ def find_landing_frame(
422
422
  return float(landing_search_start + landing_idx)
423
423
 
424
424
 
425
- def find_standing_end(velocities: np.ndarray, lowest_point: float) -> float | None:
426
- """Find end of standing phase before lowest point."""
425
+ def find_standing_end(
426
+ velocities: np.ndarray,
427
+ lowest_point: float,
428
+ positions: np.ndarray | None = None,
429
+ accelerations: np.ndarray | None = None,
430
+ ) -> float | None:
431
+ """
432
+ Find end of standing phase before lowest point.
433
+
434
+ Uses acceleration-based detection to identify when downward movement begins.
435
+ Acceleration captures movement initiation even when velocity is negligible,
436
+ making it ideal for detecting slow countermovement starts.
437
+
438
+ Args:
439
+ velocities: Signed velocity array (for backward compatibility)
440
+ lowest_point: Frame index of lowest point
441
+ positions: Position array (unused, kept for backward compatibility)
442
+ accelerations: Acceleration array (if provided, uses
443
+ acceleration-based detection)
444
+
445
+ Returns:
446
+ Frame index where standing ends (countermovement begins), or None
447
+ """
427
448
  if lowest_point <= 20:
428
449
  return None
429
450
 
451
+ # Acceleration-based detection (best for detecting movement initiation)
452
+ if accelerations is not None:
453
+ # Use middle section of standing phase as baseline (avoids initial settling)
454
+ baseline_start = 10
455
+ baseline_end = min(40, int(lowest_point) - 10)
456
+
457
+ if baseline_end <= baseline_start:
458
+ return None
459
+
460
+ # Calculate baseline acceleration statistics
461
+ baseline_accel = accelerations[baseline_start:baseline_end]
462
+ baseline_mean = float(np.mean(baseline_accel))
463
+ baseline_std = float(np.std(baseline_accel))
464
+
465
+ # Threshold: 3 standard deviations above baseline
466
+ # This detects when acceleration significantly increases (movement starts)
467
+ accel_threshold = baseline_mean + 3.0 * baseline_std
468
+
469
+ # Search forward from baseline for acceleration spike
470
+ for i in range(baseline_end, int(lowest_point)):
471
+ if accelerations[i] > accel_threshold:
472
+ # Found start of downward acceleration
473
+ return float(i)
474
+
475
+ return None
476
+
477
+ # Fallback: velocity-based detection (legacy)
430
478
  standing_search = velocities[: int(lowest_point)]
431
479
  low_vel = np.abs(standing_search) < 0.005
432
480
  if np.any(low_vel):
@@ -479,6 +527,6 @@ def detect_cmj_phases(
479
527
  takeoff_frame = find_takeoff_frame(velocities, peak_height_frame, fps)
480
528
  lowest_point = find_lowest_frame(velocities, positions, takeoff_frame, fps)
481
529
  landing_frame = find_landing_frame(accelerations, peak_height_frame, fps)
482
- standing_end = find_standing_end(velocities, lowest_point)
530
+ standing_end = find_standing_end(velocities, lowest_point, positions, accelerations)
483
531
 
484
532
  return (standing_end, lowest_point, takeoff_frame, landing_frame)
@@ -68,6 +68,22 @@ class CMJValidationResult(ValidationResult):
68
68
  class CMJMetricsValidator(MetricsValidator):
69
69
  """Comprehensive CMJ metrics validator."""
70
70
 
71
+ @staticmethod
72
+ def _get_metric_value(
73
+ data: dict, key_with_suffix: str, key_without_suffix: str
74
+ ) -> float | None:
75
+ """Get metric value, supporting both suffixed and legacy key formats.
76
+
77
+ Args:
78
+ data: Dictionary containing metrics
79
+ key_with_suffix: Key with unit suffix (e.g., "flight_time_ms")
80
+ key_without_suffix: Legacy key without suffix (e.g., "flight_time")
81
+
82
+ Returns:
83
+ Metric value or None if not found
84
+ """
85
+ return data.get(key_with_suffix) or data.get(key_without_suffix)
86
+
71
87
  def validate(self, metrics: dict) -> CMJValidationResult:
72
88
  """Validate CMJ metrics comprehensively.
73
89
 
@@ -87,25 +103,28 @@ class CMJMetricsValidator(MetricsValidator):
87
103
 
88
104
  profile = result.athlete_profile
89
105
 
106
+ # Extract metric values (handle nested "data" structure)
107
+ data = metrics.get("data", metrics) # Support both structures
108
+
90
109
  # PRIMARY BOUNDS CHECKS
91
- self._check_flight_time(metrics, result, profile)
92
- self._check_jump_height(metrics, result, profile)
93
- self._check_countermovement_depth(metrics, result, profile)
94
- self._check_concentric_duration(metrics, result, profile)
95
- self._check_eccentric_duration(metrics, result, profile)
96
- self._check_peak_velocities(metrics, result, profile)
110
+ self._check_flight_time(data, result, profile)
111
+ self._check_jump_height(data, result, profile)
112
+ self._check_countermovement_depth(data, result, profile)
113
+ self._check_concentric_duration(data, result, profile)
114
+ self._check_eccentric_duration(data, result, profile)
115
+ self._check_peak_velocities(data, result, profile)
97
116
 
98
117
  # CROSS-VALIDATION CHECKS
99
- self._check_flight_time_height_consistency(metrics, result)
100
- self._check_velocity_height_consistency(metrics, result)
101
- self._check_rsi_validity(metrics, result, profile)
118
+ self._check_flight_time_height_consistency(data, result)
119
+ self._check_velocity_height_consistency(data, result)
120
+ self._check_rsi_validity(data, result, profile)
102
121
 
103
122
  # CONSISTENCY CHECKS
104
- self._check_depth_height_ratio(metrics, result)
105
- self._check_contact_depth_ratio(metrics, result)
123
+ self._check_depth_height_ratio(data, result)
124
+ self._check_contact_depth_ratio(data, result)
106
125
 
107
126
  # TRIPLE EXTENSION ANGLES
108
- self._check_triple_extension(metrics, result, profile)
127
+ self._check_triple_extension(data, result, profile)
109
128
 
110
129
  # Finalize status
111
130
  result.finalize_status()
@@ -116,10 +135,18 @@ class CMJMetricsValidator(MetricsValidator):
116
135
  self, metrics: dict, result: CMJValidationResult, profile: AthleteProfile
117
136
  ) -> None:
118
137
  """Validate flight time."""
119
- flight_time = metrics.get("flight_time")
120
- if flight_time is None:
138
+ flight_time_raw = self._get_metric_value(
139
+ metrics, "flight_time_ms", "flight_time"
140
+ )
141
+ if flight_time_raw is None:
121
142
  return
122
143
 
144
+ # If value is in seconds (legacy), use as-is; if in ms, convert
145
+ if flight_time_raw < 10: # Likely in seconds
146
+ flight_time = flight_time_raw
147
+ else: # In milliseconds
148
+ flight_time = flight_time_raw / 1000.0
149
+
123
150
  bounds = CMJBounds.FLIGHT_TIME
124
151
 
125
152
  if not bounds.is_physically_possible(flight_time):
@@ -159,7 +186,7 @@ class CMJMetricsValidator(MetricsValidator):
159
186
  self, metrics: dict, result: CMJValidationResult, profile: AthleteProfile
160
187
  ) -> None:
161
188
  """Validate jump height."""
162
- jump_height = metrics.get("jump_height")
189
+ jump_height = self._get_metric_value(metrics, "jump_height_m", "jump_height")
163
190
  if jump_height is None:
164
191
  return
165
192
 
@@ -201,7 +228,9 @@ class CMJMetricsValidator(MetricsValidator):
201
228
  self, metrics: dict, result: CMJValidationResult, profile: AthleteProfile
202
229
  ) -> None:
203
230
  """Validate countermovement depth."""
204
- depth = metrics.get("countermovement_depth")
231
+ depth = self._get_metric_value(
232
+ metrics, "countermovement_depth_m", "countermovement_depth"
233
+ )
205
234
  if depth is None:
206
235
  return
207
236
 
@@ -243,10 +272,19 @@ class CMJMetricsValidator(MetricsValidator):
243
272
  self, metrics: dict, result: CMJValidationResult, profile: AthleteProfile
244
273
  ) -> None:
245
274
  """Validate concentric duration (contact time)."""
246
- duration = metrics.get("concentric_duration")
247
- if duration is None:
275
+ duration_raw = self._get_metric_value(
276
+ metrics, "concentric_duration_ms", "concentric_duration"
277
+ )
278
+ if duration_raw is None:
248
279
  return
249
280
 
281
+ # If value is in seconds (legacy), convert to ms first
282
+ # Values >10 are assumed to be in ms, <10 assumed to be in seconds
283
+ if duration_raw < 10: # Likely in seconds
284
+ duration = duration_raw
285
+ else: # In milliseconds
286
+ duration = duration_raw / 1000.0
287
+
250
288
  bounds = CMJBounds.CONCENTRIC_DURATION
251
289
 
252
290
  if not bounds.is_physically_possible(duration):
@@ -286,10 +324,18 @@ class CMJMetricsValidator(MetricsValidator):
286
324
  self, metrics: dict, result: CMJValidationResult, profile: AthleteProfile
287
325
  ) -> None:
288
326
  """Validate eccentric duration."""
289
- duration = metrics.get("eccentric_duration")
290
- if duration is None:
327
+ duration_raw = self._get_metric_value(
328
+ metrics, "eccentric_duration_ms", "eccentric_duration"
329
+ )
330
+ if duration_raw is None:
291
331
  return
292
332
 
333
+ # If value is in seconds (legacy), use as-is; if in ms, convert
334
+ if duration_raw < 10: # Likely in seconds
335
+ duration = duration_raw
336
+ else: # In milliseconds
337
+ duration = duration_raw / 1000.0
338
+
293
339
  bounds = CMJBounds.ECCENTRIC_DURATION
294
340
 
295
341
  if not bounds.is_physically_possible(duration):
@@ -321,7 +367,9 @@ class CMJMetricsValidator(MetricsValidator):
321
367
  ) -> None:
322
368
  """Validate peak eccentric and concentric velocities."""
323
369
  # Eccentric
324
- ecc_vel = metrics.get("peak_eccentric_velocity")
370
+ ecc_vel = self._get_metric_value(
371
+ metrics, "peak_eccentric_velocity_m_s", "peak_eccentric_velocity"
372
+ )
325
373
  if ecc_vel is not None:
326
374
  bounds = CMJBounds.PEAK_ECCENTRIC_VELOCITY
327
375
  if not bounds.is_physically_possible(ecc_vel):
@@ -349,7 +397,9 @@ class CMJMetricsValidator(MetricsValidator):
349
397
  )
350
398
 
351
399
  # Concentric
352
- con_vel = metrics.get("peak_concentric_velocity")
400
+ con_vel = self._get_metric_value(
401
+ metrics, "peak_concentric_velocity_m_s", "peak_concentric_velocity"
402
+ )
353
403
  if con_vel is not None:
354
404
  bounds = CMJBounds.PEAK_CONCENTRIC_VELOCITY
355
405
  if not bounds.is_physically_possible(con_vel):
@@ -390,12 +440,15 @@ class CMJMetricsValidator(MetricsValidator):
390
440
  self, metrics: dict, result: CMJValidationResult
391
441
  ) -> None:
392
442
  """Verify jump height is consistent with flight time."""
393
- flight_time = metrics.get("flight_time")
394
- jump_height = metrics.get("jump_height")
443
+ flight_time_ms = metrics.get("flight_time_ms")
444
+ jump_height = metrics.get("jump_height_m")
395
445
 
396
- if flight_time is None or jump_height is None:
446
+ if flight_time_ms is None or jump_height is None:
397
447
  return
398
448
 
449
+ # Convert ms to seconds
450
+ flight_time = flight_time_ms / 1000.0
451
+
399
452
  # h = g * t^2 / 8
400
453
  g = 9.81
401
454
  expected_height = (g * flight_time**2) / 8
@@ -424,8 +477,8 @@ class CMJMetricsValidator(MetricsValidator):
424
477
  self, metrics: dict, result: CMJValidationResult
425
478
  ) -> None:
426
479
  """Verify peak velocity is consistent with jump height."""
427
- velocity = metrics.get("peak_concentric_velocity")
428
- jump_height = metrics.get("jump_height")
480
+ velocity = metrics.get("peak_concentric_velocity_m_s")
481
+ jump_height = metrics.get("jump_height_m")
429
482
 
430
483
  if velocity is None or jump_height is None:
431
484
  return
@@ -461,16 +514,31 @@ class CMJMetricsValidator(MetricsValidator):
461
514
  self, metrics: dict, result: CMJValidationResult, profile: AthleteProfile
462
515
  ) -> None:
463
516
  """Validate Reactive Strength Index."""
464
- flight_time = metrics.get("flight_time")
465
- concentric_duration = metrics.get("concentric_duration")
517
+ flight_time_raw = self._get_metric_value(
518
+ metrics, "flight_time_ms", "flight_time"
519
+ )
520
+ concentric_duration_raw = self._get_metric_value(
521
+ metrics, "concentric_duration_ms", "concentric_duration"
522
+ )
466
523
 
467
524
  if (
468
- flight_time is None
469
- or concentric_duration is None
470
- or concentric_duration == 0
525
+ flight_time_raw is None
526
+ or concentric_duration_raw is None
527
+ or concentric_duration_raw == 0
471
528
  ):
472
529
  return
473
530
 
531
+ # Convert to seconds if needed
532
+ if flight_time_raw < 10: # Likely in seconds
533
+ flight_time = flight_time_raw
534
+ else: # In milliseconds
535
+ flight_time = flight_time_raw / 1000.0
536
+
537
+ if concentric_duration_raw < 10: # Likely in seconds
538
+ concentric_duration = concentric_duration_raw
539
+ else: # In milliseconds
540
+ concentric_duration = concentric_duration_raw / 1000.0
541
+
474
542
  rsi = flight_time / concentric_duration
475
543
  result.rsi = rsi
476
544
 
@@ -513,8 +581,8 @@ class CMJMetricsValidator(MetricsValidator):
513
581
  self, metrics: dict, result: CMJValidationResult
514
582
  ) -> None:
515
583
  """Check countermovement depth to jump height ratio."""
516
- depth = metrics.get("countermovement_depth")
517
- jump_height = metrics.get("jump_height")
584
+ depth = metrics.get("countermovement_depth_m")
585
+ jump_height = metrics.get("jump_height_m")
518
586
 
519
587
  if (
520
588
  depth is None or jump_height is None or depth < 0.05
@@ -557,12 +625,14 @@ class CMJMetricsValidator(MetricsValidator):
557
625
  self, metrics: dict, result: CMJValidationResult
558
626
  ) -> None:
559
627
  """Check contact time to countermovement depth ratio."""
560
- contact = metrics.get("concentric_duration")
561
- depth = metrics.get("countermovement_depth")
628
+ contact_ms = metrics.get("concentric_duration_ms")
629
+ depth = metrics.get("countermovement_depth_m")
562
630
 
563
- if contact is None or depth is None or depth < 0.05:
631
+ if contact_ms is None or depth is None or depth < 0.05:
564
632
  return
565
633
 
634
+ # Convert ms to seconds for ratio calculation
635
+ contact = contact_ms / 1000.0
566
636
  ratio = contact / depth
567
637
  result.contact_depth_ratio = ratio
568
638
 
@@ -323,7 +323,10 @@ def estimate_athlete_profile(
323
323
  Returns:
324
324
  Estimated AthleteProfile
325
325
  """
326
- jump_height = metrics_dict.get("jump_height", 0)
326
+ # Support both nested "data" structure and flat structure
327
+ # Extract with unit suffix as used in serialization, or without suffix (legacy)
328
+ data = metrics_dict.get("data", metrics_dict)
329
+ jump_height = data.get("jump_height_m") or data.get("jump_height", 0)
327
330
 
328
331
  if jump_height < 0.20:
329
332
  return AthleteProfile.ELDERLY
@@ -108,10 +108,12 @@ def auto_tune_parameters(
108
108
  # =================================================================
109
109
 
110
110
  # Velocity threshold: Scale inversely with fps
111
- # At 30fps, feet move ~2% of frame per frame when "stationary"
112
- # At 60fps, feet move ~1% of frame per frame when "stationary"
113
- # Formula: threshold = 0.02 * (30 / fps)
114
- base_velocity_threshold = 0.02 * (30.0 / fps)
111
+ # Empirically validated with 45° oblique videos at 60fps:
112
+ # - Standing (stationary): ~0.001 mean, 0.0011 max
113
+ # - Flight/drop (moving): ~0.005-0.009
114
+ # Target threshold: 0.002 at 60fps for clear separation
115
+ # Formula: threshold = 0.004 * (30 / fps)
116
+ base_velocity_threshold = 0.004 * (30.0 / fps)
115
117
 
116
118
  # Min contact frames: Scale with fps to maintain same time duration
117
119
  # Goal: ~100ms minimum contact (3 frames @ 30fps, 6 frames @ 60fps)
@@ -57,6 +57,7 @@ class DropJumpMetrics:
57
57
  self.jump_height: float | None = None
58
58
  self.jump_height_kinematic: float | None = None # From flight time
59
59
  self.jump_height_trajectory: float | None = None # From position tracking
60
+ self.drop_start_frame: int | None = None # Frame when athlete leaves box
60
61
  self.contact_start_frame: int | None = None
61
62
  self.contact_end_frame: int | None = None
62
63
  self.flight_start_frame: int | None = None
@@ -164,7 +165,7 @@ def _determine_drop_start_frame(
164
165
  foot_y_positions,
165
166
  fps,
166
167
  min_stationary_duration=0.5,
167
- position_change_threshold=0.005,
168
+ position_change_threshold=0.01, # Improved from 0.005 for better accuracy
168
169
  smoothing_window=smoothing_window,
169
170
  )
170
171
  return drop_start_frame
@@ -412,6 +413,11 @@ def calculate_drop_jump_metrics(
412
413
  drop_start_frame, foot_y_positions, fps, smoothing_window
413
414
  )
414
415
 
416
+ # Store drop start frame in metrics
417
+ metrics.drop_start_frame = (
418
+ drop_start_frame_value if drop_start_frame_value > 0 else None
419
+ )
420
+
415
421
  # Find contact phases
416
422
  phases = find_contact_phases(contact_states)
417
423
  interpolated_phases = find_interpolated_phase_transitions_with_curvature(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kinemotion
3
- Version: 0.35.0
3
+ Version: 0.35.2
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,16 +1,16 @@
1
1
  kinemotion/__init__.py,sha256=sxdDOekOrIgjxm842gy-6zfq7OWmGl9ShJtXCm4JI7c,723
2
- kinemotion/api.py,sha256=Rdz5XjFsIMK-9rByzOYiDTPz27vINB01fmdeDzPoxZY,39339
2
+ kinemotion/api.py,sha256=nbDbyzhXIMA04tGqKPH8R0fR66zgtu14x6NgWroy_QU,39471
3
3
  kinemotion/cli.py,sha256=cqYV_7URH0JUDy1VQ_EDLv63FmNO4Ns20m6s1XAjiP4,464
4
4
  kinemotion/cmj/__init__.py,sha256=Ynv0-Oco4I3Y1Ubj25m3h9h2XFqeNwpAewXmAYOmwfU,127
5
- kinemotion/cmj/analysis.py,sha256=DmZ7vptPd5PAkaWW-oSablA6DWrCj8u2qPxlKQm9cVU,17089
5
+ kinemotion/cmj/analysis.py,sha256=OfNTMLPwZIRYbX-Yd8jgZ-7pqnHRz7L2bWAHVYFsQ60,18955
6
6
  kinemotion/cmj/cli.py,sha256=Mj2h9It1jVjAauvtCxfLWTRijj7zbYhxZuebhw2Zz6w,10828
7
7
  kinemotion/cmj/debug_overlay.py,sha256=fXmWoHhqMLGo4vTtB6Ezs3yLUDOLw63zLIgU2gFlJQU,15892
8
8
  kinemotion/cmj/joint_angles.py,sha256=HmheIEiKcQz39cRezk4h-htorOhGNPsqKIR9RsAEKts,9960
9
9
  kinemotion/cmj/kinematics.py,sha256=qRBe87NkX-7HQTQ8RoF-EpvfcffgP5vycJJRrxpHboc,10307
10
- kinemotion/cmj/metrics_validator.py,sha256=p_hgg0Q0UAiWGNXKSW4E9kPvyEgPNAujaxrk2XUmsBY,28619
11
- kinemotion/cmj/validation_bounds.py,sha256=yRhmpUzGJs0QYyL1o3mOkgUSTe4XTO2MONMITTjCv3c,11778
10
+ kinemotion/cmj/metrics_validator.py,sha256=V_fmlczYH06SBtwqESv-IfGi3wDsIy3RQbd7VwOyNo0,31359
11
+ kinemotion/cmj/validation_bounds.py,sha256=9ZTo68fl3ooyWjXXyTMRLpK9tFANa_rQf3oHhq7iQGE,11995
12
12
  kinemotion/core/__init__.py,sha256=HsqolRa60cW3vrG8F9Lvr9WvWcs5hCmsTzSgo7imi-4,1278
13
- kinemotion/core/auto_tuning.py,sha256=j6cul_qC6k0XyryCG93C1AWH2MKPj3UBMzuX02xaqfI,11235
13
+ kinemotion/core/auto_tuning.py,sha256=wtCUMOhBChVJNXfEeku3GCMW4qED6MF-O_mv2sPTiVQ,11324
14
14
  kinemotion/core/cli_utils.py,sha256=zbnifPhD-OYofJioeYfJtshuWcl8OAEWtqCGVF4ctAI,7966
15
15
  kinemotion/core/debug_overlay_utils.py,sha256=TyUb5okv5qw8oeaX3jsUO_kpwf1NnaHEAOTm-8LwTno,4587
16
16
  kinemotion/core/experimental.py,sha256=IK05AF4aZS15ke85hF3TWCqRIXU1AlD_XKzFz735Ua8,3640
@@ -26,12 +26,12 @@ kinemotion/dropjump/__init__.py,sha256=tC3H3BrCg8Oj-db-Vrtx4PH_llR1Ppkd5jwaOjhQc
26
26
  kinemotion/dropjump/analysis.py,sha256=B_N_51WoChyQ8I7yaeKeqj3vw7NufgV_3QL-FBZEtW4,28752
27
27
  kinemotion/dropjump/cli.py,sha256=n_Wfv3AC6YIgRPYhO3F2nTSai0NR7fh95nAoWjryQeY,16250
28
28
  kinemotion/dropjump/debug_overlay.py,sha256=LkPw6ucb7beoYWS4L-Lvjs1KLCm5wAWDAfiznUeV2IQ,5668
29
- kinemotion/dropjump/kinematics.py,sha256=IH6nCOwTuocQNX1VPS_am9vPpMRUUla0a0MjDhEiXnA,17129
29
+ kinemotion/dropjump/kinematics.py,sha256=yB4ws4VG59SUGcw1J-uXfDFfCMXBdzRh5C4jo0osXbs,17404
30
30
  kinemotion/dropjump/metrics_validator.py,sha256=sx4RodHpeiW8_PRB0GUJvkUWto1Ard1Dvrc9z8eKk7M,9351
31
31
  kinemotion/dropjump/validation_bounds.py,sha256=5b4I3CKPybuvrbn-nP5yCcGF_sH4Vtyw3a5AWWvWnBk,4645
32
32
  kinemotion/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
- kinemotion-0.35.0.dist-info/METADATA,sha256=vMAyXr_N5nJrQ4WvcnmQClVOw_EyvliO3PN09HMIsMw,26020
34
- kinemotion-0.35.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
35
- kinemotion-0.35.0.dist-info/entry_points.txt,sha256=zaqnAnjLvcdrk1Qvj5nvXZCZ2gp0prS7it1zTJygcIY,50
36
- kinemotion-0.35.0.dist-info/licenses/LICENSE,sha256=KZajvqsHw0NoOHOi2q0FZ4NBe9HdV6oey-IPYAtHXfg,1088
37
- kinemotion-0.35.0.dist-info/RECORD,,
33
+ kinemotion-0.35.2.dist-info/METADATA,sha256=tb4l8YTLu_HqhK7tH5lwKLmQkBXvFDqhIyPPKP6qHgU,26020
34
+ kinemotion-0.35.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
35
+ kinemotion-0.35.2.dist-info/entry_points.txt,sha256=zaqnAnjLvcdrk1Qvj5nvXZCZ2gp0prS7it1zTJygcIY,50
36
+ kinemotion-0.35.2.dist-info/licenses/LICENSE,sha256=KZajvqsHw0NoOHOi2q0FZ4NBe9HdV6oey-IPYAtHXfg,1088
37
+ kinemotion-0.35.2.dist-info/RECORD,,