kinemotion 0.47.4__py3-none-any.whl → 0.48.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 +6 -0
- kinemotion/cmj/api.py +24 -22
- kinemotion/cmj/cli.py +10 -5
- kinemotion/dropjump/api.py +242 -110
- kinemotion/dropjump/cli.py +40 -8
- {kinemotion-0.47.4.dist-info → kinemotion-0.48.0.dist-info}/METADATA +1 -1
- {kinemotion-0.47.4.dist-info → kinemotion-0.48.0.dist-info}/RECORD +10 -10
- {kinemotion-0.47.4.dist-info → kinemotion-0.48.0.dist-info}/WHEEL +0 -0
- {kinemotion-0.47.4.dist-info → kinemotion-0.48.0.dist-info}/entry_points.txt +0 -0
- {kinemotion-0.47.4.dist-info → kinemotion-0.48.0.dist-info}/licenses/LICENSE +0 -0
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
|
|
126
|
+
print(f"{stage:.<40} {dur_ms:>6.0f}ms ({percentage:>5.1f}%)")
|
|
113
127
|
total_ms = total_time * 1000
|
|
114
|
-
print(f"{
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
kinemotion/dropjump/api.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
342
|
-
quality_preset,
|
|
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
|
-
|
|
346
|
-
|
|
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
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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
|
-
|
|
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
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
kinemotion/dropjump/cli.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
3
|
+
Version: 0.48.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=
|
|
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=
|
|
7
|
-
kinemotion/cmj/cli.py,sha256=
|
|
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=
|
|
32
|
-
kinemotion/dropjump/cli.py,sha256=
|
|
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.
|
|
39
|
-
kinemotion-0.
|
|
40
|
-
kinemotion-0.
|
|
41
|
-
kinemotion-0.
|
|
42
|
-
kinemotion-0.
|
|
38
|
+
kinemotion-0.48.0.dist-info/METADATA,sha256=xLTdYPgI6XtwGbaxYOnwpA0IZKfZkMF0ld48pP-rp7c,26020
|
|
39
|
+
kinemotion-0.48.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
40
|
+
kinemotion-0.48.0.dist-info/entry_points.txt,sha256=zaqnAnjLvcdrk1Qvj5nvXZCZ2gp0prS7it1zTJygcIY,50
|
|
41
|
+
kinemotion-0.48.0.dist-info/licenses/LICENSE,sha256=KZajvqsHw0NoOHOi2q0FZ4NBe9HdV6oey-IPYAtHXfg,1088
|
|
42
|
+
kinemotion-0.48.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|