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 +112 -41
- {kinemotion-0.43.0.dist-info → kinemotion-0.44.0.dist-info}/METADATA +1 -1
- {kinemotion-0.43.0.dist-info → kinemotion-0.44.0.dist-info}/RECORD +6 -6
- {kinemotion-0.43.0.dist-info → kinemotion-0.44.0.dist-info}/WHEEL +0 -0
- {kinemotion-0.43.0.dist-info → kinemotion-0.44.0.dist-info}/entry_points.txt +0 -0
- {kinemotion-0.43.0.dist-info → kinemotion-0.44.0.dist-info}/licenses/LICENSE +0 -0
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
#
|
|
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
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
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
|
|
720
|
+
for frame, idx in zip(frames, frame_indices, strict=True):
|
|
655
721
|
annotated = renderer.render_frame(
|
|
656
722
|
frame,
|
|
657
|
-
smoothed_landmarks[
|
|
658
|
-
contact_states[
|
|
659
|
-
|
|
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
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
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
|
|
743
|
+
for frame, idx in zip(frames, frame_indices, strict=True):
|
|
678
744
|
annotated = renderer.render_frame(
|
|
679
745
|
frame,
|
|
680
|
-
smoothed_landmarks[
|
|
681
|
-
contact_states[
|
|
682
|
-
|
|
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
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
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
|
|
1238
|
+
for frame, idx in zip(frames, frame_indices, strict=True):
|
|
1168
1239
|
annotated = renderer.render_frame(
|
|
1169
|
-
frame, smoothed_landmarks[
|
|
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
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
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
|
|
1256
|
+
for frame, idx in zip(frames, frame_indices, strict=True):
|
|
1186
1257
|
annotated = renderer.render_frame(
|
|
1187
|
-
frame, smoothed_landmarks[
|
|
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.
|
|
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=
|
|
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.
|
|
36
|
-
kinemotion-0.
|
|
37
|
-
kinemotion-0.
|
|
38
|
-
kinemotion-0.
|
|
39
|
-
kinemotion-0.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|