kinemotion 0.26.1__tar.gz → 0.28.0__tar.gz
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-0.26.1 → kinemotion-0.28.0}/CHANGELOG.md +16 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/PKG-INFO +1 -1
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/reference/json-output-format.md +11 -11
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/reference/json-structure-comparison.md +3 -3
- {kinemotion-0.26.1 → kinemotion-0.28.0}/pyproject.toml +1 -1
- {kinemotion-0.26.1 → kinemotion-0.28.0}/src/kinemotion/api.py +2 -2
- {kinemotion-0.26.1 → kinemotion-0.28.0}/src/kinemotion/cmj/kinematics.py +33 -23
- kinemotion-0.28.0/src/kinemotion/core/formatting.py +75 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/src/kinemotion/core/video_io.py +6 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/src/kinemotion/dropjump/kinematics.py +15 -46
- {kinemotion-0.26.1 → kinemotion-0.28.0}/tests/test_cli_cmj.py +1 -1
- {kinemotion-0.26.1 → kinemotion-0.28.0}/tests/test_cmj_kinematics.py +6 -6
- kinemotion-0.28.0/tests/test_formatting.py +179 -0
- kinemotion-0.28.0/tests/test_video_io.py +114 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/uv.lock +1 -1
- {kinemotion-0.26.1 → kinemotion-0.28.0}/.dockerignore +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/.gitattributes +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/.github/pull_request_template.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/.github/workflows/docs.yml +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/.github/workflows/release.yml +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/.github/workflows/test.yml +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/.gitignore +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/.pre-commit-config.yaml +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/.readthedocs.yml +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/.tool-versions +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/CLAUDE.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/CODE_OF_CONDUCT.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/CONTRIBUTING.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/Dockerfile +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/GEMINI.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/LICENSE +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/README.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/SECURITY.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/README.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/api/cmj.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/api/core.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/api/dropjump.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/api/overview.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/development/errors-findings.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/development/testing.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/development/type-hints.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/development/validation-plan.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/development/validation-roadmap.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/development/wallball-norep-detection.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/guides/bulk-processing.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/guides/camera-setup.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/guides/cmj-guide.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/index.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/reference/parameters.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/reference/pose-systems.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/paper-downloader/HOW-TO-FIND-DOIS.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/paper-downloader/README.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/paper-downloader/dois.txt +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/paper-downloader/download.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/paper-downloader/pyproject.toml +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/paper-downloader/uv.lock +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/sports-biomechanics-pose-estimation.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/MANUAL-DOWNLOAD-GUIDE.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/README.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/convert_pdfs.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/athlete-monitoring/2001_Foster_Session-RPE-Training-Monitoring.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/athlete-monitoring/2015_Buchheit_GPS-Accelerometers-Stride-Stiffness.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/athlete-monitoring/2018_Flatt_HRV-Recovery-Swimmers.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/athlete-monitoring/2018_Saw_Training-Camps-Monitoring.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/athlete-monitoring/2018_Ward_Putting-i-in-Team.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/injury-prevention/2011_Petersen_Nordic-Hamstring-Prevention.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/injury-prevention/2011_Wilk_Shoulder-GIRD-Baseball-Pitchers.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/injury-prevention/2012_Hewit_Multidirectional-Leg-Asymmetry.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/injury-prevention/2015_Ford_Hip-Neuromuscular-Exercise-Valgus.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/injury-prevention/2015_Mosler_Hip-Groin-Pain-Factors.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/injury-prevention/2015_Sconce_Nordic-Hamstring-Validity.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/injury-prevention/2016_Mendez-Villanueva_Hamstring-MRI-Regional.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/injury-prevention/2016_Read_Youth-Soccer-Injury-Risk.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/injury-prevention/2017_Mason-Mackay_Ankle-Dorsiflexion-Landing.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/injury-prevention/2017_Mendiguchia_Hamstring-Treatment-Algorithm.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/injury-prevention/2018_Balsalobre-Fernandez_Ankle-Dorsiflexion-App.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/injury-prevention/2018_Bramah_Pathological-Gait-Running-Injuries.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/injury-prevention/2020_Fidai_Fatigue-Knee-Valgus-Youth.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/jump-performance/2011_Harper_10-to-5-Jump-Test.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/jump-performance/2012_Samozino_Optimal-Force-Velocity-Profile.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/jump-performance/2014_Samozino_FV-Imbalance.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/jump-performance/2016_Jimenez-Reyes_Force-Velocity-Training.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/jump-performance/2016_Morin-Samozino_Power-Force-Velocity-Profiles.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/jump-performance/2018_Garcia-Ramos_Two-Point-Method-Optimization.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/jump-performance/2022_Wells_Golf-Clubhead-CMJ.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/running-biomechanics/2005_Morin_Running-Stiffness-Measurement.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/running-biomechanics/2015_Balsalobre-Fernandez_Strength-Training-Running-Economy.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/running-biomechanics/2016_Balsalobre-Fernandez_iPhone-Running-Mechanics.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/running-biomechanics/2016_Moore_Economical-Running-Technique.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/running-biomechanics/2020_Filter_Curve-Sprint-Test-Soccer.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/running-biomechanics/2020_Harper_Horizontal-Deceleration-Radar.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/running-biomechanics/2021_vanOeveren_Running-Biomechanics-Synthesis.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/running-biomechanics/2024_Bramah_Sprint-Mechanics-Assessment-Score.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/smartphone-technology/2015_Balsalobre-Fernandez_iPhone-Vertical-Jump.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/smartphone-technology/2022_Bishop_MyJumpLab-Validation.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/velocity-based-training/2011_Jidovtseff_Load-Velocity-1RM-Prediction.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/velocity-based-training/2016_Conceicao_Movement-Velocity-Lower-Limb.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/velocity-based-training/2016_Pareja-Blanco_Velocity-Loss-Training.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/velocity-based-training/2017_Balsalobre-Fernandez_Barbell-Velocity-1RM.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/velocity-based-training/2020_Balsalobre-Fernandez_Barbell-Trajectory-Snatch.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/markdown/velocity-based-training/2023_Balsalobre-Fernandez_AI-Barbell-Velocity.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/online-references-for-papers.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/athlete-monitoring/2001_Foster_Session-RPE-Training-Monitoring.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/athlete-monitoring/2015_Buchheit_GPS-Accelerometers-Stride-Stiffness.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/athlete-monitoring/2018_Flatt_HRV-Recovery-Swimmers.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/athlete-monitoring/2018_Saw_Training-Camps-Monitoring.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/athlete-monitoring/2018_Ward_Putting-i-in-Team.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/injury-prevention/2011_Petersen_Nordic-Hamstring-Prevention.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/injury-prevention/2011_Wilk_Shoulder-GIRD-Baseball-Pitchers.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/injury-prevention/2012_Hewit_Multidirectional-Leg-Asymmetry.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/injury-prevention/2015_Ford_Hip-Neuromuscular-Exercise-Valgus.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/injury-prevention/2015_Mosler_Hip-Groin-Pain-Factors.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/injury-prevention/2015_Sconce_Nordic-Hamstring-Validity.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/injury-prevention/2016_Mendez-Villanueva_Hamstring-MRI-Regional.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/injury-prevention/2016_Read_Youth-Soccer-Injury-Risk.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/injury-prevention/2017_Mason-Mackay_Ankle-Dorsiflexion-Landing.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/injury-prevention/2017_Mendiguchia_Hamstring-Treatment-Algorithm.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/injury-prevention/2018_Balsalobre-Fernandez_Ankle-Dorsiflexion-App.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/injury-prevention/2018_Bramah_Pathological-Gait-Running-Injuries.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/injury-prevention/2020_Fidai_Fatigue-Knee-Valgus-Youth.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/jump-performance/2011_Harper_10-to-5-Jump-Test.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/jump-performance/2012_Samozino_Optimal-Force-Velocity-Profile.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/jump-performance/2014_Samozino_FV-Imbalance.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/jump-performance/2016_Jimenez-Reyes_Force-Velocity-Training.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/jump-performance/2016_Morin-Samozino_Power-Force-Velocity-Profiles.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/jump-performance/2018_Garcia-Ramos_Two-Point-Method-Optimization.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/jump-performance/2022_Wells_Golf-Clubhead-CMJ.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/running-biomechanics/2005_Morin_Running-Stiffness-Measurement.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/running-biomechanics/2015_Balsalobre-Fernandez_Strength-Training-Running-Economy.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/running-biomechanics/2016_Balsalobre-Fernandez_iPhone-Running-Mechanics.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/running-biomechanics/2016_Moore_Economical-Running-Technique.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/running-biomechanics/2020_Filter_Curve-Sprint-Test-Soccer.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/running-biomechanics/2020_Harper_Horizontal-Deceleration-Radar.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/running-biomechanics/2021_vanOeveren_Running-Biomechanics-Synthesis.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/running-biomechanics/2024_Bramah_Sprint-Mechanics-Assessment-Score.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/smartphone-technology/2015_Balsalobre-Fernandez_iPhone-Vertical-Jump.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/smartphone-technology/2022_Bishop_MyJumpLab-Validation.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/velocity-based-training/2011_Jidovtseff_Load-Velocity-1RM-Prediction.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/velocity-based-training/2016_Conceicao_Movement-Velocity-Lower-Limb.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/velocity-based-training/2016_Pareja-Blanco_Velocity-Loss-Training.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/velocity-based-training/2017_Balsalobre-Fernandez_Barbell-Velocity-1RM.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/velocity-based-training/2020_Balsalobre-Fernandez_Barbell-Trajectory-Snatch.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/research/thirdparty/pdfs/velocity-based-training/2023_Balsalobre-Fernandez_AI-Barbell-Velocity.pdf +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/technical/framerate.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/technical/implementation-details.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/technical/imu-metadata.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/technical/real-time-analysis.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/technical/triple-extension.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/translations/es/camera-setup.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/validation/determinism-test.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/validation/known-height-validation.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/docs/validation-status.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/examples/bulk/README.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/examples/bulk/bulk_processing.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/examples/bulk/simple_example.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/examples/programmatic_usage.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/mkdocs.yml +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/presentation/.gitignore +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/presentation/README.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/presentation/demos/.gitignore +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/presentation/demos/README.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/presentation/demos/api_demo.ipynb +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/presentation/demos/batch_processing_demo.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/presentation/demos/sample_data/.gitkeep +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/presentation/presentation_guide.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/presentation/revealjs/.gitignore +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/presentation/revealjs/Makefile +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/presentation/revealjs/README.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/presentation/revealjs/slides.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/presentation/speaker_script.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/requirements-docs.txt +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/samples/cmjs/README.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/scripts/README.md +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/scripts/analyze_determinism_variance.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/scripts/generate_test_data.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/scripts/plot_validation_results.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/scripts/test_determinism.sh +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/scripts/validate_known_heights.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/sonar-project.properties +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/src/kinemotion/__init__.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/src/kinemotion/cli.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/src/kinemotion/cmj/__init__.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/src/kinemotion/cmj/analysis.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/src/kinemotion/cmj/cli.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/src/kinemotion/cmj/debug_overlay.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/src/kinemotion/cmj/joint_angles.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/src/kinemotion/core/__init__.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/src/kinemotion/core/auto_tuning.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/src/kinemotion/core/cli_utils.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/src/kinemotion/core/debug_overlay_utils.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/src/kinemotion/core/filtering.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/src/kinemotion/core/metadata.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/src/kinemotion/core/pose.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/src/kinemotion/core/quality.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/src/kinemotion/core/smoothing.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/src/kinemotion/dropjump/__init__.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/src/kinemotion/dropjump/analysis.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/src/kinemotion/dropjump/cli.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/src/kinemotion/dropjump/debug_overlay.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/src/kinemotion/py.typed +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/tests/__init__.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/tests/core/test_quality.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/tests/test_adaptive_threshold.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/tests/test_api.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/tests/test_aspect_ratio.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/tests/test_cli_dropjump.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/tests/test_cli_imports.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/tests/test_cmj_analysis.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/tests/test_com_estimation.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/tests/test_contact_detection.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/tests/test_filtering.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/tests/test_joint_angles.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/tests/test_kinematics.py +0 -0
- {kinemotion-0.26.1 → kinemotion-0.28.0}/tests/test_polyorder.py +0 -0
|
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
<!-- version list -->
|
|
9
9
|
|
|
10
|
+
## v0.28.0 (2025-11-15)
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
- Standardize numeric precision across jump types
|
|
15
|
+
([`def84b0`](https://github.com/feniix/kinemotion/commit/def84b00ba4aef35f398ac615e328ba15e50133d))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## v0.27.0 (2025-11-14)
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
- Extract video codec from metadata
|
|
23
|
+
([`52c7ff2`](https://github.com/feniix/kinemotion/commit/52c7ff2ee3f6620a5271f670ab60e6bca8bc38fb))
|
|
24
|
+
|
|
25
|
+
|
|
10
26
|
## v0.26.1 (2025-11-14)
|
|
11
27
|
|
|
12
28
|
### Bug Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kinemotion
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.28.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
|
|
@@ -37,14 +37,14 @@ ______________________________________________________________________
|
|
|
37
37
|
{
|
|
38
38
|
"data": {
|
|
39
39
|
"jump_height_m": 0.506,
|
|
40
|
-
"
|
|
40
|
+
"flight_time_ms": 642.0,
|
|
41
41
|
"countermovement_depth_m": 0.045,
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
42
|
+
"eccentric_duration_ms": 412.0,
|
|
43
|
+
"concentric_duration_ms": 305.0,
|
|
44
|
+
"total_movement_time_ms": 717.0,
|
|
45
45
|
"peak_eccentric_velocity_m_s": -1.234,
|
|
46
46
|
"peak_concentric_velocity_m_s": 2.634,
|
|
47
|
-
"
|
|
47
|
+
"transition_time_ms": 135.0,
|
|
48
48
|
"standing_start_frame": 145.0,
|
|
49
49
|
"lowest_point_frame": 146.0,
|
|
50
50
|
"takeoff_frame": 154.3,
|
|
@@ -200,14 +200,14 @@ ______________________________________________________________________
|
|
|
200
200
|
**CMJ-specific fields:**
|
|
201
201
|
|
|
202
202
|
- `jump_height_m` (float): Maximum vertical displacement
|
|
203
|
-
- `
|
|
203
|
+
- `flight_time_ms` (float): Time in the air in milliseconds
|
|
204
204
|
- `countermovement_depth_m` (float): Depth of eccentric phase
|
|
205
|
-
- `
|
|
206
|
-
- `
|
|
207
|
-
- `
|
|
205
|
+
- `eccentric_duration_ms` (float): Time from start to lowest point in milliseconds
|
|
206
|
+
- `concentric_duration_ms` (float): Time from lowest point to takeoff in milliseconds
|
|
207
|
+
- `total_movement_time_ms` (float): Total time from start to takeoff in milliseconds
|
|
208
208
|
- `peak_eccentric_velocity_m_s` (float): Max downward velocity (negative)
|
|
209
209
|
- `peak_concentric_velocity_m_s` (float): Max upward velocity (negative in coords)
|
|
210
|
-
- `
|
|
210
|
+
- `transition_time_ms` (float | null): Amortization phase duration in milliseconds
|
|
211
211
|
- `standing_start_frame` (float | null): Frame where movement begins
|
|
212
212
|
- `lowest_point_frame` (float): Frame at deepest countermovement
|
|
213
213
|
- `takeoff_frame` (float): Frame where feet leave ground
|
|
@@ -414,7 +414,7 @@ ______________________________________________________________________
|
|
|
414
414
|
{
|
|
415
415
|
"data": {
|
|
416
416
|
"jump_height_m": 0.352,
|
|
417
|
-
"
|
|
417
|
+
"flight_time_ms": 534.0
|
|
418
418
|
},
|
|
419
419
|
"metadata": {
|
|
420
420
|
"quality": {
|
|
@@ -11,7 +11,7 @@ ______________________________________________________________________
|
|
|
11
11
|
```json
|
|
12
12
|
{
|
|
13
13
|
"jump_height_m": 0.352,
|
|
14
|
-
"
|
|
14
|
+
"flight_time_ms": 534.0,
|
|
15
15
|
"takeoff_frame": 154.3,
|
|
16
16
|
"landing_frame": 172.8,
|
|
17
17
|
"confidence": "high",
|
|
@@ -121,7 +121,7 @@ ______________________________________________________________________
|
|
|
121
121
|
```json
|
|
122
122
|
{
|
|
123
123
|
"jump_height_m": 0.352,
|
|
124
|
-
"
|
|
124
|
+
"flight_time_ms": 534.0,
|
|
125
125
|
"takeoff_frame": 154.3,
|
|
126
126
|
"landing_frame": 172.8,
|
|
127
127
|
"quality": {
|
|
@@ -185,7 +185,7 @@ ______________________________________________________________________
|
|
|
185
185
|
class JumpData:
|
|
186
186
|
"""Physical measurements."""
|
|
187
187
|
jump_height_m: float
|
|
188
|
-
|
|
188
|
+
flight_time_ms: float
|
|
189
189
|
...
|
|
190
190
|
|
|
191
191
|
@dataclass
|
|
@@ -532,7 +532,7 @@ def process_dropjump_video(
|
|
|
532
532
|
height=video.height,
|
|
533
533
|
duration_s=video.frame_count / video.fps,
|
|
534
534
|
frame_count=video.frame_count,
|
|
535
|
-
codec=
|
|
535
|
+
codec=video.codec,
|
|
536
536
|
)
|
|
537
537
|
|
|
538
538
|
processing_info = ProcessingInfo(
|
|
@@ -973,7 +973,7 @@ def process_cmj_video(
|
|
|
973
973
|
height=video.height,
|
|
974
974
|
duration_s=video.frame_count / video.fps,
|
|
975
975
|
frame_count=video.frame_count,
|
|
976
|
-
codec=
|
|
976
|
+
codec=video.codec,
|
|
977
977
|
)
|
|
978
978
|
|
|
979
979
|
processing_info = ProcessingInfo(
|
|
@@ -6,6 +6,8 @@ from typing import TYPE_CHECKING, TypedDict
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
from numpy.typing import NDArray
|
|
8
8
|
|
|
9
|
+
from ..core.formatting import format_float_metric
|
|
10
|
+
|
|
9
11
|
if TYPE_CHECKING:
|
|
10
12
|
from ..core.metadata import ResultMetadata
|
|
11
13
|
from ..core.quality import QualityAssessment
|
|
@@ -15,14 +17,14 @@ class CMJDataDict(TypedDict, total=False):
|
|
|
15
17
|
"""Type-safe dictionary for CMJ measurement data."""
|
|
16
18
|
|
|
17
19
|
jump_height_m: float
|
|
18
|
-
|
|
20
|
+
flight_time_ms: float
|
|
19
21
|
countermovement_depth_m: float
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
eccentric_duration_ms: float
|
|
23
|
+
concentric_duration_ms: float
|
|
24
|
+
total_movement_time_ms: float
|
|
23
25
|
peak_eccentric_velocity_m_s: float
|
|
24
26
|
peak_concentric_velocity_m_s: float
|
|
25
|
-
|
|
27
|
+
transition_time_ms: float | None
|
|
26
28
|
standing_start_frame: float | None
|
|
27
29
|
lowest_point_frame: float
|
|
28
30
|
takeoff_frame: float
|
|
@@ -43,14 +45,14 @@ class CMJMetrics:
|
|
|
43
45
|
|
|
44
46
|
Attributes:
|
|
45
47
|
jump_height: Maximum jump height in meters
|
|
46
|
-
flight_time: Time spent in the air in
|
|
48
|
+
flight_time: Time spent in the air in milliseconds
|
|
47
49
|
countermovement_depth: Vertical distance traveled during eccentric phase in meters
|
|
48
|
-
eccentric_duration: Time from countermovement start to lowest point in
|
|
49
|
-
concentric_duration: Time from lowest point to takeoff in
|
|
50
|
-
total_movement_time: Total time from countermovement start to takeoff in
|
|
50
|
+
eccentric_duration: Time from countermovement start to lowest point in milliseconds
|
|
51
|
+
concentric_duration: Time from lowest point to takeoff in milliseconds
|
|
52
|
+
total_movement_time: Total time from countermovement start to takeoff in milliseconds
|
|
51
53
|
peak_eccentric_velocity: Maximum downward velocity during countermovement in m/s
|
|
52
54
|
peak_concentric_velocity: Maximum upward velocity during propulsion in m/s
|
|
53
|
-
transition_time: Duration at lowest point (amortization phase) in
|
|
55
|
+
transition_time: Duration at lowest point (amortization phase) in milliseconds
|
|
54
56
|
standing_start_frame: Frame where standing phase ends (countermovement begins)
|
|
55
57
|
lowest_point_frame: Frame at lowest point of countermovement
|
|
56
58
|
takeoff_frame: Frame where athlete leaves ground
|
|
@@ -85,19 +87,27 @@ class CMJMetrics:
|
|
|
85
87
|
Dictionary with nested data and metadata structure.
|
|
86
88
|
"""
|
|
87
89
|
data: CMJDataDict = {
|
|
88
|
-
"jump_height_m":
|
|
89
|
-
"
|
|
90
|
-
"countermovement_depth_m":
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
"
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
"
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
90
|
+
"jump_height_m": format_float_metric(self.jump_height, 1, 3), # type: ignore[typeddict-item]
|
|
91
|
+
"flight_time_ms": format_float_metric(self.flight_time, 1000, 2), # type: ignore[typeddict-item]
|
|
92
|
+
"countermovement_depth_m": format_float_metric(
|
|
93
|
+
self.countermovement_depth, 1, 3
|
|
94
|
+
), # type: ignore[typeddict-item]
|
|
95
|
+
"eccentric_duration_ms": format_float_metric(
|
|
96
|
+
self.eccentric_duration, 1000, 2
|
|
97
|
+
), # type: ignore[typeddict-item]
|
|
98
|
+
"concentric_duration_ms": format_float_metric(
|
|
99
|
+
self.concentric_duration, 1000, 2
|
|
100
|
+
), # type: ignore[typeddict-item]
|
|
101
|
+
"total_movement_time_ms": format_float_metric(
|
|
102
|
+
self.total_movement_time, 1000, 2
|
|
103
|
+
), # type: ignore[typeddict-item]
|
|
104
|
+
"peak_eccentric_velocity_m_s": format_float_metric(
|
|
105
|
+
self.peak_eccentric_velocity, 1, 4
|
|
106
|
+
), # type: ignore[typeddict-item]
|
|
107
|
+
"peak_concentric_velocity_m_s": format_float_metric(
|
|
108
|
+
self.peak_concentric_velocity, 1, 4
|
|
109
|
+
), # type: ignore[typeddict-item]
|
|
110
|
+
"transition_time_ms": format_float_metric(self.transition_time, 1000, 2),
|
|
101
111
|
"standing_start_frame": (
|
|
102
112
|
float(self.standing_start_frame)
|
|
103
113
|
if self.standing_start_frame is not None
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Formatting utilities for consistent numeric output across jump analysis types.
|
|
2
|
+
|
|
3
|
+
This module provides shared helpers for formatting numeric values with appropriate
|
|
4
|
+
precision based on measurement type and capabilities of video-based analysis.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# Standard precision values for different measurement types
|
|
8
|
+
# These values are chosen based on:
|
|
9
|
+
# - Video analysis capabilities (30-240 fps)
|
|
10
|
+
# - Typical measurement uncertainty in video-based biomechanics
|
|
11
|
+
# - Balance between accuracy and readability
|
|
12
|
+
|
|
13
|
+
PRECISION_TIME_MS = 2 # Time in milliseconds: ±0.01ms (e.g., 534.12)
|
|
14
|
+
PRECISION_DISTANCE_M = 3 # Distance in meters: ±1mm (e.g., 0.352)
|
|
15
|
+
PRECISION_VELOCITY_M_S = 4 # Velocity in m/s: ±0.0001 m/s (e.g., 2.6340)
|
|
16
|
+
PRECISION_FRAME = 3 # Sub-frame interpolation precision (e.g., 154.342)
|
|
17
|
+
PRECISION_NORMALIZED = 4 # Normalized values 0-1 ratios (e.g., 0.0582)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def format_float_metric(
|
|
21
|
+
value: float | None,
|
|
22
|
+
multiplier: float = 1.0,
|
|
23
|
+
decimals: int = 2,
|
|
24
|
+
) -> float | None:
|
|
25
|
+
"""Format a float metric value with optional scaling and rounding.
|
|
26
|
+
|
|
27
|
+
This helper ensures consistent precision across all jump analysis outputs,
|
|
28
|
+
preventing false precision in measurements while maintaining appropriate
|
|
29
|
+
accuracy for the measurement type.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
value: The value to format, or None
|
|
33
|
+
multiplier: Factor to multiply value by (e.g., 1000 for seconds→milliseconds)
|
|
34
|
+
decimals: Number of decimal places to round to
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Formatted value rounded to specified decimals, or None if input is None
|
|
38
|
+
|
|
39
|
+
Examples:
|
|
40
|
+
>>> format_float_metric(0.534123, 1000, 2) # seconds to ms
|
|
41
|
+
534.12
|
|
42
|
+
>>> format_float_metric(0.3521234, 1, 3) # meters
|
|
43
|
+
0.352
|
|
44
|
+
>>> format_float_metric(None, 1, 2)
|
|
45
|
+
None
|
|
46
|
+
>>> format_float_metric(-1.23456, 1, 4) # negative values preserved
|
|
47
|
+
-1.2346
|
|
48
|
+
"""
|
|
49
|
+
if value is None:
|
|
50
|
+
return None
|
|
51
|
+
return round(value * multiplier, decimals)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def format_int_metric(value: float | int | None) -> int | None:
|
|
55
|
+
"""Format a value as an integer.
|
|
56
|
+
|
|
57
|
+
Used for frame numbers and other integer-valued metrics.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
value: The value to format, or None
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Value converted to int, or None if input is None
|
|
64
|
+
|
|
65
|
+
Examples:
|
|
66
|
+
>>> format_int_metric(42.7)
|
|
67
|
+
42
|
|
68
|
+
>>> format_int_metric(None)
|
|
69
|
+
None
|
|
70
|
+
>>> format_int_metric(154)
|
|
71
|
+
154
|
|
72
|
+
"""
|
|
73
|
+
if value is None:
|
|
74
|
+
return None
|
|
75
|
+
return int(value)
|
|
@@ -50,6 +50,9 @@ class VideoProcessor:
|
|
|
50
50
|
# OpenCV ignores rotation metadata, so we need to extract and apply it manually
|
|
51
51
|
self.rotation = 0 # Will be set by _extract_video_metadata()
|
|
52
52
|
|
|
53
|
+
# Extract codec information from video metadata
|
|
54
|
+
self.codec: str | None = None # Will be set by _extract_video_metadata()
|
|
55
|
+
|
|
53
56
|
# Calculate display dimensions considering SAR (Sample Aspect Ratio)
|
|
54
57
|
# Mobile videos often have non-square pixels encoded in SAR metadata
|
|
55
58
|
# OpenCV doesn't directly expose SAR, but we need to handle display correctly
|
|
@@ -141,6 +144,9 @@ class VideoProcessor:
|
|
|
141
144
|
|
|
142
145
|
stream = data["streams"][0]
|
|
143
146
|
|
|
147
|
+
# Extract codec name (e.g., "h264", "hevc", "vp9")
|
|
148
|
+
self.codec = stream.get("codec_name")
|
|
149
|
+
|
|
144
150
|
# Extract and parse SAR (Sample Aspect Ratio)
|
|
145
151
|
sar_str = stream.get("sample_aspect_ratio", "1:1")
|
|
146
152
|
self._parse_sample_aspect_ratio(sar_str)
|
|
@@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, TypedDict
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
from numpy.typing import NDArray
|
|
7
7
|
|
|
8
|
+
from ..core.formatting import format_float_metric, format_int_metric
|
|
8
9
|
from ..core.smoothing import compute_acceleration_from_derivative
|
|
9
10
|
from .analysis import (
|
|
10
11
|
ContactState,
|
|
@@ -19,38 +20,6 @@ if TYPE_CHECKING:
|
|
|
19
20
|
from ..core.quality import QualityAssessment
|
|
20
21
|
|
|
21
22
|
|
|
22
|
-
def _format_float_metric(
|
|
23
|
-
value: float | None, multiplier: float = 1, decimals: int = 2
|
|
24
|
-
) -> float | None:
|
|
25
|
-
"""Format a float metric value with optional scaling and rounding.
|
|
26
|
-
|
|
27
|
-
Args:
|
|
28
|
-
value: The value to format, or None
|
|
29
|
-
multiplier: Factor to multiply value by (default: 1)
|
|
30
|
-
decimals: Number of decimal places to round to (default: 2)
|
|
31
|
-
|
|
32
|
-
Returns:
|
|
33
|
-
Formatted value rounded to specified decimals, or None if input is None
|
|
34
|
-
"""
|
|
35
|
-
if value is None:
|
|
36
|
-
return None
|
|
37
|
-
return round(value * multiplier, decimals)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def _format_int_metric(value: float | int | None) -> int | None:
|
|
41
|
-
"""Format a value as an integer.
|
|
42
|
-
|
|
43
|
-
Args:
|
|
44
|
-
value: The value to format, or None
|
|
45
|
-
|
|
46
|
-
Returns:
|
|
47
|
-
Value converted to int, or None if input is None
|
|
48
|
-
"""
|
|
49
|
-
if value is None:
|
|
50
|
-
return None
|
|
51
|
-
return int(value)
|
|
52
|
-
|
|
53
|
-
|
|
54
23
|
class DropJumpDataDict(TypedDict, total=False):
|
|
55
24
|
"""Type-safe dictionary for drop jump measurement data."""
|
|
56
25
|
|
|
@@ -108,32 +77,32 @@ class DropJumpMetrics:
|
|
|
108
77
|
Dictionary containing formatted metric values.
|
|
109
78
|
"""
|
|
110
79
|
return {
|
|
111
|
-
"ground_contact_time_ms":
|
|
80
|
+
"ground_contact_time_ms": format_float_metric(
|
|
112
81
|
self.ground_contact_time, 1000, 2
|
|
113
82
|
),
|
|
114
|
-
"flight_time_ms":
|
|
115
|
-
"jump_height_m":
|
|
116
|
-
"jump_height_kinematic_m":
|
|
83
|
+
"flight_time_ms": format_float_metric(self.flight_time, 1000, 2),
|
|
84
|
+
"jump_height_m": format_float_metric(self.jump_height, 1, 3),
|
|
85
|
+
"jump_height_kinematic_m": format_float_metric(
|
|
117
86
|
self.jump_height_kinematic, 1, 3
|
|
118
87
|
),
|
|
119
|
-
"jump_height_trajectory_normalized":
|
|
88
|
+
"jump_height_trajectory_normalized": format_float_metric(
|
|
120
89
|
self.jump_height_trajectory, 1, 4
|
|
121
90
|
),
|
|
122
|
-
"contact_start_frame":
|
|
123
|
-
"contact_end_frame":
|
|
124
|
-
"flight_start_frame":
|
|
125
|
-
"flight_end_frame":
|
|
126
|
-
"peak_height_frame":
|
|
127
|
-
"contact_start_frame_precise":
|
|
91
|
+
"contact_start_frame": format_int_metric(self.contact_start_frame),
|
|
92
|
+
"contact_end_frame": format_int_metric(self.contact_end_frame),
|
|
93
|
+
"flight_start_frame": format_int_metric(self.flight_start_frame),
|
|
94
|
+
"flight_end_frame": format_int_metric(self.flight_end_frame),
|
|
95
|
+
"peak_height_frame": format_int_metric(self.peak_height_frame),
|
|
96
|
+
"contact_start_frame_precise": format_float_metric(
|
|
128
97
|
self.contact_start_frame_precise, 1, 3
|
|
129
98
|
),
|
|
130
|
-
"contact_end_frame_precise":
|
|
99
|
+
"contact_end_frame_precise": format_float_metric(
|
|
131
100
|
self.contact_end_frame_precise, 1, 3
|
|
132
101
|
),
|
|
133
|
-
"flight_start_frame_precise":
|
|
102
|
+
"flight_start_frame_precise": format_float_metric(
|
|
134
103
|
self.flight_start_frame_precise, 1, 3
|
|
135
104
|
),
|
|
136
|
-
"flight_end_frame_precise":
|
|
105
|
+
"flight_end_frame_precise": format_float_metric(
|
|
137
106
|
self.flight_end_frame_precise, 1, 3
|
|
138
107
|
),
|
|
139
108
|
}
|
|
@@ -118,7 +118,7 @@ class TestCMJCLIFileOperations:
|
|
|
118
118
|
# Test keys exist, not their values (which may be None)
|
|
119
119
|
assert isinstance(data, dict)
|
|
120
120
|
assert "jump_height_m" in data
|
|
121
|
-
assert "
|
|
121
|
+
assert "flight_time_ms" in data
|
|
122
122
|
|
|
123
123
|
def test_debug_video_output_created(self, cli_runner, minimal_video, tmp_path):
|
|
124
124
|
"""Test debug video file is created when analysis succeeds."""
|
|
@@ -97,14 +97,14 @@ def test_cmj_metrics_to_dict() -> None:
|
|
|
97
97
|
# Verify all expected keys are present in data
|
|
98
98
|
expected_keys = [
|
|
99
99
|
"jump_height_m",
|
|
100
|
-
"
|
|
100
|
+
"flight_time_ms",
|
|
101
101
|
"countermovement_depth_m",
|
|
102
|
-
"
|
|
103
|
-
"
|
|
104
|
-
"
|
|
102
|
+
"eccentric_duration_ms",
|
|
103
|
+
"concentric_duration_ms",
|
|
104
|
+
"total_movement_time_ms",
|
|
105
105
|
"peak_eccentric_velocity_m_s",
|
|
106
106
|
"peak_concentric_velocity_m_s",
|
|
107
|
-
"
|
|
107
|
+
"transition_time_ms",
|
|
108
108
|
"standing_start_frame",
|
|
109
109
|
"lowest_point_frame",
|
|
110
110
|
"takeoff_frame",
|
|
@@ -117,7 +117,7 @@ def test_cmj_metrics_to_dict() -> None:
|
|
|
117
117
|
|
|
118
118
|
# Verify all numeric values are Python types (not NumPy)
|
|
119
119
|
assert isinstance(result_dict["data"]["jump_height_m"], float)
|
|
120
|
-
assert isinstance(result_dict["data"]["
|
|
120
|
+
assert isinstance(result_dict["data"]["flight_time_ms"], float)
|
|
121
121
|
assert isinstance(result_dict["data"]["tracking_method"], str)
|
|
122
122
|
|
|
123
123
|
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""Tests for numeric formatting utilities."""
|
|
2
|
+
|
|
3
|
+
from kinemotion.core.formatting import (
|
|
4
|
+
PRECISION_DISTANCE_M,
|
|
5
|
+
PRECISION_FRAME,
|
|
6
|
+
PRECISION_NORMALIZED,
|
|
7
|
+
PRECISION_TIME_MS,
|
|
8
|
+
PRECISION_VELOCITY_M_S,
|
|
9
|
+
format_float_metric,
|
|
10
|
+
format_int_metric,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestFormatFloatMetric:
|
|
15
|
+
"""Test float metric formatting."""
|
|
16
|
+
|
|
17
|
+
def test_basic_rounding(self) -> None:
|
|
18
|
+
"""Test basic rounding without scaling."""
|
|
19
|
+
assert format_float_metric(1.23456, 1, 2) == 1.23
|
|
20
|
+
assert format_float_metric(1.23456, 1, 3) == 1.235
|
|
21
|
+
assert format_float_metric(1.23456, 1, 4) == 1.2346
|
|
22
|
+
|
|
23
|
+
def test_scaling_with_rounding(self) -> None:
|
|
24
|
+
"""Test scaling (e.g., seconds to milliseconds) with rounding."""
|
|
25
|
+
# Seconds to milliseconds
|
|
26
|
+
assert format_float_metric(0.534123, 1000, 2) == 534.12
|
|
27
|
+
assert format_float_metric(0.001, 1000, 2) == 1.0
|
|
28
|
+
assert format_float_metric(1.999999, 1000, 2) == 2000.0
|
|
29
|
+
|
|
30
|
+
def test_none_handling(self) -> None:
|
|
31
|
+
"""Test that None values pass through."""
|
|
32
|
+
assert format_float_metric(None, 1, 2) is None
|
|
33
|
+
assert format_float_metric(None, 1000, 3) is None
|
|
34
|
+
|
|
35
|
+
def test_negative_values(self) -> None:
|
|
36
|
+
"""Test that negative values are preserved."""
|
|
37
|
+
assert format_float_metric(-1.23456, 1, 2) == -1.23
|
|
38
|
+
assert format_float_metric(-0.534, 1000, 2) == -534.0
|
|
39
|
+
|
|
40
|
+
def test_zero_values(self) -> None:
|
|
41
|
+
"""Test zero handling."""
|
|
42
|
+
assert format_float_metric(0.0, 1, 2) == 0.0
|
|
43
|
+
assert format_float_metric(0.0, 1000, 2) == 0.0
|
|
44
|
+
|
|
45
|
+
def test_precision_constants(self) -> None:
|
|
46
|
+
"""Test that precision constants are defined correctly."""
|
|
47
|
+
assert PRECISION_TIME_MS == 2
|
|
48
|
+
assert PRECISION_DISTANCE_M == 3
|
|
49
|
+
assert PRECISION_VELOCITY_M_S == 4
|
|
50
|
+
assert PRECISION_FRAME == 3
|
|
51
|
+
assert PRECISION_NORMALIZED == 4
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class TestFormatIntMetric:
|
|
55
|
+
"""Test integer metric formatting."""
|
|
56
|
+
|
|
57
|
+
def test_float_to_int(self) -> None:
|
|
58
|
+
"""Test float to integer conversion."""
|
|
59
|
+
assert format_int_metric(42.7) == 42
|
|
60
|
+
assert format_int_metric(42.2) == 42
|
|
61
|
+
assert format_int_metric(42.9) == 42
|
|
62
|
+
|
|
63
|
+
def test_int_passthrough(self) -> None:
|
|
64
|
+
"""Test that integers pass through."""
|
|
65
|
+
assert format_int_metric(42) == 42
|
|
66
|
+
assert format_int_metric(0) == 0
|
|
67
|
+
|
|
68
|
+
def test_none_handling(self) -> None:
|
|
69
|
+
"""Test that None values pass through."""
|
|
70
|
+
assert format_int_metric(None) is None
|
|
71
|
+
|
|
72
|
+
def test_negative_values(self) -> None:
|
|
73
|
+
"""Test negative value handling."""
|
|
74
|
+
assert format_int_metric(-42.7) == -42
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class TestPrecisionConsistency:
|
|
78
|
+
"""Test precision consistency across measurement types."""
|
|
79
|
+
|
|
80
|
+
def test_time_precision(self) -> None:
|
|
81
|
+
"""Test time measurements use 2 decimal places."""
|
|
82
|
+
# Time in milliseconds
|
|
83
|
+
time_ms = format_float_metric(534.123456, 1, PRECISION_TIME_MS)
|
|
84
|
+
assert time_ms == 534.12
|
|
85
|
+
|
|
86
|
+
# Seconds to milliseconds
|
|
87
|
+
time_s_to_ms = format_float_metric(0.534123456, 1000, PRECISION_TIME_MS)
|
|
88
|
+
assert time_s_to_ms == 534.12
|
|
89
|
+
|
|
90
|
+
def test_distance_precision(self) -> None:
|
|
91
|
+
"""Test distance measurements use 3 decimal places."""
|
|
92
|
+
# Jump height in meters
|
|
93
|
+
height = format_float_metric(0.35212345, 1, PRECISION_DISTANCE_M)
|
|
94
|
+
assert height == 0.352
|
|
95
|
+
|
|
96
|
+
# Countermovement depth
|
|
97
|
+
depth = format_float_metric(0.04512345, 1, PRECISION_DISTANCE_M)
|
|
98
|
+
assert depth == 0.045
|
|
99
|
+
|
|
100
|
+
def test_velocity_precision(self) -> None:
|
|
101
|
+
"""Test velocity measurements use 4 decimal places."""
|
|
102
|
+
# Velocity in m/s
|
|
103
|
+
velocity = format_float_metric(2.63401234, 1, PRECISION_VELOCITY_M_S)
|
|
104
|
+
assert velocity == 2.634
|
|
105
|
+
|
|
106
|
+
# Negative velocity (downward)
|
|
107
|
+
neg_velocity = format_float_metric(-1.23459999, 1, PRECISION_VELOCITY_M_S)
|
|
108
|
+
assert neg_velocity == -1.2346
|
|
109
|
+
|
|
110
|
+
def test_frame_precision(self) -> None:
|
|
111
|
+
"""Test frame numbers use 3 decimal places for sub-frame precision."""
|
|
112
|
+
frame = format_float_metric(154.342567, 1, PRECISION_FRAME)
|
|
113
|
+
assert frame == 154.343
|
|
114
|
+
|
|
115
|
+
def test_normalized_precision(self) -> None:
|
|
116
|
+
"""Test normalized values use 4 decimal places."""
|
|
117
|
+
normalized = format_float_metric(0.058234567, 1, PRECISION_NORMALIZED)
|
|
118
|
+
assert normalized == 0.0582
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class TestRealWorldExamples:
|
|
122
|
+
"""Test with real-world measurement examples."""
|
|
123
|
+
|
|
124
|
+
def test_cmj_measurements(self) -> None:
|
|
125
|
+
"""Test CMJ measurement formatting."""
|
|
126
|
+
# Jump height: 0.40502274976565683 → 0.405
|
|
127
|
+
assert format_float_metric(0.40502274976565683, 1, 3) == 0.405
|
|
128
|
+
|
|
129
|
+
# Flight time: 0.57471 seconds → 574.71 ms
|
|
130
|
+
assert format_float_metric(0.57471, 1000, 2) == 574.71
|
|
131
|
+
|
|
132
|
+
# Countermovement depth: 0.0024646043777466486 → 0.002
|
|
133
|
+
assert format_float_metric(0.0024646043777466486, 1, 3) == 0.002
|
|
134
|
+
|
|
135
|
+
# Peak velocity: 0.0028023441632588253 → 0.0028
|
|
136
|
+
assert format_float_metric(0.0028023441632588253, 1, 4) == 0.0028
|
|
137
|
+
|
|
138
|
+
def test_dropjump_measurements(self) -> None:
|
|
139
|
+
"""Test drop jump measurement formatting."""
|
|
140
|
+
# Ground contact time: 0.35709 seconds → 357.09 ms
|
|
141
|
+
assert format_float_metric(0.35709, 1000, 2) == 357.09
|
|
142
|
+
|
|
143
|
+
# Jump height: 0.259123 → 0.259
|
|
144
|
+
assert format_float_metric(0.259123, 1, 3) == 0.259
|
|
145
|
+
|
|
146
|
+
# Trajectory normalized: 0.058234 → 0.0582
|
|
147
|
+
assert format_float_metric(0.058234, 1, 4) == 0.0582
|
|
148
|
+
|
|
149
|
+
# Precise frame: 32.034567 → 32.035
|
|
150
|
+
assert format_float_metric(32.034567, 1, 3) == 32.035
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class TestEdgeCases:
|
|
154
|
+
"""Test edge cases and boundary conditions."""
|
|
155
|
+
|
|
156
|
+
def test_very_small_values(self) -> None:
|
|
157
|
+
"""Test very small values near zero."""
|
|
158
|
+
# Should not use scientific notation
|
|
159
|
+
assert format_float_metric(0.0001, 1, 4) == 0.0001
|
|
160
|
+
assert format_float_metric(0.00001, 1, 5) == 0.00001
|
|
161
|
+
|
|
162
|
+
def test_very_large_values(self) -> None:
|
|
163
|
+
"""Test very large values."""
|
|
164
|
+
assert format_float_metric(999999.123, 1, 2) == 999999.12
|
|
165
|
+
assert format_float_metric(1000000.5, 1, 1) == 1000000.5
|
|
166
|
+
|
|
167
|
+
def test_exact_values(self) -> None:
|
|
168
|
+
"""Test values that are exact after rounding."""
|
|
169
|
+
assert format_float_metric(1.5, 1, 2) == 1.5
|
|
170
|
+
assert format_float_metric(1.50, 1, 2) == 1.5
|
|
171
|
+
assert format_float_metric(0.0, 1, 3) == 0.0
|
|
172
|
+
|
|
173
|
+
def test_rounding_at_boundary(self) -> None:
|
|
174
|
+
"""Test rounding at .5 boundary (Python uses banker's rounding)."""
|
|
175
|
+
# Banker's rounding: round to nearest even
|
|
176
|
+
assert format_float_metric(2.5, 1, 0) == 2.0 # rounds to even (2)
|
|
177
|
+
assert format_float_metric(3.5, 1, 0) == 4.0 # rounds to even (4)
|
|
178
|
+
assert format_float_metric(1.25, 1, 1) == 1.2 # rounds to even (1.2)
|
|
179
|
+
assert format_float_metric(1.35, 1, 1) == 1.4 # rounds to even (1.4)
|