kinemotion 0.34.0__py3-none-any.whl → 0.35.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
@@ -11,6 +11,7 @@ import numpy as np
11
11
  from .cmj.analysis import detect_cmj_phases
12
12
  from .cmj.debug_overlay import CMJDebugOverlayRenderer
13
13
  from .cmj.kinematics import CMJMetrics, calculate_cmj_metrics
14
+ from .cmj.metrics_validator import CMJMetricsValidator
14
15
  from .core.auto_tuning import (
15
16
  AnalysisParameters,
16
17
  QualityPreset,
@@ -18,8 +19,6 @@ from .core.auto_tuning import (
18
19
  analyze_video_sample,
19
20
  auto_tune_parameters,
20
21
  )
21
- from .core.cmj_metrics_validator import CMJMetricsValidator
22
- from .core.dropjump_metrics_validator import DropJumpMetricsValidator
23
22
  from .core.filtering import reject_outliers
24
23
  from .core.metadata import (
25
24
  AlgorithmConfig,
@@ -44,6 +43,7 @@ from .dropjump.analysis import (
44
43
  )
45
44
  from .dropjump.debug_overlay import DebugOverlayRenderer
46
45
  from .dropjump.kinematics import DropJumpMetrics, calculate_drop_jump_metrics
46
+ from .dropjump.metrics_validator import DropJumpMetricsValidator
47
47
 
48
48
 
49
49
  def _parse_quality_preset(quality: str) -> QualityPreset:
@@ -5,6 +5,7 @@ from enum import Enum
5
5
  import numpy as np
6
6
  from scipy.signal import savgol_filter
7
7
 
8
+ from ..core.experimental import unused
8
9
  from ..core.smoothing import compute_acceleration_from_derivative
9
10
 
10
11
 
@@ -52,6 +53,10 @@ class CMJPhase(Enum):
52
53
  UNKNOWN = "unknown"
53
54
 
54
55
 
56
+ @unused(
57
+ reason="Alternative implementation not called by pipeline",
58
+ since="0.34.0",
59
+ )
55
60
  def find_standing_phase(
56
61
  positions: np.ndarray,
57
62
  velocities: np.ndarray,
@@ -101,6 +106,10 @@ def find_standing_phase(
101
106
  return None
102
107
 
103
108
 
109
+ @unused(
110
+ reason="Alternative implementation not called by pipeline",
111
+ since="0.34.0",
112
+ )
104
113
  def find_countermovement_start(
105
114
  velocities: np.ndarray,
106
115
  countermovement_threshold: float = 0.015,
@@ -184,99 +193,6 @@ def find_lowest_point(
184
193
  return lowest_frame
185
194
 
186
195
 
187
- def refine_transition_with_curvature(
188
- positions: np.ndarray,
189
- velocities: np.ndarray,
190
- initial_frame: int,
191
- transition_type: str,
192
- search_radius: int = 3,
193
- window_length: int = 5,
194
- polyorder: int = 2,
195
- ) -> float:
196
- """
197
- Refine transition frame using trajectory curvature (acceleration patterns).
198
-
199
- Uses acceleration (second derivative) to identify characteristic patterns:
200
- - Landing: Large acceleration spike (impact deceleration)
201
- - Takeoff: Acceleration change (transition from static to flight)
202
-
203
- Args:
204
- positions: Array of vertical positions
205
- velocities: Array of vertical velocities
206
- initial_frame: Initial estimate of transition frame
207
- transition_type: Type of transition ("takeoff" or "landing")
208
- search_radius: Frames to search around initial estimate (±radius)
209
- window_length: Window size for acceleration calculation
210
- polyorder: Polynomial order for Savitzky-Golay filter
211
-
212
- Returns:
213
- Refined fractional frame index.
214
- """
215
- # Compute acceleration using second derivative
216
- acceleration = compute_acceleration_from_derivative(
217
- positions, window_length=window_length, polyorder=polyorder
218
- )
219
-
220
- # Define search window
221
- search_start = max(0, initial_frame - search_radius)
222
- search_end = min(len(positions), initial_frame + search_radius + 1)
223
-
224
- if search_start >= search_end:
225
- return float(initial_frame)
226
-
227
- search_accel = acceleration[search_start:search_end]
228
-
229
- if transition_type == "landing":
230
- # Landing: Find maximum absolute acceleration (impact)
231
- peak_idx = int(np.argmax(np.abs(search_accel)))
232
- elif transition_type == "takeoff":
233
- # Takeoff: Find maximum acceleration change
234
- accel_change = np.abs(np.diff(search_accel))
235
- if len(accel_change) > 0:
236
- peak_idx = int(np.argmax(accel_change))
237
- else:
238
- peak_idx = 0
239
- else:
240
- return float(initial_frame)
241
-
242
- curvature_frame = search_start + peak_idx
243
-
244
- # Blend curvature-based estimate with velocity-based estimate
245
- # 70% curvature, 30% velocity
246
- blended_frame = 0.7 * curvature_frame + 0.3 * initial_frame
247
-
248
- return float(blended_frame)
249
-
250
-
251
- def interpolate_threshold_crossing(
252
- vel_before: float,
253
- vel_after: float,
254
- velocity_threshold: float,
255
- ) -> float:
256
- """
257
- Find fractional offset where velocity crosses threshold between two frames.
258
-
259
- Uses linear interpolation assuming velocity changes linearly between frames.
260
-
261
- Args:
262
- vel_before: Velocity at frame boundary N (absolute value)
263
- vel_after: Velocity at frame boundary N+1 (absolute value)
264
- velocity_threshold: Threshold value
265
-
266
- Returns:
267
- Fractional offset from frame N (0.0 to 1.0)
268
- """
269
- # Handle edge cases
270
- if abs(vel_after - vel_before) < 1e-9: # Velocity not changing
271
- return 0.5
272
-
273
- # Linear interpolation
274
- t = (velocity_threshold - vel_before) / (vel_after - vel_before)
275
-
276
- # Clamp to [0, 1] range
277
- return float(max(0.0, min(1.0, t)))
278
-
279
-
280
196
  def find_cmj_takeoff_from_velocity_peak(
281
197
  positions: np.ndarray,
282
198
  velocities: np.ndarray,
@@ -372,6 +288,10 @@ def find_cmj_landing_from_position_peak(
372
288
  return float(landing_frame)
373
289
 
374
290
 
291
+ @unused(
292
+ reason="Experimental alternative superseded by backward search algorithm",
293
+ since="0.34.0",
294
+ )
375
295
  def find_interpolated_takeoff_landing(
376
296
  positions: np.ndarray,
377
297
  velocities: np.ndarray,
@@ -417,7 +337,7 @@ def find_interpolated_takeoff_landing(
417
337
  return (takeoff_frame, landing_frame)
418
338
 
419
339
 
420
- def _find_takeoff_frame(
340
+ def find_takeoff_frame(
421
341
  velocities: np.ndarray, peak_height_frame: int, fps: float
422
342
  ) -> float:
423
343
  """Find takeoff frame as peak upward velocity before peak height.
@@ -448,7 +368,7 @@ def _find_takeoff_frame(
448
368
  return float(takeoff_search_start + peak_vel_idx)
449
369
 
450
370
 
451
- def _find_lowest_frame(
371
+ def find_lowest_frame(
452
372
  velocities: np.ndarray, positions: np.ndarray, takeoff_frame: float, fps: float
453
373
  ) -> float:
454
374
  """Find lowest point frame before takeoff."""
@@ -469,7 +389,7 @@ def _find_lowest_frame(
469
389
  return float(int(takeoff_frame) - int(fps * 0.2))
470
390
 
471
391
 
472
- def _find_landing_frame(
392
+ def find_landing_frame(
473
393
  accelerations: np.ndarray, peak_height_frame: int, fps: float
474
394
  ) -> float:
475
395
  """Find landing frame after peak height after takeoff.
@@ -502,7 +422,7 @@ def _find_landing_frame(
502
422
  return float(landing_search_start + landing_idx)
503
423
 
504
424
 
505
- def _find_standing_end(velocities: np.ndarray, lowest_point: float) -> float | None:
425
+ def find_standing_end(velocities: np.ndarray, lowest_point: float) -> float | None:
506
426
  """Find end of standing phase before lowest point."""
507
427
  if lowest_point <= 20:
508
428
  return None
@@ -556,9 +476,9 @@ def detect_cmj_phases(
556
476
  return None # Peak too early, invalid
557
477
 
558
478
  # Step 2-4: Find all phases using helper functions
559
- takeoff_frame = _find_takeoff_frame(velocities, peak_height_frame, fps)
560
- lowest_point = _find_lowest_frame(velocities, positions, takeoff_frame, fps)
561
- landing_frame = _find_landing_frame(accelerations, peak_height_frame, fps)
562
- standing_end = _find_standing_end(velocities, lowest_point)
479
+ takeoff_frame = find_takeoff_frame(velocities, peak_height_frame, fps)
480
+ lowest_point = find_lowest_frame(velocities, positions, takeoff_frame, fps)
481
+ landing_frame = find_landing_frame(accelerations, peak_height_frame, fps)
482
+ standing_end = find_standing_end(velocities, lowest_point)
563
483
 
564
484
  return (standing_end, lowest_point, takeoff_frame, landing_frame)
@@ -7,120 +7,33 @@ Provides severity levels (ERROR, WARNING, INFO) for different categories
7
7
  of metric issues.
8
8
  """
9
9
 
10
- from dataclasses import dataclass, field
11
- from enum import Enum
10
+ from dataclasses import dataclass
12
11
 
13
- from kinemotion.core.cmj_validation_bounds import (
14
- AthleteProfile,
12
+ from kinemotion.cmj.validation_bounds import (
15
13
  CMJBounds,
16
- MetricBounds,
17
14
  MetricConsistency,
18
15
  RSIBounds,
19
16
  TripleExtensionBounds,
20
17
  estimate_athlete_profile,
21
18
  )
22
-
23
-
24
- class ValidationSeverity(Enum):
25
- """Severity level for validation issues."""
26
-
27
- ERROR = "ERROR" # Metrics invalid, likely data corruption
28
- WARNING = "WARNING" # Metrics valid but unusual, needs review
29
- INFO = "INFO" # Normal variation, informational only
30
-
31
-
32
- @dataclass
33
- class ValidationIssue:
34
- """Single validation issue."""
35
-
36
- severity: ValidationSeverity
37
- metric: str
38
- message: str
39
- value: float | None = None
40
- bounds: tuple[float, float] | None = None
19
+ from kinemotion.core.validation import (
20
+ AthleteProfile,
21
+ MetricBounds,
22
+ MetricsValidator,
23
+ ValidationResult,
24
+ )
41
25
 
42
26
 
43
27
  @dataclass
44
- class ValidationResult:
45
- """Complete validation result for CMJ metrics."""
28
+ class CMJValidationResult(ValidationResult):
29
+ """CMJ-specific validation result."""
46
30
 
47
- issues: list[ValidationIssue] = field(default_factory=list)
48
- status: str = "PASS" # "PASS", "PASS_WITH_WARNINGS", "FAIL"
49
- athlete_profile: AthleteProfile | None = None
50
31
  rsi: float | None = None
51
32
  height_flight_time_consistency: float | None = None # % error
52
33
  velocity_height_consistency: float | None = None # % error
53
34
  depth_height_ratio: float | None = None
54
35
  contact_depth_ratio: float | None = None
55
36
 
56
- def add_error(
57
- self,
58
- metric: str,
59
- message: str,
60
- value: float | None = None,
61
- bounds: tuple[float, float] | None = None,
62
- ) -> None:
63
- """Add error-level issue."""
64
- self.issues.append(
65
- ValidationIssue(
66
- severity=ValidationSeverity.ERROR,
67
- metric=metric,
68
- message=message,
69
- value=value,
70
- bounds=bounds,
71
- )
72
- )
73
-
74
- def add_warning(
75
- self,
76
- metric: str,
77
- message: str,
78
- value: float | None = None,
79
- bounds: tuple[float, float] | None = None,
80
- ) -> None:
81
- """Add warning-level issue."""
82
- self.issues.append(
83
- ValidationIssue(
84
- severity=ValidationSeverity.WARNING,
85
- metric=metric,
86
- message=message,
87
- value=value,
88
- bounds=bounds,
89
- )
90
- )
91
-
92
- def add_info(
93
- self,
94
- metric: str,
95
- message: str,
96
- value: float | None = None,
97
- ) -> None:
98
- """Add info-level issue."""
99
- self.issues.append(
100
- ValidationIssue(
101
- severity=ValidationSeverity.INFO,
102
- metric=metric,
103
- message=message,
104
- value=value,
105
- )
106
- )
107
-
108
- def finalize_status(self) -> None:
109
- """Determine final pass/fail status based on issues."""
110
- has_errors = any(
111
- issue.severity == ValidationSeverity.ERROR for issue in self.issues
112
- )
113
- has_warnings = any(
114
- issue.severity == ValidationSeverity.WARNING for issue in self.issues
115
- )
116
-
117
- if has_errors:
118
- self.status = "FAIL"
119
- elif has_warnings:
120
- self.status = "PASS_WITH_WARNINGS"
121
- else:
122
- self.status = "PASS"
123
-
124
37
  def to_dict(self) -> dict:
125
38
  """Convert validation result to JSON-serializable dictionary.
126
39
 
@@ -152,28 +65,19 @@ class ValidationResult:
152
65
  }
153
66
 
154
67
 
155
- class CMJMetricsValidator:
68
+ class CMJMetricsValidator(MetricsValidator):
156
69
  """Comprehensive CMJ metrics validator."""
157
70
 
158
- def __init__(self, assumed_profile: AthleteProfile | None = None):
159
- """Initialize validator.
160
-
161
- Args:
162
- assumed_profile: If provided, validate against this specific profile.
163
- Otherwise, estimate from metrics.
164
- """
165
- self.assumed_profile = assumed_profile
166
-
167
- def validate(self, metrics: dict) -> ValidationResult:
71
+ def validate(self, metrics: dict) -> CMJValidationResult:
168
72
  """Validate CMJ metrics comprehensively.
169
73
 
170
74
  Args:
171
75
  metrics: Dictionary with CMJ metric values
172
76
 
173
77
  Returns:
174
- ValidationResult with all issues and status
78
+ CMJValidationResult with all issues and status
175
79
  """
176
- result = ValidationResult()
80
+ result = CMJValidationResult()
177
81
 
178
82
  # Estimate athlete profile if not provided
179
83
  if self.assumed_profile:
@@ -209,7 +113,7 @@ class CMJMetricsValidator:
209
113
  return result
210
114
 
211
115
  def _check_flight_time(
212
- self, metrics: dict, result: ValidationResult, profile: AthleteProfile
116
+ self, metrics: dict, result: CMJValidationResult, profile: AthleteProfile
213
117
  ) -> None:
214
118
  """Validate flight time."""
215
119
  flight_time = metrics.get("flight_time")
@@ -252,7 +156,7 @@ class CMJMetricsValidator:
252
156
  )
253
157
 
254
158
  def _check_jump_height(
255
- self, metrics: dict, result: ValidationResult, profile: AthleteProfile
159
+ self, metrics: dict, result: CMJValidationResult, profile: AthleteProfile
256
160
  ) -> None:
257
161
  """Validate jump height."""
258
162
  jump_height = metrics.get("jump_height")
@@ -294,7 +198,7 @@ class CMJMetricsValidator:
294
198
  )
295
199
 
296
200
  def _check_countermovement_depth(
297
- self, metrics: dict, result: ValidationResult, profile: AthleteProfile
201
+ self, metrics: dict, result: CMJValidationResult, profile: AthleteProfile
298
202
  ) -> None:
299
203
  """Validate countermovement depth."""
300
204
  depth = metrics.get("countermovement_depth")
@@ -336,7 +240,7 @@ class CMJMetricsValidator:
336
240
  )
337
241
 
338
242
  def _check_concentric_duration(
339
- self, metrics: dict, result: ValidationResult, profile: AthleteProfile
243
+ self, metrics: dict, result: CMJValidationResult, profile: AthleteProfile
340
244
  ) -> None:
341
245
  """Validate concentric duration (contact time)."""
342
246
  duration = metrics.get("concentric_duration")
@@ -379,7 +283,7 @@ class CMJMetricsValidator:
379
283
  )
380
284
 
381
285
  def _check_eccentric_duration(
382
- self, metrics: dict, result: ValidationResult, profile: AthleteProfile
286
+ self, metrics: dict, result: CMJValidationResult, profile: AthleteProfile
383
287
  ) -> None:
384
288
  """Validate eccentric duration."""
385
289
  duration = metrics.get("eccentric_duration")
@@ -413,7 +317,7 @@ class CMJMetricsValidator:
413
317
  )
414
318
 
415
319
  def _check_peak_velocities(
416
- self, metrics: dict, result: ValidationResult, profile: AthleteProfile
320
+ self, metrics: dict, result: CMJValidationResult, profile: AthleteProfile
417
321
  ) -> None:
418
322
  """Validate peak eccentric and concentric velocities."""
419
323
  # Eccentric
@@ -483,7 +387,7 @@ class CMJMetricsValidator:
483
387
  )
484
388
 
485
389
  def _check_flight_time_height_consistency(
486
- self, metrics: dict, result: ValidationResult
390
+ self, metrics: dict, result: CMJValidationResult
487
391
  ) -> None:
488
392
  """Verify jump height is consistent with flight time."""
489
393
  flight_time = metrics.get("flight_time")
@@ -517,7 +421,7 @@ class CMJMetricsValidator:
517
421
  )
518
422
 
519
423
  def _check_velocity_height_consistency(
520
- self, metrics: dict, result: ValidationResult
424
+ self, metrics: dict, result: CMJValidationResult
521
425
  ) -> None:
522
426
  """Verify peak velocity is consistent with jump height."""
523
427
  velocity = metrics.get("peak_concentric_velocity")
@@ -554,7 +458,7 @@ class CMJMetricsValidator:
554
458
  )
555
459
 
556
460
  def _check_rsi_validity(
557
- self, metrics: dict, result: ValidationResult, profile: AthleteProfile
461
+ self, metrics: dict, result: CMJValidationResult, profile: AthleteProfile
558
462
  ) -> None:
559
463
  """Validate Reactive Strength Index."""
560
464
  flight_time = metrics.get("flight_time")
@@ -606,7 +510,7 @@ class CMJMetricsValidator:
606
510
  )
607
511
 
608
512
  def _check_depth_height_ratio(
609
- self, metrics: dict, result: ValidationResult
513
+ self, metrics: dict, result: CMJValidationResult
610
514
  ) -> None:
611
515
  """Check countermovement depth to jump height ratio."""
612
516
  depth = metrics.get("countermovement_depth")
@@ -650,7 +554,7 @@ class CMJMetricsValidator:
650
554
  )
651
555
 
652
556
  def _check_contact_depth_ratio(
653
- self, metrics: dict, result: ValidationResult
557
+ self, metrics: dict, result: CMJValidationResult
654
558
  ) -> None:
655
559
  """Check contact time to countermovement depth ratio."""
656
560
  contact = metrics.get("concentric_duration")
@@ -691,7 +595,7 @@ class CMJMetricsValidator:
691
595
  )
692
596
 
693
597
  def _check_triple_extension(
694
- self, metrics: dict, result: ValidationResult, profile: AthleteProfile
598
+ self, metrics: dict, result: CMJValidationResult, profile: AthleteProfile
695
599
  ) -> None:
696
600
  """Validate triple extension angles."""
697
601
  angles = metrics.get("triple_extension")
@@ -750,7 +654,7 @@ class CMJMetricsValidator:
750
654
  self._check_joint_compensation_pattern(angles, result, profile)
751
655
 
752
656
  def _check_joint_compensation_pattern(
753
- self, angles: dict, result: ValidationResult, profile: AthleteProfile
657
+ self, angles: dict, result: CMJValidationResult, profile: AthleteProfile
754
658
  ) -> None:
755
659
  """Detect compensatory joint patterns in triple extension.
756
660
 
@@ -15,64 +15,7 @@ References:
15
15
  - Bogdanis (2012): Plyometric training effects
16
16
  """
17
17
 
18
- from dataclasses import dataclass
19
- from enum import Enum
20
-
21
-
22
- class AthleteProfile(Enum):
23
- """Athlete performance categories for metric bounds."""
24
-
25
- ELDERLY = "elderly" # 70+, deconditioned
26
- UNTRAINED = "untrained" # Sedentary, no training
27
- RECREATIONAL = "recreational" # Fitness class, moderate activity
28
- TRAINED = "trained" # Regular athlete, 3-5 years training
29
- ELITE = "elite" # Competitive athlete, college/professional level
30
-
31
-
32
- @dataclass
33
- class MetricBounds:
34
- """Physiological bounds for a single metric.
35
-
36
- Attributes:
37
- absolute_min: Absolute minimum value (error threshold)
38
- practical_min: Practical minimum for weakest athletes
39
- recreational_min: Minimum for recreational athletes
40
- recreational_max: Maximum for recreational athletes
41
- elite_min: Minimum for elite athletes
42
- elite_max: Maximum for elite athletes
43
- absolute_max: Absolute maximum value (error threshold)
44
- unit: Unit of measurement (e.g., "m", "s", "m/s", "degrees")
45
- """
46
-
47
- absolute_min: float
48
- practical_min: float
49
- recreational_min: float
50
- recreational_max: float
51
- elite_min: float
52
- elite_max: float
53
- absolute_max: float
54
- unit: str
55
-
56
- def contains(self, value: float, profile: AthleteProfile) -> bool:
57
- """Check if value is within bounds for athlete profile."""
58
- if profile == AthleteProfile.ELDERLY:
59
- return self.practical_min <= value <= self.recreational_max
60
- elif profile == AthleteProfile.UNTRAINED:
61
- return self.practical_min <= value <= self.recreational_max
62
- elif profile == AthleteProfile.RECREATIONAL:
63
- return self.recreational_min <= value <= self.recreational_max
64
- elif profile == AthleteProfile.TRAINED:
65
- # Trained athletes: midpoint between recreational and elite
66
- trained_min = (self.recreational_min + self.elite_min) / 2
67
- trained_max = (self.recreational_max + self.elite_max) / 2
68
- return trained_min <= value <= trained_max
69
- elif profile == AthleteProfile.ELITE:
70
- return self.elite_min <= value <= self.elite_max
71
- return False
72
-
73
- def is_physically_possible(self, value: float) -> bool:
74
- """Check if value is within absolute physiological limits."""
75
- return self.absolute_min <= value <= self.absolute_max
18
+ from kinemotion.core.validation import AthleteProfile, MetricBounds
76
19
 
77
20
 
78
21
  class CMJBounds:
@@ -6,6 +6,7 @@ from typing import Any, Protocol
6
6
  import click
7
7
 
8
8
  from .auto_tuning import AnalysisParameters, QualityPreset, VideoCharacteristics
9
+ from .experimental import unused
9
10
  from .pose import PoseTracker
10
11
  from .smoothing import smooth_landmarks, smooth_landmarks_advanced
11
12
  from .video_io import VideoProcessor
@@ -22,6 +23,11 @@ class ExpertParameters(Protocol):
22
23
  visibility_threshold: float | None
23
24
 
24
25
 
26
+ @unused(
27
+ reason="Not called by analysis pipeline - remnant from CLI refactoring",
28
+ remove_in="1.0.0",
29
+ since="0.34.0",
30
+ )
25
31
  def determine_initial_confidence(
26
32
  quality_preset: QualityPreset,
27
33
  expert_params: ExpertParameters,
@@ -54,6 +60,11 @@ def determine_initial_confidence(
54
60
  return initial_detection_conf, initial_tracking_conf
55
61
 
56
62
 
63
+ @unused(
64
+ reason="Not called by analysis pipeline - remnant from CLI refactoring",
65
+ remove_in="1.0.0",
66
+ since="0.34.0",
67
+ )
57
68
  def track_all_frames(video: VideoProcessor, tracker: PoseTracker) -> tuple[list, list]:
58
69
  """Track pose landmarks in all video frames.
59
70
 
@@ -84,6 +95,11 @@ def track_all_frames(video: VideoProcessor, tracker: PoseTracker) -> tuple[list,
84
95
  return frames, landmarks_sequence
85
96
 
86
97
 
98
+ @unused(
99
+ reason="Not called by analysis pipeline - remnant from CLI refactoring",
100
+ remove_in="1.0.0",
101
+ since="0.34.0",
102
+ )
87
103
  def apply_expert_param_overrides(
88
104
  params: AnalysisParameters, expert_params: ExpertParameters
89
105
  ) -> AnalysisParameters:
@@ -107,6 +123,11 @@ def apply_expert_param_overrides(
107
123
  return params
108
124
 
109
125
 
126
+ @unused(
127
+ reason="Not called by analysis pipeline - remnant from CLI refactoring",
128
+ remove_in="1.0.0",
129
+ since="0.34.0",
130
+ )
110
131
  def print_auto_tuned_params(
111
132
  video: VideoProcessor,
112
133
  quality_preset: QualityPreset,
@@ -161,6 +182,11 @@ def print_auto_tuned_params(
161
182
  click.echo("=" * 60 + "\n", err=True)
162
183
 
163
184
 
185
+ @unused(
186
+ reason="Not called by analysis pipeline - remnant from CLI refactoring",
187
+ remove_in="1.0.0",
188
+ since="0.34.0",
189
+ )
164
190
  def smooth_landmark_sequence(
165
191
  landmarks_sequence: list, params: AnalysisParameters
166
192
  ) -> list: