kinemotion 0.47.4__py3-none-any.whl → 0.49.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
@@ -7,6 +7,9 @@ The actual implementations have been moved to their respective submodules:
7
7
  """
8
8
 
9
9
  # CMJ API
10
+ from .cmj.api import (
11
+ AnalysisOverrides as CMJAnalysisOverrides,
12
+ )
10
13
  from .cmj.api import (
11
14
  CMJVideoConfig,
12
15
  CMJVideoResult,
@@ -17,6 +20,7 @@ from .cmj.kinematics import CMJMetrics
17
20
 
18
21
  # Drop jump API
19
22
  from .dropjump.api import (
23
+ AnalysisOverrides,
20
24
  DropJumpVideoConfig,
21
25
  DropJumpVideoResult,
22
26
  process_dropjump_video,
@@ -25,11 +29,13 @@ from .dropjump.api import (
25
29
 
26
30
  __all__ = [
27
31
  # Drop jump
32
+ "AnalysisOverrides",
28
33
  "DropJumpVideoConfig",
29
34
  "DropJumpVideoResult",
30
35
  "process_dropjump_video",
31
36
  "process_dropjump_videos_bulk",
32
37
  # CMJ
38
+ "CMJAnalysisOverrides",
33
39
  "CMJMetrics",
34
40
  "CMJVideoConfig",
35
41
  "CMJVideoResult",
kinemotion/cmj/api.py CHANGED
@@ -48,6 +48,20 @@ from .kinematics import CMJMetrics, calculate_cmj_metrics
48
48
  from .metrics_validator import CMJMetricsValidator
49
49
 
50
50
 
51
+ @dataclass
52
+ class AnalysisOverrides:
53
+ """Optional overrides for analysis parameters.
54
+
55
+ Allows fine-tuning of specific analysis parameters beyond quality presets.
56
+ If None, values will be determined by the quality preset.
57
+ """
58
+
59
+ smoothing_window: int | None = None
60
+ velocity_threshold: float | None = None
61
+ min_contact_frames: int | None = None
62
+ visibility_threshold: float | None = None
63
+
64
+
51
65
  def _generate_debug_video(
52
66
  output_video: str,
53
67
  frames: list[NDArray[np.uint8]],
@@ -109,9 +123,9 @@ def _print_timing_summary(start_time: float, timer: Timer, metrics: CMJMetrics)
109
123
  for stage, duration in stage_times.items():
110
124
  percentage = (duration / total_time) * 100
111
125
  dur_ms = duration * 1000
112
- print(f"{stage:. <40} {dur_ms:>6.0f}ms ({percentage:>5.1f}%)")
126
+ print(f"{stage:.<40} {dur_ms:>6.0f}ms ({percentage:>5.1f}%)")
113
127
  total_ms = total_time * 1000
114
- print(f"{('Total'):.>40} {total_ms:>6.0f}ms (100.0%)")
128
+ print(f"{'Total':.<40} {total_ms:>6.0f}ms (100.0%)")
115
129
  print()
116
130
 
117
131
  print(f"\nJump height: {metrics.jump_height:.3f}m")
@@ -212,10 +226,7 @@ class CMJVideoConfig:
212
226
  quality: str = "balanced"
213
227
  output_video: str | None = None
214
228
  json_output: str | None = None
215
- smoothing_window: int | None = None
216
- velocity_threshold: float | None = None
217
- min_contact_frames: int | None = None
218
- visibility_threshold: float | None = None
229
+ overrides: AnalysisOverrides | None = None
219
230
  detection_confidence: float | None = None
220
231
  tracking_confidence: float | None = None
221
232
 
@@ -236,10 +247,7 @@ def process_cmj_video(
236
247
  quality: str = "balanced",
237
248
  output_video: str | None = None,
238
249
  json_output: str | None = None,
239
- smoothing_window: int | None = None,
240
- velocity_threshold: float | None = None,
241
- min_contact_frames: int | None = None,
242
- visibility_threshold: float | None = None,
250
+ overrides: AnalysisOverrides | None = None,
243
251
  detection_confidence: float | None = None,
244
252
  tracking_confidence: float | None = None,
245
253
  verbose: bool = False,
@@ -258,10 +266,7 @@ def process_cmj_video(
258
266
  quality: Analysis quality preset ("fast", "balanced", or "accurate")
259
267
  output_video: Optional path for debug video output
260
268
  json_output: Optional path for JSON metrics output
261
- smoothing_window: Optional override for smoothing window
262
- velocity_threshold: Optional override for velocity threshold
263
- min_contact_frames: Optional override for minimum contact frames
264
- visibility_threshold: Optional override for visibility threshold
269
+ overrides: Optional AnalysisOverrides with parameter fine-tuning
265
270
  detection_confidence: Optional override for pose detection confidence
266
271
  tracking_confidence: Optional override for pose tracking confidence
267
272
  verbose: Print processing details
@@ -315,10 +320,10 @@ def process_cmj_video(
315
320
  params = auto_tune_parameters(characteristics, quality_preset)
316
321
  params = apply_expert_overrides(
317
322
  params,
318
- smoothing_window,
319
- velocity_threshold,
320
- min_contact_frames,
321
- visibility_threshold,
323
+ overrides.smoothing_window if overrides else None,
324
+ overrides.velocity_threshold if overrides else None,
325
+ overrides.min_contact_frames if overrides else None,
326
+ overrides.visibility_threshold if overrides else None,
322
327
  )
323
328
 
324
329
  if verbose:
@@ -463,10 +468,7 @@ def _process_cmj_video_wrapper(config: CMJVideoConfig) -> CMJVideoResult:
463
468
  quality=config.quality,
464
469
  output_video=config.output_video,
465
470
  json_output=config.json_output,
466
- smoothing_window=config.smoothing_window,
467
- velocity_threshold=config.velocity_threshold,
468
- min_contact_frames=config.min_contact_frames,
469
- visibility_threshold=config.visibility_threshold,
471
+ overrides=config.overrides,
470
472
  detection_confidence=config.detection_confidence,
471
473
  tracking_confidence=config.tracking_confidence,
472
474
  verbose=False,
kinemotion/cmj/cli.py CHANGED
@@ -12,7 +12,7 @@ from ..core.cli_utils import (
12
12
  common_output_options,
13
13
  generate_batch_output_paths,
14
14
  )
15
- from .api import process_cmj_video
15
+ from .api import AnalysisOverrides, process_cmj_video
16
16
  from .kinematics import CMJMetrics
17
17
 
18
18
 
@@ -260,16 +260,21 @@ def _process_single(
260
260
  ) -> None:
261
261
  """Process a single CMJ video by calling the API."""
262
262
  try:
263
+ # Create overrides from expert parameters
264
+ overrides = AnalysisOverrides(
265
+ smoothing_window=expert_params.smoothing_window,
266
+ velocity_threshold=expert_params.velocity_threshold,
267
+ min_contact_frames=expert_params.min_contact_frames,
268
+ visibility_threshold=expert_params.visibility_threshold,
269
+ )
270
+
263
271
  # Call the API function (handles all processing logic)
264
272
  metrics = process_cmj_video(
265
273
  video_path=video_path,
266
274
  quality=quality_preset.value,
267
275
  output_video=output,
268
276
  json_output=json_output,
269
- smoothing_window=expert_params.smoothing_window,
270
- velocity_threshold=expert_params.velocity_threshold,
271
- min_contact_frames=expert_params.min_contact_frames,
272
- visibility_threshold=expert_params.visibility_threshold,
277
+ overrides=overrides,
273
278
  detection_confidence=expert_params.detection_confidence,
274
279
  tracking_confidence=expert_params.tracking_confidence,
275
280
  verbose=verbose,
@@ -13,6 +13,7 @@ if TYPE_CHECKING:
13
13
  from ..core.auto_tuning import (
14
14
  AnalysisParameters,
15
15
  QualityPreset,
16
+ VideoCharacteristics,
16
17
  analyze_video_sample,
17
18
  auto_tune_parameters,
18
19
  )
@@ -52,6 +53,20 @@ from .kinematics import DropJumpMetrics, calculate_drop_jump_metrics
52
53
  from .metrics_validator import DropJumpMetricsValidator
53
54
 
54
55
 
56
+ @dataclass
57
+ class AnalysisOverrides:
58
+ """Optional overrides for analysis parameters.
59
+
60
+ Allows fine-tuning of specific analysis parameters beyond quality presets.
61
+ If None, values will be determined by the quality preset.
62
+ """
63
+
64
+ smoothing_window: int | None = None
65
+ velocity_threshold: float | None = None
66
+ min_contact_frames: int | None = None
67
+ visibility_threshold: float | None = None
68
+
69
+
55
70
  @dataclass
56
71
  class DropJumpVideoResult:
57
72
  """Result of processing a single drop jump video."""
@@ -72,10 +87,7 @@ class DropJumpVideoConfig:
72
87
  output_video: str | None = None
73
88
  json_output: str | None = None
74
89
  drop_start_frame: int | None = None
75
- smoothing_window: int | None = None
76
- velocity_threshold: float | None = None
77
- min_contact_frames: int | None = None
78
- visibility_threshold: float | None = None
90
+ overrides: AnalysisOverrides | None = None
79
91
  detection_confidence: float | None = None
80
92
  tracking_confidence: float | None = None
81
93
 
@@ -219,6 +231,195 @@ def _print_dropjump_summary(
219
231
  print("Analysis complete!")
220
232
 
221
233
 
234
+ def _setup_pose_tracker(
235
+ quality_preset: QualityPreset,
236
+ detection_confidence: float | None,
237
+ tracking_confidence: float | None,
238
+ pose_tracker: "PoseTracker | None",
239
+ timer: Timer,
240
+ ) -> tuple["PoseTracker", bool]:
241
+ """Set up pose tracker and determine if it should be closed."""
242
+ detection_conf, tracking_conf = determine_confidence_levels(
243
+ quality_preset, detection_confidence, tracking_confidence
244
+ )
245
+
246
+ tracker = pose_tracker
247
+ should_close_tracker = False
248
+
249
+ if tracker is None:
250
+ tracker = PoseTracker(
251
+ min_detection_confidence=detection_conf,
252
+ min_tracking_confidence=tracking_conf,
253
+ timer=timer,
254
+ )
255
+ should_close_tracker = True
256
+
257
+ return tracker, should_close_tracker
258
+
259
+
260
+ def _process_frames_and_landmarks(
261
+ video: "VideoProcessor",
262
+ tracker: "PoseTracker",
263
+ should_close_tracker: bool,
264
+ verbose: bool,
265
+ timer: Timer,
266
+ ) -> tuple[list, list, list[int]]:
267
+ """Process all video frames and extract landmarks."""
268
+ if verbose:
269
+ print("Processing all frames with MediaPipe pose tracking...")
270
+
271
+ frames, landmarks_sequence, frame_indices = process_all_frames(
272
+ video, tracker, verbose, timer, close_tracker=should_close_tracker
273
+ )
274
+
275
+ return frames, landmarks_sequence, frame_indices
276
+
277
+
278
+ def _tune_and_smooth(
279
+ landmarks_sequence: list,
280
+ video_fps: float,
281
+ frame_count: int,
282
+ quality_preset: QualityPreset,
283
+ overrides: AnalysisOverrides | None,
284
+ timer: Timer,
285
+ verbose: bool,
286
+ ) -> tuple[list, AnalysisParameters, VideoCharacteristics]:
287
+ """Tune parameters and apply smoothing to landmarks.
288
+
289
+ Args:
290
+ landmarks_sequence: Sequence of pose landmarks
291
+ video_fps: Video frame rate
292
+ frame_count: Total number of frames
293
+ quality_preset: Quality preset for analysis
294
+ overrides: Optional parameter overrides
295
+ timer: Performance timer
296
+ verbose: Verbose output flag
297
+
298
+ Returns:
299
+ Tuple of (smoothed_landmarks, params, characteristics)
300
+ """
301
+ with timer.measure("parameter_auto_tuning"):
302
+ characteristics = analyze_video_sample(
303
+ landmarks_sequence, video_fps, frame_count
304
+ )
305
+ params = auto_tune_parameters(characteristics, quality_preset)
306
+
307
+ # Apply overrides if provided
308
+ if overrides:
309
+ params = apply_expert_overrides(
310
+ params,
311
+ overrides.smoothing_window,
312
+ overrides.velocity_threshold,
313
+ overrides.min_contact_frames,
314
+ overrides.visibility_threshold,
315
+ )
316
+ else:
317
+ params = apply_expert_overrides(
318
+ params,
319
+ None,
320
+ None,
321
+ None,
322
+ None,
323
+ )
324
+
325
+ smoothed_landmarks = apply_smoothing(landmarks_sequence, params, verbose, timer)
326
+
327
+ return smoothed_landmarks, params, characteristics
328
+
329
+
330
+ def _extract_positions_and_detect_contact(
331
+ smoothed_landmarks: list,
332
+ params: AnalysisParameters,
333
+ timer: Timer,
334
+ verbose: bool,
335
+ ) -> tuple["NDArray", "NDArray", list]:
336
+ """Extract vertical positions and detect ground contact."""
337
+ if verbose:
338
+ print("Extracting foot positions...")
339
+ with timer.measure("vertical_position_extraction"):
340
+ vertical_positions, visibilities = extract_vertical_positions(
341
+ smoothed_landmarks
342
+ )
343
+
344
+ if verbose:
345
+ print("Detecting ground contact...")
346
+ with timer.measure("ground_contact_detection"):
347
+ contact_states = detect_ground_contact(
348
+ vertical_positions,
349
+ velocity_threshold=params.velocity_threshold,
350
+ min_contact_frames=params.min_contact_frames,
351
+ visibility_threshold=params.visibility_threshold,
352
+ visibilities=visibilities,
353
+ window_length=params.smoothing_window,
354
+ polyorder=params.polyorder,
355
+ timer=timer,
356
+ )
357
+
358
+ return vertical_positions, visibilities, contact_states
359
+
360
+
361
+ def _calculate_metrics_and_assess_quality(
362
+ contact_states: list,
363
+ vertical_positions: "NDArray",
364
+ visibilities: "NDArray",
365
+ video_fps: float,
366
+ drop_start_frame: int | None,
367
+ params: AnalysisParameters,
368
+ timer: Timer,
369
+ verbose: bool,
370
+ ) -> tuple[DropJumpMetrics, QualityAssessment]:
371
+ """Calculate metrics and assess quality."""
372
+ if verbose:
373
+ print("Calculating metrics...")
374
+ with timer.measure("metrics_calculation"):
375
+ metrics = calculate_drop_jump_metrics(
376
+ contact_states,
377
+ vertical_positions,
378
+ video_fps,
379
+ drop_start_frame=drop_start_frame,
380
+ velocity_threshold=params.velocity_threshold,
381
+ smoothing_window=params.smoothing_window,
382
+ polyorder=params.polyorder,
383
+ use_curvature=params.use_curvature,
384
+ timer=timer,
385
+ )
386
+
387
+ if verbose:
388
+ print("Assessing tracking quality...")
389
+ with timer.measure("quality_assessment"):
390
+ quality_result, _, _, _ = _assess_dropjump_quality(
391
+ vertical_positions, visibilities, contact_states, video_fps
392
+ )
393
+
394
+ return metrics, quality_result
395
+
396
+
397
+ def _print_quality_warnings(quality_result: QualityAssessment, verbose: bool) -> None:
398
+ """Print quality warnings if present."""
399
+ if verbose and quality_result.warnings:
400
+ print("\n⚠️ Quality Warnings:")
401
+ for warning in quality_result.warnings:
402
+ print(f" - {warning}")
403
+ print()
404
+
405
+
406
+ def _validate_metrics_and_print_results(
407
+ metrics: DropJumpMetrics,
408
+ timer: Timer,
409
+ verbose: bool,
410
+ ) -> None:
411
+ """Validate metrics and print validation results if verbose."""
412
+ with timer.measure("metrics_validation"):
413
+ validator = DropJumpMetricsValidator()
414
+ validation_result = validator.validate(metrics.to_dict()) # type: ignore[arg-type]
415
+ metrics.validation_result = validation_result
416
+
417
+ if verbose and validation_result.issues:
418
+ print("\n⚠️ Validation Results:")
419
+ for issue in validation_result.issues:
420
+ print(f" [{issue.severity.value}] {issue.metric}: {issue.message}")
421
+
422
+
222
423
  def _generate_debug_video(
223
424
  output_video: str,
224
425
  frames: list,
@@ -285,10 +486,7 @@ def process_dropjump_video(
285
486
  output_video: str | None = None,
286
487
  json_output: str | None = None,
287
488
  drop_start_frame: int | None = None,
288
- smoothing_window: int | None = None,
289
- velocity_threshold: float | None = None,
290
- min_contact_frames: int | None = None,
291
- visibility_threshold: float | None = None,
489
+ overrides: AnalysisOverrides | None = None,
292
490
  detection_confidence: float | None = None,
293
491
  tracking_confidence: float | None = None,
294
492
  verbose: bool = False,
@@ -306,10 +504,7 @@ def process_dropjump_video(
306
504
  output_video: Optional path for debug video output
307
505
  json_output: Optional path for JSON metrics output
308
506
  drop_start_frame: Optional manual drop start frame
309
- smoothing_window: Optional override for smoothing window
310
- velocity_threshold: Optional override for velocity threshold
311
- min_contact_frames: Optional override for minimum contact frames
312
- visibility_threshold: Optional override for visibility threshold
507
+ overrides: Optional AnalysisOverrides for fine-tuning parameters
313
508
  detection_confidence: Optional override for pose detection confidence
314
509
  tracking_confidence: Optional override for pose tracking confidence
315
510
  verbose: Print processing details
@@ -331,106 +526,54 @@ def process_dropjump_video(
331
526
  set_deterministic_mode(seed=42)
332
527
 
333
528
  start_time = time.time()
334
- if timer is None:
335
- timer = PerformanceTimer()
336
-
529
+ timer = timer or PerformanceTimer()
337
530
  quality_preset = parse_quality_preset(quality)
338
531
 
339
532
  with timer.measure("video_initialization"):
340
533
  with VideoProcessor(video_path, timer=timer) as video:
341
- detection_conf, tracking_conf = determine_confidence_levels(
342
- quality_preset, detection_confidence, tracking_confidence
534
+ tracker, should_close_tracker = _setup_pose_tracker(
535
+ quality_preset,
536
+ detection_confidence,
537
+ tracking_confidence,
538
+ pose_tracker,
539
+ timer,
343
540
  )
344
541
 
345
- if verbose:
346
- print("Processing all frames with MediaPipe pose tracking...")
347
-
348
- tracker = pose_tracker
349
- should_close_tracker = False
350
-
351
- if tracker is None:
352
- tracker = PoseTracker(
353
- min_detection_confidence=detection_conf,
354
- min_tracking_confidence=tracking_conf,
355
- timer=timer,
356
- )
357
- should_close_tracker = True
358
-
359
- frames, landmarks_sequence, frame_indices = process_all_frames(
360
- video, tracker, verbose, timer, close_tracker=should_close_tracker
542
+ frames, landmarks_sequence, frame_indices = _process_frames_and_landmarks(
543
+ video, tracker, should_close_tracker, verbose, timer
361
544
  )
362
545
 
363
- with timer.measure("parameter_auto_tuning"):
364
- characteristics = analyze_video_sample(
365
- landmarks_sequence, video.fps, video.frame_count
366
- )
367
- params = auto_tune_parameters(characteristics, quality_preset)
368
-
369
- params = apply_expert_overrides(
370
- params,
371
- smoothing_window,
372
- velocity_threshold,
373
- min_contact_frames,
374
- visibility_threshold,
375
- )
376
-
377
- if verbose:
378
- print_verbose_parameters(
379
- video, characteristics, quality_preset, params
380
- )
381
-
382
- smoothed_landmarks = apply_smoothing(
383
- landmarks_sequence, params, verbose, timer
546
+ smoothed_landmarks, params, characteristics = _tune_and_smooth(
547
+ landmarks_sequence,
548
+ video.fps,
549
+ video.frame_count,
550
+ quality_preset,
551
+ overrides,
552
+ timer,
553
+ verbose,
384
554
  )
385
555
 
386
556
  if verbose:
387
- print("Extracting foot positions...")
388
- with timer.measure("vertical_position_extraction"):
389
- vertical_positions, visibilities = extract_vertical_positions(
390
- smoothed_landmarks
391
- )
557
+ print_verbose_parameters(video, characteristics, quality_preset, params)
392
558
 
393
- if verbose:
394
- print("Detecting ground contact...")
395
- with timer.measure("ground_contact_detection"):
396
- contact_states = detect_ground_contact(
397
- vertical_positions,
398
- velocity_threshold=params.velocity_threshold,
399
- min_contact_frames=params.min_contact_frames,
400
- visibility_threshold=params.visibility_threshold,
401
- visibilities=visibilities,
402
- window_length=params.smoothing_window,
403
- polyorder=params.polyorder,
404
- timer=timer,
405
- )
406
-
407
- if verbose:
408
- print("Calculating metrics...")
409
- with timer.measure("metrics_calculation"):
410
- metrics = calculate_drop_jump_metrics(
411
- contact_states,
412
- vertical_positions,
413
- video.fps,
414
- drop_start_frame=drop_start_frame,
415
- velocity_threshold=params.velocity_threshold,
416
- smoothing_window=params.smoothing_window,
417
- polyorder=params.polyorder,
418
- use_curvature=params.use_curvature,
419
- timer=timer,
559
+ vertical_positions, visibilities, contact_states = (
560
+ _extract_positions_and_detect_contact(
561
+ smoothed_landmarks, params, timer, verbose
420
562
  )
563
+ )
421
564
 
422
- if verbose:
423
- print("Assessing tracking quality...")
424
- with timer.measure("quality_assessment"):
425
- quality_result, _, _, _ = _assess_dropjump_quality(
426
- vertical_positions, visibilities, contact_states, video.fps
427
- )
565
+ metrics, quality_result = _calculate_metrics_and_assess_quality(
566
+ contact_states,
567
+ vertical_positions,
568
+ visibilities,
569
+ video.fps,
570
+ drop_start_frame,
571
+ params,
572
+ timer,
573
+ verbose,
574
+ )
428
575
 
429
- if verbose and quality_result.warnings:
430
- print("\n⚠️ Quality Warnings:")
431
- for warning in quality_result.warnings:
432
- print(f" - {warning}")
433
- print()
576
+ _print_quality_warnings(quality_result, verbose)
434
577
 
435
578
  if output_video:
436
579
  _generate_debug_video(
@@ -445,15 +588,7 @@ def process_dropjump_video(
445
588
  verbose,
446
589
  )
447
590
 
448
- with timer.measure("metrics_validation"):
449
- validator = DropJumpMetricsValidator()
450
- validation_result = validator.validate(metrics.to_dict()) # type: ignore[arg-type]
451
- metrics.validation_result = validation_result
452
-
453
- if verbose and validation_result.issues:
454
- print("\n⚠️ Validation Results:")
455
- for issue in validation_result.issues:
456
- print(f" [{issue.severity.value}] {issue.metric}: {issue.message}")
591
+ _validate_metrics_and_print_results(metrics, timer, verbose)
457
592
 
458
593
  processing_time = time.time() - start_time
459
594
  result_metadata = _build_dropjump_metadata(
@@ -512,10 +647,7 @@ def _process_dropjump_video_wrapper(config: DropJumpVideoConfig) -> DropJumpVide
512
647
  output_video=config.output_video,
513
648
  json_output=config.json_output,
514
649
  drop_start_frame=config.drop_start_frame,
515
- smoothing_window=config.smoothing_window,
516
- velocity_threshold=config.velocity_threshold,
517
- min_contact_frames=config.min_contact_frames,
518
- visibility_threshold=config.visibility_threshold,
650
+ overrides=config.overrides,
519
651
  detection_confidence=config.detection_confidence,
520
652
  tracking_confidence=config.tracking_confidence,
521
653
  verbose=False,
@@ -237,6 +237,25 @@ def _process_single(
237
237
  click.echo(f"Analyzing video: {video_path}", err=True)
238
238
 
239
239
  try:
240
+ # Create AnalysisOverrides if any expert parameters are set
241
+ from .api import AnalysisOverrides
242
+
243
+ overrides = None
244
+ if any(
245
+ [
246
+ expert_params.smoothing_window is not None,
247
+ expert_params.velocity_threshold is not None,
248
+ expert_params.min_contact_frames is not None,
249
+ expert_params.visibility_threshold is not None,
250
+ ]
251
+ ):
252
+ overrides = AnalysisOverrides(
253
+ smoothing_window=expert_params.smoothing_window,
254
+ velocity_threshold=expert_params.velocity_threshold,
255
+ min_contact_frames=expert_params.min_contact_frames,
256
+ visibility_threshold=expert_params.visibility_threshold,
257
+ )
258
+
240
259
  # Call the API function (handles all processing logic)
241
260
  metrics = process_dropjump_video(
242
261
  video_path=video_path,
@@ -244,10 +263,7 @@ def _process_single(
244
263
  output_video=output,
245
264
  json_output=json_output,
246
265
  drop_start_frame=expert_params.drop_start_frame,
247
- smoothing_window=expert_params.smoothing_window,
248
- velocity_threshold=expert_params.velocity_threshold,
249
- min_contact_frames=expert_params.min_contact_frames,
250
- visibility_threshold=expert_params.visibility_threshold,
266
+ overrides=overrides,
251
267
  detection_confidence=expert_params.detection_confidence,
252
268
  tracking_confidence=expert_params.tracking_confidence,
253
269
  verbose=verbose,
@@ -312,16 +328,32 @@ def _create_video_configs(
312
328
  video_file, output_dir, json_output_dir
313
329
  )
314
330
 
331
+ # Create AnalysisOverrides if any expert parameters are set
332
+ from .api import AnalysisOverrides
333
+
334
+ overrides = None
335
+ if any(
336
+ [
337
+ expert_params.smoothing_window is not None,
338
+ expert_params.velocity_threshold is not None,
339
+ expert_params.min_contact_frames is not None,
340
+ expert_params.visibility_threshold is not None,
341
+ ]
342
+ ):
343
+ overrides = AnalysisOverrides(
344
+ smoothing_window=expert_params.smoothing_window,
345
+ velocity_threshold=expert_params.velocity_threshold,
346
+ min_contact_frames=expert_params.min_contact_frames,
347
+ visibility_threshold=expert_params.visibility_threshold,
348
+ )
349
+
315
350
  config = DropJumpVideoConfig(
316
351
  video_path=video_file,
317
352
  quality=quality,
318
353
  output_video=debug_video,
319
354
  json_output=json_file,
320
355
  drop_start_frame=expert_params.drop_start_frame,
321
- smoothing_window=expert_params.smoothing_window,
322
- velocity_threshold=expert_params.velocity_threshold,
323
- min_contact_frames=expert_params.min_contact_frames,
324
- visibility_threshold=expert_params.visibility_threshold,
356
+ overrides=overrides,
325
357
  detection_confidence=expert_params.detection_confidence,
326
358
  tracking_confidence=expert_params.tracking_confidence,
327
359
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kinemotion
3
- Version: 0.47.4
3
+ Version: 0.49.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,10 +1,10 @@
1
1
  kinemotion/__init__.py,sha256=wPItmyGJUOFM6GPRVhAEvRz0-ErI7e2qiUREYJ9EfPQ,943
2
- kinemotion/api.py,sha256=uj3py8jXuG3mYnmsZQnzuCQtWrO4O6gvZzGAMfZne4o,891
2
+ kinemotion/api.py,sha256=q33-C6xlZKzXthjii1FKnschFU_WT60EHabRgczy3ic,1039
3
3
  kinemotion/cli.py,sha256=cqYV_7URH0JUDy1VQ_EDLv63FmNO4Ns20m6s1XAjiP4,464
4
4
  kinemotion/cmj/__init__.py,sha256=SkAw9ka8Yd1Qfv9hcvk22m3EfucROzYrSNGNF5kDzho,113
5
5
  kinemotion/cmj/analysis.py,sha256=3l0vYQB9tN4HtEO2MPFHVtrdzSmXgwpCm03qzYLCF0c,22196
6
- kinemotion/cmj/api.py,sha256=MLyc4pkzK8wdSI5-pZ5ekfcI9MDwySknAqOUWxWCT9s,16895
7
- kinemotion/cmj/cli.py,sha256=S4-3YmaCjtGutDwjG475h8nIiw5utiLg5L6hCGfLOHY,9926
6
+ kinemotion/cmj/api.py,sha256=TYWja-Ellfyq_R2ixfvQyCWnPON7CG7IZk8odlLVM8E,16784
7
+ kinemotion/cmj/cli.py,sha256=r3k5LDRXob12PV_6f6XnXOzKXoGn5WfeCMXkxiJ_CYE,10078
8
8
  kinemotion/cmj/debug_overlay.py,sha256=fXmWoHhqMLGo4vTtB6Ezs3yLUDOLw63zLIgU2gFlJQU,15892
9
9
  kinemotion/cmj/joint_angles.py,sha256=HmheIEiKcQz39cRezk4h-htorOhGNPsqKIR9RsAEKts,9960
10
10
  kinemotion/cmj/kinematics.py,sha256=Q-L8M7wG-MJ6EJTq6GO17c8sD5cb0Jg6Hc5vUZr14bA,13673
@@ -28,15 +28,15 @@ kinemotion/core/validation.py,sha256=LmKfSl4Ayw3DgwKD9IrhsPdzp5ia4drLsHA2UuU1SCM
28
28
  kinemotion/core/video_io.py,sha256=vCwpWnlW2y29l48dFXokdehQn42w_IQvayxbVTjpXqQ,7863
29
29
  kinemotion/dropjump/__init__.py,sha256=tC3H3BrCg8Oj-db-Vrtx4PH_llR1Ppkd5jwaOjhQcLg,862
30
30
  kinemotion/dropjump/analysis.py,sha256=p7nnCe7V6vnhQKZVYk--_nhsTvVa_WY-A3zXmyplsew,28211
31
- kinemotion/dropjump/api.py,sha256=9tetwjoFdY7Z8PqXpNfaS96L9YVqEkJl7jejGnewhbE,17517
32
- kinemotion/dropjump/cli.py,sha256=pPQkjpuPUUefGcsRuMvRTtjsxpPSqSgQ9K49rsN_X_o,15823
31
+ kinemotion/dropjump/api.py,sha256=O8DSTLankRibFH8pf1A9idK0x9-khKpG1h2X5nlg5Ms,20688
32
+ kinemotion/dropjump/cli.py,sha256=Ho80fSOgH8zo2e8dGQA90VXL-mZPVvnpc1ZKtl51vB0,16917
33
33
  kinemotion/dropjump/debug_overlay.py,sha256=8XVuDyZ3nuNoCYkxcUWC7wyEoHyBxx77Sb--B1KiYWw,5974
34
34
  kinemotion/dropjump/kinematics.py,sha256=PATlGaClutGKJslL-LRIXHmTsvb-xEB8PUIMScU_K4c,19849
35
35
  kinemotion/dropjump/metrics_validator.py,sha256=CrTlGup8q2kyPXtA6HNwm7_yq0AsBaDllG7RVZdXmYA,9342
36
36
  kinemotion/dropjump/validation_bounds.py,sha256=fyl04ZV7nfvHkL5eob6oEpV9Hxce6aiOWQ9pclLp7AQ,5077
37
37
  kinemotion/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
- kinemotion-0.47.4.dist-info/METADATA,sha256=-N2sOXvyGeykCej_Uq07hmu51025vrRF8EUFEiO5kVI,26020
39
- kinemotion-0.47.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
40
- kinemotion-0.47.4.dist-info/entry_points.txt,sha256=zaqnAnjLvcdrk1Qvj5nvXZCZ2gp0prS7it1zTJygcIY,50
41
- kinemotion-0.47.4.dist-info/licenses/LICENSE,sha256=KZajvqsHw0NoOHOi2q0FZ4NBe9HdV6oey-IPYAtHXfg,1088
42
- kinemotion-0.47.4.dist-info/RECORD,,
38
+ kinemotion-0.49.0.dist-info/METADATA,sha256=5lngG5Rjd7iFSYSnSdUDHlafk41F4XFScXq_QgWOS60,26020
39
+ kinemotion-0.49.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
40
+ kinemotion-0.49.0.dist-info/entry_points.txt,sha256=zaqnAnjLvcdrk1Qvj5nvXZCZ2gp0prS7it1zTJygcIY,50
41
+ kinemotion-0.49.0.dist-info/licenses/LICENSE,sha256=KZajvqsHw0NoOHOi2q0FZ4NBe9HdV6oey-IPYAtHXfg,1088
42
+ kinemotion-0.49.0.dist-info/RECORD,,