kinemotion 0.43.0__py3-none-any.whl → 0.44.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of kinemotion might be problematic. Click here for more details.

kinemotion/api.py CHANGED
@@ -6,6 +6,7 @@ from concurrent.futures import ProcessPoolExecutor, as_completed
6
6
  from dataclasses import dataclass
7
7
  from pathlib import Path
8
8
 
9
+ import cv2
9
10
  import numpy as np
10
11
 
11
12
  from .cmj.analysis import compute_average_hip_position, detect_cmj_phases
@@ -178,18 +179,26 @@ def _process_all_frames(
178
179
  verbose: bool,
179
180
  timer: PerformanceTimer | None = None,
180
181
  close_tracker: bool = True,
181
- ) -> tuple[list, list]:
182
+ target_debug_fps: float = 30.0,
183
+ max_debug_dim: int = 720,
184
+ ) -> tuple[list, list, list]:
182
185
  """Process all frames from video and extract pose landmarks.
183
186
 
187
+ Optimizes memory and speed by:
188
+ 1. Decimating frames stored for debug video (to target_debug_fps)
189
+ 2. Pre-resizing stored frames (to max_debug_dim)
190
+
184
191
  Args:
185
192
  video: Video processor to read frames from
186
193
  tracker: Pose tracker for landmark detection
187
194
  verbose: Print progress messages
188
195
  timer: Optional PerformanceTimer for measuring operations
189
196
  close_tracker: Whether to close the tracker after processing (default: True)
197
+ target_debug_fps: Target FPS for debug video (default: 30.0)
198
+ max_debug_dim: Max dimension for debug video frames (default: 720)
190
199
 
191
200
  Returns:
192
- Tuple of (frames, landmarks_sequence)
201
+ Tuple of (debug_frames, landmarks_sequence, frame_indices)
193
202
 
194
203
  Raises:
195
204
  ValueError: If no frames could be processed
@@ -198,7 +207,24 @@ def _process_all_frames(
198
207
  print("Tracking pose landmarks...")
199
208
 
200
209
  landmarks_sequence = []
201
- frames = []
210
+ debug_frames = []
211
+ frame_indices = []
212
+
213
+ # Calculate decimation and resize parameters
214
+ step = max(1, int(video.fps / target_debug_fps))
215
+
216
+ # Calculate resize dimensions maintaining aspect ratio
217
+ # Logic mirrors BaseDebugOverlayRenderer to ensure consistency
218
+ w, h = video.display_width, video.display_height
219
+ scale = 1.0
220
+ if max(w, h) > max_debug_dim:
221
+ scale = max_debug_dim / max(w, h)
222
+
223
+ debug_w = int(w * scale) // 2 * 2
224
+ debug_h = int(h * scale) // 2 * 2
225
+ should_resize = (debug_w != video.width) or (debug_h != video.height)
226
+
227
+ frame_idx = 0
202
228
 
203
229
  if timer:
204
230
  with timer.measure("pose_tracking"):
@@ -207,26 +233,54 @@ def _process_all_frames(
207
233
  if frame is None:
208
234
  break
209
235
 
210
- frames.append(frame)
236
+ # 1. Track on FULL resolution frame (preserves accuracy)
211
237
  landmarks = tracker.process_frame(frame)
212
238
  landmarks_sequence.append(landmarks)
239
+
240
+ # 2. Store frame for debug video ONLY if matches step
241
+ if frame_idx % step == 0:
242
+ # Pre-resize to save memory and later encoding time
243
+ if should_resize:
244
+ # Use simple linear interpolation for speed (debug only)
245
+ processed_frame = cv2.resize(
246
+ frame, (debug_w, debug_h), interpolation=cv2.INTER_LINEAR
247
+ )
248
+ else:
249
+ processed_frame = frame
250
+
251
+ debug_frames.append(processed_frame)
252
+ frame_indices.append(frame_idx)
253
+
254
+ frame_idx += 1
213
255
  else:
214
256
  while True:
215
257
  frame = video.read_frame()
216
258
  if frame is None:
217
259
  break
218
260
 
219
- frames.append(frame)
220
261
  landmarks = tracker.process_frame(frame)
221
262
  landmarks_sequence.append(landmarks)
222
263
 
264
+ if frame_idx % step == 0:
265
+ if should_resize:
266
+ processed_frame = cv2.resize(
267
+ frame, (debug_w, debug_h), interpolation=cv2.INTER_LINEAR
268
+ )
269
+ else:
270
+ processed_frame = frame
271
+
272
+ debug_frames.append(processed_frame)
273
+ frame_indices.append(frame_idx)
274
+
275
+ frame_idx += 1
276
+
223
277
  if close_tracker:
224
278
  tracker.close()
225
279
 
226
280
  if not landmarks_sequence:
227
281
  raise ValueError("No frames could be processed from video")
228
282
 
229
- return frames, landmarks_sequence
283
+ return debug_frames, landmarks_sequence, frame_indices
230
284
 
231
285
 
232
286
  def _apply_smoothing(
@@ -492,11 +546,11 @@ def process_dropjump_video(
492
546
  )
493
547
  should_close_tracker = True
494
548
 
495
- frames, landmarks_sequence = _process_all_frames(
549
+ frames, landmarks_sequence, frame_indices = _process_all_frames(
496
550
  video, tracker, verbose, timer, close_tracker=should_close_tracker
497
551
  )
498
552
 
499
- # Analyze video characteristics and auto-tune parameters
553
+ # Auto-tune parameters
500
554
  with timer.measure("parameter_auto_tuning"):
501
555
  characteristics = analyze_video_sample(
502
556
  landmarks_sequence, video.fps, video.frame_count
@@ -640,23 +694,35 @@ def process_dropjump_video(
640
694
  if verbose:
641
695
  print(f"Generating debug video: {output_video}")
642
696
 
697
+ # Determine debug video properties from the pre-processed frames
698
+ debug_h, debug_w = frames[0].shape[:2]
699
+ if video.fps > 30:
700
+ debug_fps = video.fps / (video.fps / 30.0)
701
+ else:
702
+ debug_fps = video.fps
703
+ # Use approximate 30fps if decimated, or actual if not
704
+ if len(frames) < len(landmarks_sequence):
705
+ # Re-calculate step to get precise FPS
706
+ step = max(1, int(video.fps / 30.0))
707
+ debug_fps = video.fps / step
708
+
643
709
  if timer:
644
710
  with timer.measure("debug_video_generation"):
645
711
  with DebugOverlayRenderer(
646
712
  output_video,
647
- video.width,
648
- video.height,
649
- video.display_width,
650
- video.display_height,
651
- video.fps,
713
+ debug_w, # Encoded width = pre-resized width
714
+ debug_h, # Encoded height
715
+ debug_w, # Display width (already corrected)
716
+ debug_h, # Display height
717
+ debug_fps,
652
718
  timer=timer,
653
719
  ) as renderer:
654
- for i, frame in enumerate(frames):
720
+ for frame, idx in zip(frames, frame_indices, strict=True):
655
721
  annotated = renderer.render_frame(
656
722
  frame,
657
- smoothed_landmarks[i],
658
- contact_states[i],
659
- i,
723
+ smoothed_landmarks[idx],
724
+ contact_states[idx],
725
+ idx,
660
726
  metrics,
661
727
  use_com=False,
662
728
  )
@@ -667,19 +733,19 @@ def process_dropjump_video(
667
733
  else:
668
734
  with DebugOverlayRenderer(
669
735
  output_video,
670
- video.width,
671
- video.height,
672
- video.display_width,
673
- video.display_height,
674
- video.fps,
736
+ debug_w,
737
+ debug_h,
738
+ debug_w,
739
+ debug_h,
740
+ debug_fps,
675
741
  timer=timer,
676
742
  ) as renderer:
677
- for i, frame in enumerate(frames):
743
+ for frame, idx in zip(frames, frame_indices, strict=True):
678
744
  annotated = renderer.render_frame(
679
745
  frame,
680
- smoothed_landmarks[i],
681
- contact_states[i],
682
- i,
746
+ smoothed_landmarks[idx],
747
+ contact_states[idx],
748
+ idx,
683
749
  metrics,
684
750
  use_com=False,
685
751
  )
@@ -999,7 +1065,7 @@ def process_cmj_video(
999
1065
  )
1000
1066
  should_close_tracker = True
1001
1067
 
1002
- frames, landmarks_sequence = _process_all_frames(
1068
+ frames, landmarks_sequence, frame_indices = _process_all_frames(
1003
1069
  video, tracker, verbose, timer, close_tracker=should_close_tracker
1004
1070
  )
1005
1071
 
@@ -1153,20 +1219,25 @@ def process_cmj_video(
1153
1219
  if verbose:
1154
1220
  print(f"Generating debug video: {output_video}")
1155
1221
 
1222
+ # Determine debug video properties from the pre-processed frames
1223
+ debug_h, debug_w = frames[0].shape[:2]
1224
+ step = max(1, int(video.fps / 30.0))
1225
+ debug_fps = video.fps / step
1226
+
1156
1227
  if timer:
1157
1228
  with timer.measure("debug_video_generation"):
1158
1229
  with CMJDebugOverlayRenderer(
1159
1230
  output_video,
1160
- video.width,
1161
- video.height,
1162
- video.display_width,
1163
- video.display_height,
1164
- video.fps,
1231
+ debug_w,
1232
+ debug_h,
1233
+ debug_w,
1234
+ debug_h,
1235
+ debug_fps,
1165
1236
  timer=timer, # Passing timer here too
1166
1237
  ) as renderer:
1167
- for i, frame in enumerate(frames):
1238
+ for frame, idx in zip(frames, frame_indices, strict=True):
1168
1239
  annotated = renderer.render_frame(
1169
- frame, smoothed_landmarks[i], i, metrics
1240
+ frame, smoothed_landmarks[idx], idx, metrics
1170
1241
  )
1171
1242
  renderer.write_frame(annotated)
1172
1243
  # Capture re-encoding duration separately
@@ -1175,16 +1246,16 @@ def process_cmj_video(
1175
1246
  else:
1176
1247
  with CMJDebugOverlayRenderer(
1177
1248
  output_video,
1178
- video.width,
1179
- video.height,
1180
- video.display_width,
1181
- video.display_height,
1182
- video.fps,
1249
+ debug_w,
1250
+ debug_h,
1251
+ debug_w,
1252
+ debug_h,
1253
+ debug_fps,
1183
1254
  timer=timer, # Passing timer here too
1184
1255
  ) as renderer:
1185
- for i, frame in enumerate(frames):
1256
+ for frame, idx in zip(frames, frame_indices, strict=True):
1186
1257
  annotated = renderer.render_frame(
1187
- frame, smoothed_landmarks[i], i, metrics
1258
+ frame, smoothed_landmarks[idx], idx, metrics
1188
1259
  )
1189
1260
  renderer.write_frame(annotated)
1190
1261
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kinemotion
3
- Version: 0.43.0
3
+ Version: 0.44.0
4
4
  Summary: Video-based kinematic analysis for athletic performance
5
5
  Project-URL: Homepage, https://github.com/feniix/kinemotion
6
6
  Project-URL: Repository, https://github.com/feniix/kinemotion
@@ -1,5 +1,5 @@
1
1
  kinemotion/__init__.py,sha256=wPItmyGJUOFM6GPRVhAEvRz0-ErI7e2qiUREYJ9EfPQ,943
2
- kinemotion/api.py,sha256=oSCc3GNJcIBhjHyCoHbWRur8q3IzMP5y43ngwjyLwkg,52174
2
+ kinemotion/api.py,sha256=qrNJcnoTO-x2bBvzrtuQPDu4dTmJZjLW1HCpGhwYSXo,55202
3
3
  kinemotion/cli.py,sha256=cqYV_7URH0JUDy1VQ_EDLv63FmNO4Ns20m6s1XAjiP4,464
4
4
  kinemotion/cmj/__init__.py,sha256=Ynv0-Oco4I3Y1Ubj25m3h9h2XFqeNwpAewXmAYOmwfU,127
5
5
  kinemotion/cmj/analysis.py,sha256=qtULzp9uYzm5M0_Qu5YGJpuwjg9fz1VKAg6xg4NJxvM,21639
@@ -32,8 +32,8 @@ kinemotion/dropjump/kinematics.py,sha256=kH-XM66wlOCYMpjvyb6_Qh5ZebyOfFZ47rmhgE1
32
32
  kinemotion/dropjump/metrics_validator.py,sha256=CrTlGup8q2kyPXtA6HNwm7_yq0AsBaDllG7RVZdXmYA,9342
33
33
  kinemotion/dropjump/validation_bounds.py,sha256=5b4I3CKPybuvrbn-nP5yCcGF_sH4Vtyw3a5AWWvWnBk,4645
34
34
  kinemotion/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
- kinemotion-0.43.0.dist-info/METADATA,sha256=BTxQ2TBeVYsVzux_OTmoE-tOu8gbEBv2oxe7j-3nyDM,26020
36
- kinemotion-0.43.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
37
- kinemotion-0.43.0.dist-info/entry_points.txt,sha256=zaqnAnjLvcdrk1Qvj5nvXZCZ2gp0prS7it1zTJygcIY,50
38
- kinemotion-0.43.0.dist-info/licenses/LICENSE,sha256=KZajvqsHw0NoOHOi2q0FZ4NBe9HdV6oey-IPYAtHXfg,1088
39
- kinemotion-0.43.0.dist-info/RECORD,,
35
+ kinemotion-0.44.0.dist-info/METADATA,sha256=lyKjnYzgLU38CqyTavYfa3u3Wta898CY57RSPVKPf-8,26020
36
+ kinemotion-0.44.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
37
+ kinemotion-0.44.0.dist-info/entry_points.txt,sha256=zaqnAnjLvcdrk1Qvj5nvXZCZ2gp0prS7it1zTJygcIY,50
38
+ kinemotion-0.44.0.dist-info/licenses/LICENSE,sha256=KZajvqsHw0NoOHOi2q0FZ4NBe9HdV6oey-IPYAtHXfg,1088
39
+ kinemotion-0.44.0.dist-info/RECORD,,