kinemotion 0.12.2__py3-none-any.whl → 0.12.3__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/core/auto_tuning.py +66 -30
- kinemotion/core/video_io.py +53 -29
- kinemotion/dropjump/analysis.py +36 -0
- kinemotion/dropjump/cli.py +60 -55
- {kinemotion-0.12.2.dist-info → kinemotion-0.12.3.dist-info}/METADATA +1 -1
- {kinemotion-0.12.2.dist-info → kinemotion-0.12.3.dist-info}/RECORD +9 -9
- {kinemotion-0.12.2.dist-info → kinemotion-0.12.3.dist-info}/WHEEL +0 -0
- {kinemotion-0.12.2.dist-info → kinemotion-0.12.3.dist-info}/entry_points.txt +0 -0
- {kinemotion-0.12.2.dist-info → kinemotion-0.12.3.dist-info}/licenses/LICENSE +0 -0
kinemotion/core/auto_tuning.py
CHANGED
|
@@ -216,6 +216,59 @@ def auto_tune_parameters(
|
|
|
216
216
|
)
|
|
217
217
|
|
|
218
218
|
|
|
219
|
+
def _collect_foot_visibility_and_positions(
|
|
220
|
+
frame_landmarks: dict[str, tuple[float, float, float]],
|
|
221
|
+
) -> tuple[list[float], list[float]]:
|
|
222
|
+
"""
|
|
223
|
+
Collect visibility scores and Y positions from foot landmarks.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
frame_landmarks: Landmarks for a single frame
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
Tuple of (visibility_scores, y_positions)
|
|
230
|
+
"""
|
|
231
|
+
foot_keys = [
|
|
232
|
+
"left_ankle",
|
|
233
|
+
"right_ankle",
|
|
234
|
+
"left_heel",
|
|
235
|
+
"right_heel",
|
|
236
|
+
"left_foot_index",
|
|
237
|
+
"right_foot_index",
|
|
238
|
+
]
|
|
239
|
+
|
|
240
|
+
frame_vis = []
|
|
241
|
+
frame_y_positions = []
|
|
242
|
+
|
|
243
|
+
for key in foot_keys:
|
|
244
|
+
if key in frame_landmarks:
|
|
245
|
+
_, y, vis = frame_landmarks[key] # x not needed for analysis
|
|
246
|
+
frame_vis.append(vis)
|
|
247
|
+
frame_y_positions.append(y)
|
|
248
|
+
|
|
249
|
+
return frame_vis, frame_y_positions
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _check_stable_period(positions: list[float]) -> bool:
|
|
253
|
+
"""
|
|
254
|
+
Check if video has a stable period at the start.
|
|
255
|
+
|
|
256
|
+
A stable period (low variance in first 30 frames) indicates
|
|
257
|
+
the subject is standing on an elevated platform before jumping.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
positions: List of average Y positions per frame
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
True if stable period detected, False otherwise
|
|
264
|
+
"""
|
|
265
|
+
if len(positions) < 30:
|
|
266
|
+
return False
|
|
267
|
+
|
|
268
|
+
first_30_std = float(np.std(positions[:30]))
|
|
269
|
+
return first_30_std < 0.01 # Very stable = on platform
|
|
270
|
+
|
|
271
|
+
|
|
219
272
|
def analyze_video_sample(
|
|
220
273
|
landmarks_sequence: list[dict[str, tuple[float, float, float]] | None],
|
|
221
274
|
fps: float,
|
|
@@ -235,35 +288,22 @@ def analyze_video_sample(
|
|
|
235
288
|
Returns:
|
|
236
289
|
VideoCharacteristics with analyzed properties
|
|
237
290
|
"""
|
|
238
|
-
# Calculate average landmark visibility
|
|
239
291
|
visibilities = []
|
|
240
292
|
positions = []
|
|
241
293
|
|
|
294
|
+
# Collect visibility and position data from all frames
|
|
242
295
|
for frame_landmarks in landmarks_sequence:
|
|
243
|
-
if frame_landmarks:
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
frame_vis = []
|
|
255
|
-
frame_y_positions = []
|
|
256
|
-
|
|
257
|
-
for key in foot_keys:
|
|
258
|
-
if key in frame_landmarks:
|
|
259
|
-
_, y, vis = frame_landmarks[key] # x not needed for analysis
|
|
260
|
-
frame_vis.append(vis)
|
|
261
|
-
frame_y_positions.append(y)
|
|
262
|
-
|
|
263
|
-
if frame_vis:
|
|
264
|
-
visibilities.append(float(np.mean(frame_vis)))
|
|
265
|
-
if frame_y_positions:
|
|
266
|
-
positions.append(float(np.mean(frame_y_positions)))
|
|
296
|
+
if not frame_landmarks:
|
|
297
|
+
continue
|
|
298
|
+
|
|
299
|
+
frame_vis, frame_y_positions = _collect_foot_visibility_and_positions(
|
|
300
|
+
frame_landmarks
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
if frame_vis:
|
|
304
|
+
visibilities.append(float(np.mean(frame_vis)))
|
|
305
|
+
if frame_y_positions:
|
|
306
|
+
positions.append(float(np.mean(frame_y_positions)))
|
|
267
307
|
|
|
268
308
|
# Compute metrics
|
|
269
309
|
avg_visibility = float(np.mean(visibilities)) if visibilities else 0.5
|
|
@@ -273,11 +313,7 @@ def analyze_video_sample(
|
|
|
273
313
|
tracking_quality = analyze_tracking_quality(avg_visibility)
|
|
274
314
|
|
|
275
315
|
# Check for stable period (indicates drop jump from elevated platform)
|
|
276
|
-
|
|
277
|
-
has_stable_period = False
|
|
278
|
-
if len(positions) >= 30:
|
|
279
|
-
first_30_std = float(np.std(positions[:30]))
|
|
280
|
-
has_stable_period = first_30_std < 0.01 # Very stable = on platform
|
|
316
|
+
has_stable_period = _check_stable_period(positions)
|
|
281
317
|
|
|
282
318
|
return VideoCharacteristics(
|
|
283
319
|
fps=fps,
|
kinemotion/core/video_io.py
CHANGED
|
@@ -65,6 +65,43 @@ class VideoProcessor:
|
|
|
65
65
|
self.display_width,
|
|
66
66
|
)
|
|
67
67
|
|
|
68
|
+
def _parse_sample_aspect_ratio(self, sar_str: str) -> None:
|
|
69
|
+
"""
|
|
70
|
+
Parse SAR string and update display dimensions.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
sar_str: SAR string in format "width:height" (e.g., "270:473")
|
|
74
|
+
"""
|
|
75
|
+
if not sar_str or ":" not in sar_str:
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
sar_parts = sar_str.split(":")
|
|
79
|
+
sar_width = int(sar_parts[0])
|
|
80
|
+
sar_height = int(sar_parts[1])
|
|
81
|
+
|
|
82
|
+
# Calculate display dimensions if pixels are non-square
|
|
83
|
+
# DAR = (width * SAR_width) / (height * SAR_height)
|
|
84
|
+
if sar_width != sar_height:
|
|
85
|
+
self.display_width = int(self.width * sar_width / sar_height)
|
|
86
|
+
self.display_height = self.height
|
|
87
|
+
|
|
88
|
+
def _extract_rotation_from_stream(self, stream: dict) -> int: # type: ignore[type-arg]
|
|
89
|
+
"""
|
|
90
|
+
Extract rotation metadata from video stream.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
stream: ffprobe stream dictionary
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Rotation angle in degrees (0, 90, -90, 180)
|
|
97
|
+
"""
|
|
98
|
+
side_data_list = stream.get("side_data_list", [])
|
|
99
|
+
for side_data in side_data_list:
|
|
100
|
+
if side_data.get("side_data_type") == "Display Matrix":
|
|
101
|
+
rotation = side_data.get("rotation", 0)
|
|
102
|
+
return int(rotation)
|
|
103
|
+
return 0
|
|
104
|
+
|
|
68
105
|
def _extract_video_metadata(self) -> None:
|
|
69
106
|
"""
|
|
70
107
|
Extract video metadata including SAR and rotation using ffprobe.
|
|
@@ -94,35 +131,22 @@ class VideoProcessor:
|
|
|
94
131
|
timeout=5,
|
|
95
132
|
)
|
|
96
133
|
|
|
97
|
-
if result.returncode
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if sar_width != sar_height:
|
|
114
|
-
self.display_width = int(
|
|
115
|
-
self.width * sar_width / sar_height
|
|
116
|
-
)
|
|
117
|
-
self.display_height = self.height
|
|
118
|
-
|
|
119
|
-
# Extract rotation from side_data_list (common for iPhone videos)
|
|
120
|
-
side_data_list = stream.get("side_data_list", [])
|
|
121
|
-
for side_data in side_data_list:
|
|
122
|
-
if side_data.get("side_data_type") == "Display Matrix":
|
|
123
|
-
rotation = side_data.get("rotation", 0)
|
|
124
|
-
# Convert to int and normalize to 0, 90, -90, 180
|
|
125
|
-
self.rotation = int(rotation)
|
|
134
|
+
if result.returncode != 0:
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
data = json.loads(result.stdout)
|
|
138
|
+
if "streams" not in data or len(data["streams"]) == 0:
|
|
139
|
+
return
|
|
140
|
+
|
|
141
|
+
stream = data["streams"][0]
|
|
142
|
+
|
|
143
|
+
# Extract and parse SAR (Sample Aspect Ratio)
|
|
144
|
+
sar_str = stream.get("sample_aspect_ratio", "1:1")
|
|
145
|
+
self._parse_sample_aspect_ratio(sar_str)
|
|
146
|
+
|
|
147
|
+
# Extract rotation from side_data_list (common for iPhone videos)
|
|
148
|
+
self.rotation = self._extract_rotation_from_stream(stream)
|
|
149
|
+
|
|
126
150
|
except (subprocess.TimeoutExpired, FileNotFoundError, json.JSONDecodeError):
|
|
127
151
|
# If ffprobe fails, keep original dimensions (square pixels)
|
|
128
152
|
pass
|
kinemotion/dropjump/analysis.py
CHANGED
|
@@ -752,3 +752,39 @@ def compute_average_foot_position(
|
|
|
752
752
|
return (0.5, 0.5) # Default to center if no visible feet
|
|
753
753
|
|
|
754
754
|
return (float(np.mean(x_positions)), float(np.mean(y_positions)))
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
def extract_foot_positions_and_visibilities(
|
|
758
|
+
smoothed_landmarks: list[dict[str, tuple[float, float, float]] | None],
|
|
759
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
760
|
+
"""
|
|
761
|
+
Extract vertical positions and average visibilities from smoothed landmarks.
|
|
762
|
+
|
|
763
|
+
This utility function eliminates code duplication between CLI and programmatic usage.
|
|
764
|
+
|
|
765
|
+
Args:
|
|
766
|
+
smoothed_landmarks: Smoothed landmark sequence from tracking
|
|
767
|
+
|
|
768
|
+
Returns:
|
|
769
|
+
Tuple of (vertical_positions, visibilities) as numpy arrays
|
|
770
|
+
"""
|
|
771
|
+
position_list: list[float] = []
|
|
772
|
+
visibilities_list: list[float] = []
|
|
773
|
+
|
|
774
|
+
for frame_landmarks in smoothed_landmarks:
|
|
775
|
+
if frame_landmarks:
|
|
776
|
+
_, foot_y = compute_average_foot_position(frame_landmarks)
|
|
777
|
+
position_list.append(foot_y)
|
|
778
|
+
|
|
779
|
+
# Average visibility of foot landmarks
|
|
780
|
+
foot_vis = []
|
|
781
|
+
for key in ["left_ankle", "right_ankle", "left_heel", "right_heel"]:
|
|
782
|
+
if key in frame_landmarks:
|
|
783
|
+
foot_vis.append(frame_landmarks[key][2])
|
|
784
|
+
visibilities_list.append(float(np.mean(foot_vis)) if foot_vis else 0.0)
|
|
785
|
+
else:
|
|
786
|
+
# Fill missing frames with last known position or default
|
|
787
|
+
position_list.append(position_list[-1] if position_list else 0.5)
|
|
788
|
+
visibilities_list.append(0.0)
|
|
789
|
+
|
|
790
|
+
return np.array(position_list), np.array(visibilities_list)
|
kinemotion/dropjump/cli.py
CHANGED
|
@@ -28,8 +28,8 @@ from ..core.pose import PoseTracker
|
|
|
28
28
|
from ..core.video_io import VideoProcessor
|
|
29
29
|
from .analysis import (
|
|
30
30
|
ContactState,
|
|
31
|
-
compute_average_foot_position,
|
|
32
31
|
detect_ground_contact,
|
|
32
|
+
extract_foot_positions_and_visibilities,
|
|
33
33
|
)
|
|
34
34
|
from .debug_overlay import DebugOverlayRenderer
|
|
35
35
|
from .kinematics import DropJumpMetrics, calculate_drop_jump_metrics
|
|
@@ -258,26 +258,7 @@ def _extract_positions_and_visibilities(
|
|
|
258
258
|
Tuple of (vertical_positions, visibilities)
|
|
259
259
|
"""
|
|
260
260
|
click.echo("Extracting foot positions...", err=True)
|
|
261
|
-
|
|
262
|
-
position_list: list[float] = []
|
|
263
|
-
visibilities_list: list[float] = []
|
|
264
|
-
|
|
265
|
-
for frame_landmarks in smoothed_landmarks:
|
|
266
|
-
if frame_landmarks:
|
|
267
|
-
_, foot_y = compute_average_foot_position(frame_landmarks)
|
|
268
|
-
position_list.append(foot_y)
|
|
269
|
-
|
|
270
|
-
# Average visibility of foot landmarks
|
|
271
|
-
foot_vis = []
|
|
272
|
-
for key in ["left_ankle", "right_ankle", "left_heel", "right_heel"]:
|
|
273
|
-
if key in frame_landmarks:
|
|
274
|
-
foot_vis.append(frame_landmarks[key][2])
|
|
275
|
-
visibilities_list.append(float(np.mean(foot_vis)) if foot_vis else 0.0)
|
|
276
|
-
else:
|
|
277
|
-
position_list.append(position_list[-1] if position_list else 0.5)
|
|
278
|
-
visibilities_list.append(0.0)
|
|
279
|
-
|
|
280
|
-
return np.array(position_list), np.array(visibilities_list)
|
|
261
|
+
return extract_foot_positions_and_visibilities(smoothed_landmarks)
|
|
281
262
|
|
|
282
263
|
|
|
283
264
|
def _create_debug_video(
|
|
@@ -567,6 +548,63 @@ def _compute_batch_statistics(results: list[VideoResult]) -> None:
|
|
|
567
548
|
)
|
|
568
549
|
|
|
569
550
|
|
|
551
|
+
def _format_time_metric(value: float | None, multiplier: float = 1000.0) -> str:
|
|
552
|
+
"""Format time metric for CSV output.
|
|
553
|
+
|
|
554
|
+
Args:
|
|
555
|
+
value: Time value in seconds
|
|
556
|
+
multiplier: Multiplier to convert to milliseconds (default: 1000.0)
|
|
557
|
+
|
|
558
|
+
Returns:
|
|
559
|
+
Formatted string or "N/A" if value is None
|
|
560
|
+
"""
|
|
561
|
+
return f"{value * multiplier:.1f}" if value is not None else "N/A"
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
def _format_distance_metric(value: float | None) -> str:
|
|
565
|
+
"""Format distance metric for CSV output.
|
|
566
|
+
|
|
567
|
+
Args:
|
|
568
|
+
value: Distance value in meters
|
|
569
|
+
|
|
570
|
+
Returns:
|
|
571
|
+
Formatted string or "N/A" if value is None
|
|
572
|
+
"""
|
|
573
|
+
return f"{value:.3f}" if value is not None else "N/A"
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def _create_csv_row_from_result(result: VideoResult) -> list[str]:
|
|
577
|
+
"""Create CSV row from video processing result.
|
|
578
|
+
|
|
579
|
+
Args:
|
|
580
|
+
result: Video processing result
|
|
581
|
+
|
|
582
|
+
Returns:
|
|
583
|
+
List of formatted values for CSV row
|
|
584
|
+
"""
|
|
585
|
+
video_name = Path(result.video_path).name
|
|
586
|
+
processing_time = f"{result.processing_time:.2f}"
|
|
587
|
+
|
|
588
|
+
if result.success and result.metrics:
|
|
589
|
+
return [
|
|
590
|
+
video_name,
|
|
591
|
+
_format_time_metric(result.metrics.ground_contact_time),
|
|
592
|
+
_format_time_metric(result.metrics.flight_time),
|
|
593
|
+
_format_distance_metric(result.metrics.jump_height),
|
|
594
|
+
processing_time,
|
|
595
|
+
"Success",
|
|
596
|
+
]
|
|
597
|
+
else:
|
|
598
|
+
return [
|
|
599
|
+
video_name,
|
|
600
|
+
"N/A",
|
|
601
|
+
"N/A",
|
|
602
|
+
"N/A",
|
|
603
|
+
processing_time,
|
|
604
|
+
f"Failed: {result.error}",
|
|
605
|
+
]
|
|
606
|
+
|
|
607
|
+
|
|
570
608
|
def _write_csv_summary(
|
|
571
609
|
csv_summary: str | None, results: list[VideoResult], successful: list[VideoResult]
|
|
572
610
|
) -> None:
|
|
@@ -600,40 +638,7 @@ def _write_csv_summary(
|
|
|
600
638
|
|
|
601
639
|
# Data rows
|
|
602
640
|
for result in results:
|
|
603
|
-
|
|
604
|
-
writer.writerow(
|
|
605
|
-
[
|
|
606
|
-
Path(result.video_path).name,
|
|
607
|
-
(
|
|
608
|
-
f"{result.metrics.ground_contact_time * 1000:.1f}"
|
|
609
|
-
if result.metrics.ground_contact_time
|
|
610
|
-
else "N/A"
|
|
611
|
-
),
|
|
612
|
-
(
|
|
613
|
-
f"{result.metrics.flight_time * 1000:.1f}"
|
|
614
|
-
if result.metrics.flight_time
|
|
615
|
-
else "N/A"
|
|
616
|
-
),
|
|
617
|
-
(
|
|
618
|
-
f"{result.metrics.jump_height:.3f}"
|
|
619
|
-
if result.metrics.jump_height
|
|
620
|
-
else "N/A"
|
|
621
|
-
),
|
|
622
|
-
f"{result.processing_time:.2f}",
|
|
623
|
-
"Success",
|
|
624
|
-
]
|
|
625
|
-
)
|
|
626
|
-
else:
|
|
627
|
-
writer.writerow(
|
|
628
|
-
[
|
|
629
|
-
Path(result.video_path).name,
|
|
630
|
-
"N/A",
|
|
631
|
-
"N/A",
|
|
632
|
-
"N/A",
|
|
633
|
-
f"{result.processing_time:.2f}",
|
|
634
|
-
f"Failed: {result.error}",
|
|
635
|
-
]
|
|
636
|
-
)
|
|
641
|
+
writer.writerow(_create_csv_row_from_result(result))
|
|
637
642
|
|
|
638
643
|
click.echo("CSV summary written successfully", err=True)
|
|
639
644
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kinemotion
|
|
3
|
-
Version: 0.12.
|
|
3
|
+
Version: 0.12.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
|
|
@@ -8,21 +8,21 @@ kinemotion/cmj/debug_overlay.py,sha256=D-y2FQKI01KY0WXFKTKg6p9Qj3AkXCE7xjau3Ais0
|
|
|
8
8
|
kinemotion/cmj/joint_angles.py,sha256=8ucpDGPvbt4iX3tx9eVxJEUv0laTm2Y58_--VzJCogE,9113
|
|
9
9
|
kinemotion/cmj/kinematics.py,sha256=Xl_PlC2OqMoA-zOc3SRB_GqI0AgLlJol5FTPe5J_qLc,7573
|
|
10
10
|
kinemotion/core/__init__.py,sha256=3yzDhb5PekDNjydqrs8aWGneUGJBt-lB0SoB_Y2FXqU,1010
|
|
11
|
-
kinemotion/core/auto_tuning.py,sha256=
|
|
11
|
+
kinemotion/core/auto_tuning.py,sha256=j6cul_qC6k0XyryCG93C1AWH2MKPj3UBMzuX02xaqfI,11235
|
|
12
12
|
kinemotion/core/cli_utils.py,sha256=Pq1JF7yvK1YbH0tOUWKjplthCbWsJQt4Lv7esPYH4FM,7254
|
|
13
13
|
kinemotion/core/debug_overlay_utils.py,sha256=TyUb5okv5qw8oeaX3jsUO_kpwf1NnaHEAOTm-8LwTno,4587
|
|
14
14
|
kinemotion/core/filtering.py,sha256=f-m-aA59e4WqE6u-9MA51wssu7rI-Y_7n1cG8IWdeRQ,11241
|
|
15
15
|
kinemotion/core/pose.py,sha256=ztemdZ_ysVVK3gbXabm8qS_dr1VfJX9KZjmcO-Z-iNE,8532
|
|
16
16
|
kinemotion/core/smoothing.py,sha256=C9GK3PAN16RpqJw2UWeVslSTJZEvALeVADjtnJnSF88,14240
|
|
17
|
-
kinemotion/core/video_io.py,sha256=
|
|
17
|
+
kinemotion/core/video_io.py,sha256=kH5FYPx3y3lFZ3ybdgxaZfKPdHJ37eqxSeAaZjyQnJk,6817
|
|
18
18
|
kinemotion/dropjump/__init__.py,sha256=yc1XiZ9vfo5h_n7PKVSiX2TTgaIfGL7Y7SkQtiDZj_E,838
|
|
19
|
-
kinemotion/dropjump/analysis.py,sha256=
|
|
20
|
-
kinemotion/dropjump/cli.py,sha256=
|
|
19
|
+
kinemotion/dropjump/analysis.py,sha256=xx5NWy6s0eb9BEyO_FByY1Ahunaoh3TyaTAxjlPrvxg,27153
|
|
20
|
+
kinemotion/dropjump/cli.py,sha256=Oni7gntysA6Zwb_ehsAnk6Ytd2ofUhN0yXVCsCsiris,21196
|
|
21
21
|
kinemotion/dropjump/debug_overlay.py,sha256=LkPw6ucb7beoYWS4L-Lvjs1KLCm5wAWDAfiznUeV2IQ,5668
|
|
22
22
|
kinemotion/dropjump/kinematics.py,sha256=txDxpDti3VJVctWGbe3aIrlIx83UY8-ynzlX01TOvTA,15577
|
|
23
23
|
kinemotion/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
|
-
kinemotion-0.12.
|
|
25
|
-
kinemotion-0.12.
|
|
26
|
-
kinemotion-0.12.
|
|
27
|
-
kinemotion-0.12.
|
|
28
|
-
kinemotion-0.12.
|
|
24
|
+
kinemotion-0.12.3.dist-info/METADATA,sha256=rbD5mEdFYxRAlAbmaobBcrnMaFh0mFd2L3GyHipRlGY,18990
|
|
25
|
+
kinemotion-0.12.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
26
|
+
kinemotion-0.12.3.dist-info/entry_points.txt,sha256=zaqnAnjLvcdrk1Qvj5nvXZCZ2gp0prS7it1zTJygcIY,50
|
|
27
|
+
kinemotion-0.12.3.dist-info/licenses/LICENSE,sha256=KZajvqsHw0NoOHOi2q0FZ4NBe9HdV6oey-IPYAtHXfg,1088
|
|
28
|
+
kinemotion-0.12.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|