kinemotion 0.10.1__tar.gz → 0.10.3__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.
Potentially problematic release.
This version of kinemotion might be problematic. Click here for more details.
- {kinemotion-0.10.1 → kinemotion-0.10.3}/CHANGELOG.md +16 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/PKG-INFO +1 -1
- {kinemotion-0.10.1 → kinemotion-0.10.3}/pyproject.toml +1 -1
- {kinemotion-0.10.1 → kinemotion-0.10.3}/src/kinemotion/dropjump/cli.py +49 -48
- {kinemotion-0.10.1 → kinemotion-0.10.3}/tests/test_adaptive_threshold.py +17 -16
- {kinemotion-0.10.1 → kinemotion-0.10.3}/tests/test_aspect_ratio.py +2 -1
- {kinemotion-0.10.1 → kinemotion-0.10.3}/tests/test_filtering.py +10 -5
- {kinemotion-0.10.1 → kinemotion-0.10.3}/tests/test_polyorder.py +8 -4
- {kinemotion-0.10.1 → kinemotion-0.10.3}/uv.lock +1 -1
- {kinemotion-0.10.1 → kinemotion-0.10.3}/.dockerignore +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/.github/pull_request_template.md +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/.github/workflows/release.yml +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/.gitignore +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/.pre-commit-config.yaml +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/.tool-versions +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/CLAUDE.md +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/CODE_OF_CONDUCT.md +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/CONTRIBUTING.md +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/Dockerfile +1 -1
- {kinemotion-0.10.1 → kinemotion-0.10.3}/GEMINI.md +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/LICENSE +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/README.md +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/SECURITY.md +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/docs/BULK_PROCESSING.md +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/docs/ERRORS_FINDINGS.md +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/docs/FRAMERATE.md +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/docs/IMU_METADATA_PRESERVATION.md +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/docs/PARAMETERS.md +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/docs/VALIDATION_PLAN.md +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/examples/bulk/README.md +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/examples/bulk/bulk_processing.py +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/examples/bulk/simple_example.py +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/examples/programmatic_usage.py +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/src/kinemotion/__init__.py +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/src/kinemotion/api.py +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/src/kinemotion/cli.py +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/src/kinemotion/core/__init__.py +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/src/kinemotion/core/auto_tuning.py +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/src/kinemotion/core/filtering.py +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/src/kinemotion/core/pose.py +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/src/kinemotion/core/smoothing.py +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/src/kinemotion/core/video_io.py +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/src/kinemotion/dropjump/__init__.py +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/src/kinemotion/dropjump/analysis.py +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/src/kinemotion/dropjump/debug_overlay.py +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/src/kinemotion/dropjump/kinematics.py +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/src/kinemotion/py.typed +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/tests/__init__.py +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/tests/test_api.py +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/tests/test_com_estimation.py +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/tests/test_contact_detection.py +0 -0
- {kinemotion-0.10.1 → kinemotion-0.10.3}/tests/test_kinematics.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.10.3 (2025-11-03)
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
- Reduce function parameter count using dataclass
|
|
15
|
+
([`0b8abfd`](https://github.com/feniix/kinemotion/commit/0b8abfd6ee53835ba3d787924747ab5e46066395))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## v0.10.2 (2025-11-03)
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
|
|
22
|
+
- Replace legacy numpy random functions with Generator API
|
|
23
|
+
([`5cfa31b`](https://github.com/feniix/kinemotion/commit/5cfa31bce040eadfc53d52654c2e75087ef087a5))
|
|
24
|
+
|
|
25
|
+
|
|
10
26
|
## v0.10.1 (2025-11-03)
|
|
11
27
|
|
|
12
28
|
### Bug Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kinemotion
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.3
|
|
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
|
|
@@ -4,6 +4,7 @@ import csv
|
|
|
4
4
|
import glob
|
|
5
5
|
import json
|
|
6
6
|
import sys
|
|
7
|
+
from dataclasses import dataclass
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
from typing import Any
|
|
9
10
|
|
|
@@ -27,6 +28,19 @@ from .debug_overlay import DebugOverlayRenderer
|
|
|
27
28
|
from .kinematics import calculate_drop_jump_metrics
|
|
28
29
|
|
|
29
30
|
|
|
31
|
+
@dataclass
|
|
32
|
+
class AnalysisParameters:
|
|
33
|
+
"""Expert parameters for analysis customization."""
|
|
34
|
+
|
|
35
|
+
drop_start_frame: int | None = None
|
|
36
|
+
smoothing_window: int | None = None
|
|
37
|
+
velocity_threshold: float | None = None
|
|
38
|
+
min_contact_frames: int | None = None
|
|
39
|
+
visibility_threshold: float | None = None
|
|
40
|
+
detection_confidence: float | None = None
|
|
41
|
+
tracking_confidence: float | None = None
|
|
42
|
+
|
|
43
|
+
|
|
30
44
|
@click.command(name="dropjump-analyze")
|
|
31
45
|
@click.argument("video_path", nargs=-1, type=click.Path(exists=False), required=True)
|
|
32
46
|
@click.option(
|
|
@@ -202,6 +216,17 @@ def dropjump_analyze(
|
|
|
202
216
|
# Determine if batch mode should be used
|
|
203
217
|
use_batch = batch or len(video_files) > 1
|
|
204
218
|
|
|
219
|
+
# Group expert parameters
|
|
220
|
+
params = AnalysisParameters(
|
|
221
|
+
drop_start_frame=drop_start_frame,
|
|
222
|
+
smoothing_window=smoothing_window,
|
|
223
|
+
velocity_threshold=velocity_threshold,
|
|
224
|
+
min_contact_frames=min_contact_frames,
|
|
225
|
+
visibility_threshold=visibility_threshold,
|
|
226
|
+
detection_confidence=detection_confidence,
|
|
227
|
+
tracking_confidence=tracking_confidence,
|
|
228
|
+
)
|
|
229
|
+
|
|
205
230
|
if use_batch:
|
|
206
231
|
_process_batch(
|
|
207
232
|
video_files,
|
|
@@ -211,13 +236,7 @@ def dropjump_analyze(
|
|
|
211
236
|
output_dir,
|
|
212
237
|
json_output_dir,
|
|
213
238
|
csv_summary,
|
|
214
|
-
|
|
215
|
-
smoothing_window,
|
|
216
|
-
velocity_threshold,
|
|
217
|
-
min_contact_frames,
|
|
218
|
-
visibility_threshold,
|
|
219
|
-
detection_confidence,
|
|
220
|
-
tracking_confidence,
|
|
239
|
+
params,
|
|
221
240
|
)
|
|
222
241
|
else:
|
|
223
242
|
# Single video mode (original behavior)
|
|
@@ -228,13 +247,7 @@ def dropjump_analyze(
|
|
|
228
247
|
drop_height,
|
|
229
248
|
quality,
|
|
230
249
|
verbose,
|
|
231
|
-
|
|
232
|
-
smoothing_window,
|
|
233
|
-
velocity_threshold,
|
|
234
|
-
min_contact_frames,
|
|
235
|
-
visibility_threshold,
|
|
236
|
-
detection_confidence,
|
|
237
|
-
tracking_confidence,
|
|
250
|
+
params,
|
|
238
251
|
)
|
|
239
252
|
|
|
240
253
|
|
|
@@ -245,13 +258,7 @@ def _process_single(
|
|
|
245
258
|
drop_height: float,
|
|
246
259
|
quality: str,
|
|
247
260
|
verbose: bool,
|
|
248
|
-
|
|
249
|
-
smoothing_window: int | None,
|
|
250
|
-
velocity_threshold: float | None,
|
|
251
|
-
min_contact_frames: int | None,
|
|
252
|
-
visibility_threshold: float | None,
|
|
253
|
-
detection_confidence: float | None,
|
|
254
|
-
tracking_confidence: float | None,
|
|
261
|
+
expert_params: AnalysisParameters,
|
|
255
262
|
) -> None:
|
|
256
263
|
"""Process a single video (original CLI behavior)."""
|
|
257
264
|
click.echo(f"Analyzing video: {video_path}", err=True)
|
|
@@ -285,10 +292,10 @@ def _process_single(
|
|
|
285
292
|
initial_tracking_conf = 0.6
|
|
286
293
|
|
|
287
294
|
# Override with expert values if provided
|
|
288
|
-
if detection_confidence is not None:
|
|
289
|
-
initial_detection_conf = detection_confidence
|
|
290
|
-
if tracking_confidence is not None:
|
|
291
|
-
initial_tracking_conf = tracking_confidence
|
|
295
|
+
if expert_params.detection_confidence is not None:
|
|
296
|
+
initial_detection_conf = expert_params.detection_confidence
|
|
297
|
+
if expert_params.tracking_confidence is not None:
|
|
298
|
+
initial_tracking_conf = expert_params.tracking_confidence
|
|
292
299
|
|
|
293
300
|
# Initialize pose tracker
|
|
294
301
|
tracker = PoseTracker(
|
|
@@ -334,14 +341,14 @@ def _process_single(
|
|
|
334
341
|
params = auto_tune_parameters(characteristics, quality_preset)
|
|
335
342
|
|
|
336
343
|
# Apply expert overrides if provided
|
|
337
|
-
if smoothing_window is not None:
|
|
338
|
-
params.smoothing_window = smoothing_window
|
|
339
|
-
if velocity_threshold is not None:
|
|
340
|
-
params.velocity_threshold = velocity_threshold
|
|
341
|
-
if min_contact_frames is not None:
|
|
342
|
-
params.min_contact_frames = min_contact_frames
|
|
343
|
-
if visibility_threshold is not None:
|
|
344
|
-
params.visibility_threshold = visibility_threshold
|
|
344
|
+
if expert_params.smoothing_window is not None:
|
|
345
|
+
params.smoothing_window = expert_params.smoothing_window
|
|
346
|
+
if expert_params.velocity_threshold is not None:
|
|
347
|
+
params.velocity_threshold = expert_params.velocity_threshold
|
|
348
|
+
if expert_params.min_contact_frames is not None:
|
|
349
|
+
params.min_contact_frames = expert_params.min_contact_frames
|
|
350
|
+
if expert_params.visibility_threshold is not None:
|
|
351
|
+
params.visibility_threshold = expert_params.visibility_threshold
|
|
345
352
|
|
|
346
353
|
# Show selected parameters if verbose
|
|
347
354
|
if verbose:
|
|
@@ -463,7 +470,7 @@ def _process_single(
|
|
|
463
470
|
vertical_positions,
|
|
464
471
|
video.fps,
|
|
465
472
|
drop_height_m=drop_height,
|
|
466
|
-
drop_start_frame=drop_start_frame,
|
|
473
|
+
drop_start_frame=expert_params.drop_start_frame,
|
|
467
474
|
velocity_threshold=params.velocity_threshold,
|
|
468
475
|
smoothing_window=params.smoothing_window,
|
|
469
476
|
polyorder=params.polyorder,
|
|
@@ -545,13 +552,7 @@ def _process_batch(
|
|
|
545
552
|
output_dir: str | None,
|
|
546
553
|
json_output_dir: str | None,
|
|
547
554
|
csv_summary: str | None,
|
|
548
|
-
|
|
549
|
-
smoothing_window: int | None,
|
|
550
|
-
velocity_threshold: float | None,
|
|
551
|
-
min_contact_frames: int | None,
|
|
552
|
-
visibility_threshold: float | None,
|
|
553
|
-
detection_confidence: float | None,
|
|
554
|
-
tracking_confidence: float | None,
|
|
555
|
+
expert_params: AnalysisParameters,
|
|
555
556
|
) -> None:
|
|
556
557
|
"""Process multiple videos in batch mode using parallel processing."""
|
|
557
558
|
click.echo(
|
|
@@ -588,13 +589,13 @@ def _process_batch(
|
|
|
588
589
|
quality=quality,
|
|
589
590
|
output_video=debug_video,
|
|
590
591
|
json_output=json_file,
|
|
591
|
-
drop_start_frame=drop_start_frame,
|
|
592
|
-
smoothing_window=smoothing_window,
|
|
593
|
-
velocity_threshold=velocity_threshold,
|
|
594
|
-
min_contact_frames=min_contact_frames,
|
|
595
|
-
visibility_threshold=visibility_threshold,
|
|
596
|
-
detection_confidence=detection_confidence,
|
|
597
|
-
tracking_confidence=tracking_confidence,
|
|
592
|
+
drop_start_frame=expert_params.drop_start_frame,
|
|
593
|
+
smoothing_window=expert_params.smoothing_window,
|
|
594
|
+
velocity_threshold=expert_params.velocity_threshold,
|
|
595
|
+
min_contact_frames=expert_params.min_contact_frames,
|
|
596
|
+
visibility_threshold=expert_params.visibility_threshold,
|
|
597
|
+
detection_confidence=expert_params.detection_confidence,
|
|
598
|
+
tracking_confidence=expert_params.tracking_confidence,
|
|
598
599
|
)
|
|
599
600
|
configs.append(config)
|
|
600
601
|
|
|
@@ -14,8 +14,8 @@ def test_adaptive_threshold_basic() -> None:
|
|
|
14
14
|
baseline_frames = int(3 * fps) # 90 frames
|
|
15
15
|
|
|
16
16
|
# Baseline: small random noise around position 0.5
|
|
17
|
-
np.random.
|
|
18
|
-
baseline_positions = 0.5 +
|
|
17
|
+
rng = np.random.default_rng(42)
|
|
18
|
+
baseline_positions = 0.5 + rng.normal(0, 0.005, baseline_frames)
|
|
19
19
|
|
|
20
20
|
# Movement: larger position changes
|
|
21
21
|
movement_positions = np.linspace(0.5, 0.7, 60)
|
|
@@ -36,8 +36,8 @@ def test_adaptive_threshold_high_noise() -> None:
|
|
|
36
36
|
baseline_frames = int(3 * fps)
|
|
37
37
|
|
|
38
38
|
# High noise baseline
|
|
39
|
-
np.random.
|
|
40
|
-
baseline_positions = 0.5 +
|
|
39
|
+
rng = np.random.default_rng(42)
|
|
40
|
+
baseline_positions = 0.5 + rng.normal(0, 0.015, baseline_frames)
|
|
41
41
|
movement_positions = np.linspace(0.5, 0.8, 60)
|
|
42
42
|
positions = np.concatenate([baseline_positions, movement_positions])
|
|
43
43
|
|
|
@@ -56,8 +56,8 @@ def test_adaptive_threshold_low_noise() -> None:
|
|
|
56
56
|
baseline_frames = int(3 * fps)
|
|
57
57
|
|
|
58
58
|
# Very low noise baseline
|
|
59
|
-
np.random.
|
|
60
|
-
baseline_positions = 0.5 +
|
|
59
|
+
rng = np.random.default_rng(42)
|
|
60
|
+
baseline_positions = 0.5 + rng.normal(0, 0.002, baseline_frames)
|
|
61
61
|
movement_positions = np.linspace(0.5, 0.7, 60)
|
|
62
62
|
positions = np.concatenate([baseline_positions, movement_positions])
|
|
63
63
|
|
|
@@ -90,8 +90,8 @@ def test_adaptive_threshold_maximum_bound() -> None:
|
|
|
90
90
|
baseline_frames = int(3 * fps)
|
|
91
91
|
|
|
92
92
|
# Extreme noise baseline
|
|
93
|
-
np.random.
|
|
94
|
-
baseline_positions = 0.5 +
|
|
93
|
+
rng = np.random.default_rng(42)
|
|
94
|
+
baseline_positions = 0.5 + rng.normal(0, 0.05, baseline_frames)
|
|
95
95
|
movement_positions = np.linspace(0.5, 0.8, 60)
|
|
96
96
|
positions = np.concatenate([baseline_positions, movement_positions])
|
|
97
97
|
|
|
@@ -106,7 +106,8 @@ def test_adaptive_threshold_short_video() -> None:
|
|
|
106
106
|
fps = 30.0
|
|
107
107
|
|
|
108
108
|
# Only 60 frames (2 seconds) - less than 3 second baseline
|
|
109
|
-
|
|
109
|
+
rng = np.random.default_rng(42)
|
|
110
|
+
positions = 0.5 + rng.normal(0, 0.01, 60)
|
|
110
111
|
|
|
111
112
|
threshold = calculate_adaptive_threshold(positions, fps, baseline_duration=3.0)
|
|
112
113
|
|
|
@@ -137,8 +138,8 @@ def test_adaptive_threshold_different_fps() -> None:
|
|
|
137
138
|
fps = 60.0
|
|
138
139
|
baseline_frames = int(3 * fps) # 180 frames
|
|
139
140
|
|
|
140
|
-
np.random.
|
|
141
|
-
baseline_positions = 0.5 +
|
|
141
|
+
rng = np.random.default_rng(42)
|
|
142
|
+
baseline_positions = 0.5 + rng.normal(0, 0.008, baseline_frames)
|
|
142
143
|
movement_positions = np.linspace(0.5, 0.7, 120)
|
|
143
144
|
positions = np.concatenate([baseline_positions, movement_positions])
|
|
144
145
|
|
|
@@ -153,8 +154,8 @@ def test_adaptive_threshold_custom_multiplier() -> None:
|
|
|
153
154
|
fps = 30.0
|
|
154
155
|
baseline_frames = int(3 * fps)
|
|
155
156
|
|
|
156
|
-
np.random.
|
|
157
|
-
baseline_positions = 0.5 +
|
|
157
|
+
rng = np.random.default_rng(42)
|
|
158
|
+
baseline_positions = 0.5 + rng.normal(0, 0.008, baseline_frames)
|
|
158
159
|
movement_positions = np.linspace(0.5, 0.7, 60)
|
|
159
160
|
positions = np.concatenate([baseline_positions, movement_positions])
|
|
160
161
|
|
|
@@ -181,9 +182,9 @@ def test_adaptive_threshold_baseline_duration() -> None:
|
|
|
181
182
|
fps = 30.0
|
|
182
183
|
|
|
183
184
|
# Long video with different noise in different sections
|
|
184
|
-
np.random.
|
|
185
|
-
first_3s = 0.5 +
|
|
186
|
-
next_2s = 0.5 +
|
|
185
|
+
rng = np.random.default_rng(42)
|
|
186
|
+
first_3s = 0.5 + rng.normal(0, 0.005, int(3 * fps)) # Low noise
|
|
187
|
+
next_2s = 0.5 + rng.normal(0, 0.015, int(2 * fps)) # High noise
|
|
187
188
|
movement = np.linspace(0.5, 0.7, 60)
|
|
188
189
|
|
|
189
190
|
positions = np.concatenate([first_3s, next_2s, movement])
|
|
@@ -21,9 +21,10 @@ def create_test_video(
|
|
|
21
21
|
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
|
|
22
22
|
writer = cv2.VideoWriter(temp_path, fourcc, fps, (width, height))
|
|
23
23
|
|
|
24
|
+
rng = np.random.default_rng(42)
|
|
24
25
|
for _ in range(num_frames):
|
|
25
26
|
# Create a random frame
|
|
26
|
-
frame =
|
|
27
|
+
frame = rng.integers(0, 255, (height, width, 3), dtype=np.uint8)
|
|
27
28
|
writer.write(frame)
|
|
28
29
|
|
|
29
30
|
writer.release()
|
|
@@ -35,8 +35,9 @@ def test_detect_outliers_ransac_finds_glitches() -> None:
|
|
|
35
35
|
def test_detect_outliers_ransac_handles_clean_data() -> None:
|
|
36
36
|
"""Test that RANSAC does not flag valid points as outliers."""
|
|
37
37
|
# Create smooth motion with small noise
|
|
38
|
+
rng = np.random.default_rng(42)
|
|
38
39
|
positions = np.array([0.5 + 0.001 * i**2 for i in range(30)])
|
|
39
|
-
positions +=
|
|
40
|
+
positions += rng.normal(0, 0.001, 30) # Small noise
|
|
40
41
|
|
|
41
42
|
outliers = detect_outliers_ransac(
|
|
42
43
|
positions, window_size=15, threshold=0.02, min_inliers=0.7
|
|
@@ -220,8 +221,9 @@ def test_bilateral_temporal_filter_preserves_edges() -> None:
|
|
|
220
221
|
def test_bilateral_temporal_filter_smooths_noise() -> None:
|
|
221
222
|
"""Test that bilateral filter smooths noise within smooth regions."""
|
|
222
223
|
# Noisy flat region
|
|
224
|
+
rng = np.random.default_rng(42)
|
|
223
225
|
positions = np.array([0.5] * 30)
|
|
224
|
-
positions +=
|
|
226
|
+
positions += rng.normal(0, 0.01, 30)
|
|
225
227
|
|
|
226
228
|
filtered = bilateral_temporal_filter(
|
|
227
229
|
positions, window_size=9, sigma_spatial=3.0, sigma_intensity=0.02
|
|
@@ -233,7 +235,8 @@ def test_bilateral_temporal_filter_smooths_noise() -> None:
|
|
|
233
235
|
|
|
234
236
|
def test_bilateral_temporal_filter_window_size() -> None:
|
|
235
237
|
"""Test that bilateral filter handles even window sizes."""
|
|
236
|
-
|
|
238
|
+
rng = np.random.default_rng(42)
|
|
239
|
+
positions = rng.random(50)
|
|
237
240
|
|
|
238
241
|
# Even window size should be adjusted to odd
|
|
239
242
|
filtered_even = bilateral_temporal_filter(
|
|
@@ -284,10 +287,11 @@ def test_smooth_landmarks_advanced_with_bilateral() -> None:
|
|
|
284
287
|
# Create landmark sequence with sharp transition (landing)
|
|
285
288
|
n_frames = 40
|
|
286
289
|
landmark_sequence = []
|
|
290
|
+
rng = np.random.default_rng(42)
|
|
287
291
|
|
|
288
292
|
for i in range(n_frames):
|
|
289
293
|
y = 0.8 if i < 20 else 0.5 # Sharp drop at frame 20
|
|
290
|
-
y +=
|
|
294
|
+
y += rng.normal(0, 0.005) # Add noise
|
|
291
295
|
|
|
292
296
|
landmark_sequence.append(
|
|
293
297
|
{
|
|
@@ -322,10 +326,11 @@ def test_smooth_landmarks_advanced_combined() -> None:
|
|
|
322
326
|
n_frames = 50
|
|
323
327
|
landmark_sequence = []
|
|
324
328
|
|
|
329
|
+
rng = np.random.default_rng(42)
|
|
325
330
|
for i in range(n_frames):
|
|
326
331
|
# Parabolic motion
|
|
327
332
|
y = 0.5 + 0.001 * (i - 25) ** 2
|
|
328
|
-
y +=
|
|
333
|
+
y += rng.normal(0, 0.005) # Noise
|
|
329
334
|
|
|
330
335
|
# Add tracking glitch
|
|
331
336
|
if i == 25:
|
|
@@ -15,10 +15,11 @@ def test_smooth_landmarks_polyorder2_vs_polyorder3() -> None:
|
|
|
15
15
|
n_frames = 30
|
|
16
16
|
landmark_sequence = []
|
|
17
17
|
|
|
18
|
+
rng = np.random.default_rng(42)
|
|
18
19
|
for i in range(n_frames):
|
|
19
20
|
# Smooth parabolic motion with noise
|
|
20
21
|
base_y = 0.5 + 0.01 * (i - 15) ** 2 / 225 # Parabola
|
|
21
|
-
noisy_y = base_y +
|
|
22
|
+
noisy_y = base_y + rng.normal(0, 0.01)
|
|
22
23
|
|
|
23
24
|
landmark_sequence.append(
|
|
24
25
|
{
|
|
@@ -49,9 +50,10 @@ def test_smooth_landmarks_polyorder2_vs_polyorder3() -> None:
|
|
|
49
50
|
def test_velocity_from_derivative_polyorder() -> None:
|
|
50
51
|
"""Test velocity computation with different polynomial orders."""
|
|
51
52
|
# Create synthetic position data with quadratic motion
|
|
53
|
+
rng = np.random.default_rng(42)
|
|
52
54
|
t = np.linspace(0, 2, 60)
|
|
53
55
|
positions = 0.5 + 0.1 * t**2 # Parabolic trajectory
|
|
54
|
-
positions +=
|
|
56
|
+
positions += rng.normal(0, 0.005, len(positions)) # Add noise
|
|
55
57
|
|
|
56
58
|
# Compute velocity with different polynomial orders
|
|
57
59
|
velocity_2 = compute_velocity_from_derivative(
|
|
@@ -79,9 +81,10 @@ def test_velocity_from_derivative_polyorder() -> None:
|
|
|
79
81
|
def test_acceleration_from_derivative_polyorder() -> None:
|
|
80
82
|
"""Test acceleration computation with different polynomial orders."""
|
|
81
83
|
# Create synthetic position data with cubic motion (non-zero third derivative)
|
|
84
|
+
rng = np.random.default_rng(42)
|
|
82
85
|
t = np.linspace(0, 2, 60)
|
|
83
86
|
positions = 0.5 + 0.05 * t**3 # Cubic trajectory
|
|
84
|
-
positions +=
|
|
87
|
+
positions += rng.normal(0, 0.003, len(positions)) # Add noise
|
|
85
88
|
|
|
86
89
|
# Compute acceleration with different polynomial orders
|
|
87
90
|
accel_2 = compute_acceleration_from_derivative(
|
|
@@ -111,7 +114,8 @@ def test_acceleration_from_derivative_polyorder() -> None:
|
|
|
111
114
|
|
|
112
115
|
def test_polyorder_validation() -> None:
|
|
113
116
|
"""Test that polyorder must be less than window_length."""
|
|
114
|
-
|
|
117
|
+
rng = np.random.default_rng(42)
|
|
118
|
+
positions = rng.random(50)
|
|
115
119
|
|
|
116
120
|
# polyorder=2 with window=5 should work (2 < 5)
|
|
117
121
|
velocity_valid = compute_velocity_from_derivative(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -53,10 +53,10 @@ LABEL org.opencontainers.image.title="kinemotion" \
|
|
|
53
53
|
# - ffmpeg: Video codec support for OpenCV
|
|
54
54
|
# hadolint ignore=DL3008
|
|
55
55
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
56
|
+
ffmpeg \
|
|
56
57
|
libgl1 \
|
|
57
58
|
libglib2.0-0 \
|
|
58
59
|
libgomp1 \
|
|
59
|
-
ffmpeg \
|
|
60
60
|
&& apt-get clean \
|
|
61
61
|
&& rm -rf /var/lib/apt/lists/*
|
|
62
62
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|