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.

Files changed (55) hide show
  1. {kinemotion-0.10.1 → kinemotion-0.10.3}/CHANGELOG.md +16 -0
  2. {kinemotion-0.10.1 → kinemotion-0.10.3}/PKG-INFO +1 -1
  3. {kinemotion-0.10.1 → kinemotion-0.10.3}/pyproject.toml +1 -1
  4. {kinemotion-0.10.1 → kinemotion-0.10.3}/src/kinemotion/dropjump/cli.py +49 -48
  5. {kinemotion-0.10.1 → kinemotion-0.10.3}/tests/test_adaptive_threshold.py +17 -16
  6. {kinemotion-0.10.1 → kinemotion-0.10.3}/tests/test_aspect_ratio.py +2 -1
  7. {kinemotion-0.10.1 → kinemotion-0.10.3}/tests/test_filtering.py +10 -5
  8. {kinemotion-0.10.1 → kinemotion-0.10.3}/tests/test_polyorder.py +8 -4
  9. {kinemotion-0.10.1 → kinemotion-0.10.3}/uv.lock +1 -1
  10. {kinemotion-0.10.1 → kinemotion-0.10.3}/.dockerignore +0 -0
  11. {kinemotion-0.10.1 → kinemotion-0.10.3}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  12. {kinemotion-0.10.1 → kinemotion-0.10.3}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  13. {kinemotion-0.10.1 → kinemotion-0.10.3}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  14. {kinemotion-0.10.1 → kinemotion-0.10.3}/.github/pull_request_template.md +0 -0
  15. {kinemotion-0.10.1 → kinemotion-0.10.3}/.github/workflows/release.yml +0 -0
  16. {kinemotion-0.10.1 → kinemotion-0.10.3}/.gitignore +0 -0
  17. {kinemotion-0.10.1 → kinemotion-0.10.3}/.pre-commit-config.yaml +0 -0
  18. {kinemotion-0.10.1 → kinemotion-0.10.3}/.tool-versions +0 -0
  19. {kinemotion-0.10.1 → kinemotion-0.10.3}/CLAUDE.md +0 -0
  20. {kinemotion-0.10.1 → kinemotion-0.10.3}/CODE_OF_CONDUCT.md +0 -0
  21. {kinemotion-0.10.1 → kinemotion-0.10.3}/CONTRIBUTING.md +0 -0
  22. {kinemotion-0.10.1 → kinemotion-0.10.3}/Dockerfile +1 -1
  23. {kinemotion-0.10.1 → kinemotion-0.10.3}/GEMINI.md +0 -0
  24. {kinemotion-0.10.1 → kinemotion-0.10.3}/LICENSE +0 -0
  25. {kinemotion-0.10.1 → kinemotion-0.10.3}/README.md +0 -0
  26. {kinemotion-0.10.1 → kinemotion-0.10.3}/SECURITY.md +0 -0
  27. {kinemotion-0.10.1 → kinemotion-0.10.3}/docs/BULK_PROCESSING.md +0 -0
  28. {kinemotion-0.10.1 → kinemotion-0.10.3}/docs/ERRORS_FINDINGS.md +0 -0
  29. {kinemotion-0.10.1 → kinemotion-0.10.3}/docs/FRAMERATE.md +0 -0
  30. {kinemotion-0.10.1 → kinemotion-0.10.3}/docs/IMU_METADATA_PRESERVATION.md +0 -0
  31. {kinemotion-0.10.1 → kinemotion-0.10.3}/docs/PARAMETERS.md +0 -0
  32. {kinemotion-0.10.1 → kinemotion-0.10.3}/docs/VALIDATION_PLAN.md +0 -0
  33. {kinemotion-0.10.1 → kinemotion-0.10.3}/examples/bulk/README.md +0 -0
  34. {kinemotion-0.10.1 → kinemotion-0.10.3}/examples/bulk/bulk_processing.py +0 -0
  35. {kinemotion-0.10.1 → kinemotion-0.10.3}/examples/bulk/simple_example.py +0 -0
  36. {kinemotion-0.10.1 → kinemotion-0.10.3}/examples/programmatic_usage.py +0 -0
  37. {kinemotion-0.10.1 → kinemotion-0.10.3}/src/kinemotion/__init__.py +0 -0
  38. {kinemotion-0.10.1 → kinemotion-0.10.3}/src/kinemotion/api.py +0 -0
  39. {kinemotion-0.10.1 → kinemotion-0.10.3}/src/kinemotion/cli.py +0 -0
  40. {kinemotion-0.10.1 → kinemotion-0.10.3}/src/kinemotion/core/__init__.py +0 -0
  41. {kinemotion-0.10.1 → kinemotion-0.10.3}/src/kinemotion/core/auto_tuning.py +0 -0
  42. {kinemotion-0.10.1 → kinemotion-0.10.3}/src/kinemotion/core/filtering.py +0 -0
  43. {kinemotion-0.10.1 → kinemotion-0.10.3}/src/kinemotion/core/pose.py +0 -0
  44. {kinemotion-0.10.1 → kinemotion-0.10.3}/src/kinemotion/core/smoothing.py +0 -0
  45. {kinemotion-0.10.1 → kinemotion-0.10.3}/src/kinemotion/core/video_io.py +0 -0
  46. {kinemotion-0.10.1 → kinemotion-0.10.3}/src/kinemotion/dropjump/__init__.py +0 -0
  47. {kinemotion-0.10.1 → kinemotion-0.10.3}/src/kinemotion/dropjump/analysis.py +0 -0
  48. {kinemotion-0.10.1 → kinemotion-0.10.3}/src/kinemotion/dropjump/debug_overlay.py +0 -0
  49. {kinemotion-0.10.1 → kinemotion-0.10.3}/src/kinemotion/dropjump/kinematics.py +0 -0
  50. {kinemotion-0.10.1 → kinemotion-0.10.3}/src/kinemotion/py.typed +0 -0
  51. {kinemotion-0.10.1 → kinemotion-0.10.3}/tests/__init__.py +0 -0
  52. {kinemotion-0.10.1 → kinemotion-0.10.3}/tests/test_api.py +0 -0
  53. {kinemotion-0.10.1 → kinemotion-0.10.3}/tests/test_com_estimation.py +0 -0
  54. {kinemotion-0.10.1 → kinemotion-0.10.3}/tests/test_contact_detection.py +0 -0
  55. {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.1
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kinemotion"
3
- version = "0.10.1"
3
+ version = "0.10.3"
4
4
  description = "Video-based kinematic analysis for athletic performance"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10,<3.13"
@@ -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
- drop_start_frame,
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
- drop_start_frame,
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
- drop_start_frame: int | None,
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
- drop_start_frame: int | None,
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.seed(42)
18
- baseline_positions = 0.5 + np.random.normal(0, 0.005, baseline_frames)
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.seed(42)
40
- baseline_positions = 0.5 + np.random.normal(0, 0.015, baseline_frames)
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.seed(42)
60
- baseline_positions = 0.5 + np.random.normal(0, 0.002, baseline_frames)
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.seed(42)
94
- baseline_positions = 0.5 + np.random.normal(0, 0.05, baseline_frames)
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
- positions = 0.5 + np.random.normal(0, 0.01, 60)
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.seed(42)
141
- baseline_positions = 0.5 + np.random.normal(0, 0.008, baseline_frames)
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.seed(42)
157
- baseline_positions = 0.5 + np.random.normal(0, 0.005, baseline_frames)
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.seed(42)
185
- first_3s = 0.5 + np.random.normal(0, 0.005, int(3 * fps)) # Low noise
186
- next_2s = 0.5 + np.random.normal(0, 0.015, int(2 * fps)) # High noise
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 = np.random.randint(0, 255, (height, width, 3), dtype=np.uint8)
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 += np.random.normal(0, 0.001, 30) # Small noise
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 += np.random.normal(0, 0.01, 30)
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
- positions = np.random.rand(50)
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 += np.random.normal(0, 0.005) # Add noise
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 += np.random.normal(0, 0.005) # Noise
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 + np.random.normal(0, 0.01)
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 += np.random.normal(0, 0.005, len(positions)) # Add noise
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 += np.random.normal(0, 0.003, len(positions)) # Add noise
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
- positions = np.random.rand(50)
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(
@@ -603,7 +603,7 @@ wheels = [
603
603
 
604
604
  [[package]]
605
605
  name = "kinemotion"
606
- version = "0.10.0"
606
+ version = "0.10.2"
607
607
  source = { editable = "." }
608
608
  dependencies = [
609
609
  { name = "click" },
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