posetail 0.1.0__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.
Files changed (39) hide show
  1. posetail-0.1.0/LICENSE +28 -0
  2. posetail-0.1.0/PKG-INFO +36 -0
  3. posetail-0.1.0/README.md +1 -0
  4. posetail-0.1.0/inference_utils.py +256 -0
  5. posetail-0.1.0/inference_video.py +1312 -0
  6. posetail-0.1.0/posetail/__init__.py +13 -0
  7. posetail-0.1.0/posetail/datasets/__init__.py +0 -0
  8. posetail-0.1.0/posetail/datasets/inference_dataset.py +445 -0
  9. posetail-0.1.0/posetail/datasets/posetail_dataset.py +1605 -0
  10. posetail-0.1.0/posetail/datasets/scorer_corruption.py +568 -0
  11. posetail-0.1.0/posetail/datasets/utils.py +118 -0
  12. posetail-0.1.0/posetail/posetail/__init__.py +0 -0
  13. posetail-0.1.0/posetail/posetail/cube.py +661 -0
  14. posetail-0.1.0/posetail/posetail/encoder_decoder.py +1497 -0
  15. posetail-0.1.0/posetail/posetail/eval_metrics.py +535 -0
  16. posetail-0.1.0/posetail/posetail/losses.py +1165 -0
  17. posetail-0.1.0/posetail/posetail/losses_scorer.py +109 -0
  18. posetail-0.1.0/posetail/posetail/muon.py +68 -0
  19. posetail-0.1.0/posetail/posetail/networks.py +1049 -0
  20. posetail-0.1.0/posetail/posetail/pscan.py +109 -0
  21. posetail-0.1.0/posetail/posetail/scorer_encoder.py +292 -0
  22. posetail-0.1.0/posetail/posetail/tapnext.py +290 -0
  23. posetail-0.1.0/posetail/posetail/tapnext_lru_modules.py +564 -0
  24. posetail-0.1.0/posetail/posetail/tracker.py +1076 -0
  25. posetail-0.1.0/posetail/posetail/tracker_encoder.py +990 -0
  26. posetail-0.1.0/posetail/posetail/tracker_tapnext.py +673 -0
  27. posetail-0.1.0/posetail/posetail/transformer.py +270 -0
  28. posetail-0.1.0/posetail/posetail/utils.py +161 -0
  29. posetail-0.1.0/posetail/posetail/vjepa2.py +961 -0
  30. posetail-0.1.0/posetail.egg-info/PKG-INFO +36 -0
  31. posetail-0.1.0/posetail.egg-info/SOURCES.txt +37 -0
  32. posetail-0.1.0/posetail.egg-info/dependency_links.txt +1 -0
  33. posetail-0.1.0/posetail.egg-info/requires.txt +19 -0
  34. posetail-0.1.0/posetail.egg-info/top_level.txt +4 -0
  35. posetail-0.1.0/pyproject.toml +97 -0
  36. posetail-0.1.0/setup.cfg +4 -0
  37. posetail-0.1.0/setup.py +6 -0
  38. posetail-0.1.0/tests/test_grid_decode_space.py +81 -0
  39. posetail-0.1.0/train_utils.py +1110 -0
posetail-0.1.0/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2025, Allen Institute for Neural Dynamics
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,36 @@
1
+ Metadata-Version: 2.4
2
+ Name: posetail
3
+ Version: 0.1.0
4
+ Summary: a model for tracking 2d or 3d animal pose through time
5
+ License: BSD-3-Clause
6
+ Project-URL: Repository, https://github.com/AllenNeuralDynamics/posetail
7
+ Keywords: 3d point tracking,2d point tracking,multi-view tracking,pose estimation
8
+ Classifier: Intended Audience :: Science/Research
9
+ Classifier: Topic :: Scientific/Engineering :: Image Recognition
10
+ Classifier: License :: OSI Approved :: BSD License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Requires-Python: >=3.11
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: aniposelib>=0.7.0
16
+ Requires-Dist: easydict==1.13
17
+ Requires-Dist: einops==0.7.0
18
+ Requires-Dist: opencv-python==4.9.0.80
19
+ Requires-Dist: toml==0.10.2
20
+ Requires-Dist: wandb==0.19.5
21
+ Requires-Dist: lightning<3,>=2.5.6
22
+ Requires-Dist: rerun-sdk[notebook]<=0.22.1
23
+ Requires-Dist: numpy==1.26
24
+ Requires-Dist: imgaug
25
+ Requires-Dist: imagecorruptions
26
+ Requires-Dist: setuptools<81
27
+ Requires-Dist: schedulefree
28
+ Requires-Dist: decord
29
+ Requires-Dist: tqdm
30
+ Requires-Dist: fastapi>=0.111
31
+ Requires-Dist: uvicorn[standard]>=0.29
32
+ Requires-Dist: python-multipart>=0.0.9
33
+ Requires-Dist: requests>=2.31
34
+ Dynamic: license-file
35
+
36
+ # posetail
@@ -0,0 +1 @@
1
+ # posetail
@@ -0,0 +1,256 @@
1
+ import os
2
+ import cv2
3
+ import glob
4
+
5
+ import torch
6
+
7
+ import numpy as np
8
+
9
+ from collections import defaultdict
10
+
11
+ from posetail.datasets.utils import get_dirs
12
+ from train_utils import *
13
+
14
+
15
+
16
+ def get_checkpoint(wandb_prefix, run_id, checkpoint = None):
17
+
18
+ if checkpoint is not None:
19
+ checkpoint_fmt = str(checkpoint).zfill(8)
20
+ checkpoint_path = os.path.join(
21
+ wandb_prefix, run_id, 'files', 'checkpoints',
22
+ f'checkpoint_{checkpoint_fmt}.pth')
23
+
24
+ else:
25
+ checkpoints = sorted(glob.glob(
26
+ os.path.join(wandb_prefix, run_id, 'files', 'checkpoints', '*.pth')))
27
+ checkpoint_path = checkpoints[-1]
28
+
29
+ return checkpoint_path
30
+
31
+
32
+ def load_predictions(data_path, device):
33
+
34
+ data = np.load(data_path)
35
+
36
+ coords_pred = torch.from_numpy(data['coords_pred']).to(device)
37
+ vis_pred = torch.from_numpy(data['vis_pred']).to(device)
38
+ conf_pred = torch.from_numpy(data['conf_pred']).to(device)
39
+
40
+ coords_true = torch.from_numpy(data['coords_true']).to(device)
41
+ vis_true = torch.from_numpy(data['vis_true']).to(device)
42
+
43
+ fnums = torch.from_numpy(data['fnums']).to(device)
44
+ video_path = ''.join(data['video_path'])
45
+
46
+ return coords_pred, vis_pred, conf_pred, coords_true, vis_true, fnums, video_path
47
+
48
+
49
+ def combine_predictions(prefix):
50
+
51
+ # traverse results for a particular dataset
52
+ for session in get_dirs(prefix):
53
+
54
+ session_path = os.path.join(prefix, session)
55
+
56
+ for trial in get_dirs(session_path):
57
+
58
+ trial_path = os.path.join(session_path, trial)
59
+
60
+ # skip if there are no npz prediction files
61
+ prediction_paths = sorted(glob.glob(os.path.join(trial_path, 'predictions', 'predictions_*.npz')))
62
+ if len(prediction_paths) == 0:
63
+ print(f'skipping... no prediction paths found at {trial_path}')
64
+ continue
65
+
66
+ data = [np.load(p) for p in prediction_paths]
67
+
68
+ # extract metadata
69
+ keys_to_exclude = ['coords_pred', 'vis_pred', 'conf_pred',
70
+ 'coords_true', 'vis_true', 'fnums']
71
+
72
+ sample_info = {k: data[0][k] for k in data[0].keys() if k not in keys_to_exclude}
73
+
74
+ # combine predictions from each consecutive time period
75
+ coords_pred = np.concatenate([d['coords_pred'] for d in data], axis = 0)
76
+
77
+ vis_pred = np.concatenate([d['vis_pred'] for d in data], axis = 0)
78
+ conf_pred = np.concatenate([d['conf_pred']for d in data], axis = 0)
79
+ coords_true = np.concatenate([d['coords_true'] for d in data], axis = 0)
80
+ vis_true = np.concatenate([d['vis_true'] for d in data], axis = 0)
81
+ fnums = np.concatenate([d['fnums'] for d in data], axis = 0)
82
+
83
+ results = {
84
+ 'coords_pred': coords_pred,
85
+ 'vis_pred': vis_pred,
86
+ 'conf_pred': conf_pred,
87
+ 'coords_true': coords_true,
88
+ 'vis_true': vis_true,
89
+ 'fnums': fnums,
90
+ }
91
+
92
+ results.update(sample_info)
93
+
94
+ # save combined data
95
+ predictions_fname = os.path.join(prefix, session, trial, f'predictions.npz')
96
+ np.savez(predictions_fname, **results)
97
+ print(f'predictions saved to {predictions_fname}')
98
+
99
+
100
+ def predict_on_dataset_3d(model, dataloader, outpath, device,
101
+ max_kpts = 1000, debug_ix = None):
102
+
103
+ torch.set_float32_matmul_precision('high')
104
+ model.eval()
105
+
106
+ for j, batch in enumerate(dataloader):
107
+
108
+ if debug_ix and j == debug_ix:
109
+ break
110
+
111
+ views = [view.to(device) for view in batch.views]
112
+ coords = batch.coords.to(device)
113
+ vis = batch.vis
114
+ fnums = batch.fnums.cpu().numpy()
115
+ cgroup = batch.cgroup
116
+ sample_info = batch.sample_info
117
+
118
+ # fallback if visibilities are not provided
119
+ if vis is None:
120
+ vis = get_vis_true(coords)
121
+
122
+ if cgroup:
123
+ cgroup = [dict_to_device(cam_dict, device) for cam_dict in cgroup]
124
+
125
+ # can do multiple passes if there are a lot of keypoints to predict
126
+ # (helps reduce memory)
127
+ n_passes = np.ceil(coords.shape[2] / max_kpts).astype(int)
128
+ coords_pred = []
129
+ vis_pred = []
130
+ conf_pred = []
131
+ coords_true = []
132
+ vis_true = []
133
+
134
+ for i in range(n_passes):
135
+
136
+ coords_subset = coords[:, :, i * max_kpts : i * max_kpts + max_kpts, :]
137
+ vis_subset = vis[:, :, i * max_kpts : i * max_kpts + max_kpts, :]
138
+
139
+ # mask NaNs, don't want to pass in coords that are NaN in the first frame
140
+ coords_first = coords_subset[:, 0, :, :] # B, N, R
141
+ valid_mask = ~torch.isnan(coords_first).any(dim = -1) # B, N
142
+ coords_valid = coords_first[:, valid_mask[0], :] # B, n, R
143
+
144
+ # get model predictions given coords in the first frame
145
+ with torch.no_grad():
146
+
147
+ outputs = model(
148
+ views = views,
149
+ coords = coords_valid,
150
+ camera_group = cgroup
151
+ )
152
+
153
+ # populate valid predictions in side of coords
154
+ B, S, N, R = coords_subset.shape
155
+ output_coords_valid = torch.full((B, S, N, R), float('nan'),
156
+ device = coords_subset.device, dtype = coords_subset.dtype)
157
+ output_vis_valid = torch.full((B, S, N, 1), float('nan'),
158
+ device = coords_subset.device, dtype = coords_subset.dtype)
159
+ output_conf_valid = torch.full((B, S, N, 1), float('nan'),
160
+ device = coords_subset.device, dtype = coords_subset.dtype)
161
+
162
+ output_coords_valid[:, :, valid_mask[0], :] = outputs['coords_pred']
163
+ output_vis_valid[:, :, valid_mask[0], :] = outputs['vis_pred']
164
+ output_conf_valid[:, :, valid_mask[0], :] = outputs['conf_pred']
165
+
166
+ print('true', coords_subset[:, :, 0, :])
167
+ print('pred', outputs['coords_pred'][:, :, 0, :])
168
+
169
+ coords_pred.append(torch.squeeze(outputs['coords_pred'], dim = 0).cpu().numpy())
170
+ vis_pred.append(torch.squeeze(outputs['vis_pred'], dim = 0).cpu().numpy())
171
+ conf_pred.append(torch.squeeze(outputs['conf_pred'], dim = 0).cpu().numpy())
172
+ coords_true.append(torch.squeeze(coords_subset, dim = 0).cpu().numpy())
173
+ vis_true.append(torch.squeeze(vis_subset, dim = 0).cpu().numpy())
174
+
175
+ results = {
176
+ 'coords_pred': np.concatenate(coords_pred, axis = 1),
177
+ 'vis_pred': np.concatenate(vis_pred, axis = 1),
178
+ 'conf_pred': np.concatenate(conf_pred, axis = 1),
179
+ 'coords_true': np.concatenate(coords_true, axis = 1),
180
+ 'vis_true': np.concatenate(vis_true, axis = 1),
181
+ }
182
+ results.update({'fnums': fnums})
183
+
184
+ keys_to_exclude = ['fnums']
185
+ if sample_info.subject_ids is not None:
186
+ results.update({'subject_ids': sample_info.subject_ids})
187
+ else:
188
+ keys_to_exclude.append('subject_ids')
189
+
190
+ results.update({k: sample_info[k] for k in sample_info.keys() if k not in keys_to_exclude})
191
+
192
+ # save predictions
193
+ start_ix = str(results['start_ix']).zfill(8)
194
+ predictions_outpath = os.path.join(
195
+ outpath, results['session'], results['trial'], 'predictions')
196
+ os.makedirs(predictions_outpath, exist_ok = True)
197
+ predictions_fname = os.path.join(predictions_outpath, f'predictions_{start_ix}.npz')
198
+ np.savez(predictions_fname, **results)
199
+ print(f'predictions saved to {predictions_fname}')
200
+
201
+ return outpath
202
+
203
+
204
+ def generate_video_2d(video_path, results_path, outpath, run_id, scale, device):
205
+ # NOTE: deprecated for now
206
+ # TODO: get camera group and project coords
207
+ (coords_pred, vis_pred, conf_pred, coords_true,
208
+ vis_true, fnums, video_path) = load_predictions(results_path, device)
209
+
210
+ coords_true = coords_true.cpu().numpy().astype(int)
211
+ coords_true[..., 0] = coords_true[..., 0] * scale[0]
212
+ coords_true[..., 1] = coords_true[..., 1] * scale[1]
213
+
214
+ coords_pred = coords_pred.cpu().numpy().astype(int)
215
+ coords_pred[..., 0] = coords_pred[..., 0] * scale[0]
216
+ coords_pred[..., 1] = coords_pred[..., 1] * scale[1]
217
+
218
+ cap = cv2.VideoCapture(video_path)
219
+ frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
220
+ frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
221
+ fps = 30.0 # cap.get(cv2.CAP_PROP_FPS)
222
+
223
+ fourcc = cv2.VideoWriter_fourcc(*'mp4v')
224
+ runid = run_id.split('-')[-1]
225
+ video_name = os.path.splitext(os.path.basename(video_path))[0]
226
+ video_outpath = os.path.join(outpath, f'{video_name}_{runid}.mp4')
227
+ out = cv2.VideoWriter(video_outpath, fourcc, fps, (frame_width, frame_height))
228
+
229
+ i = 0
230
+ j = 0
231
+ ret = True
232
+
233
+ while ret:
234
+
235
+ ret, frame = cap.read()
236
+
237
+ if not ret:
238
+ break
239
+
240
+ if i not in fnums:
241
+ out.write(frame)
242
+
243
+ else:
244
+ for coord_true, coord_pred in zip(coords_true[j], coords_pred[j]):
245
+ cv2.circle(frame, tuple(coord_true), 5, (0, 255, 0), -1)
246
+ cv2.circle(frame, tuple(coord_pred), 5, (0, 0, 255), -1)
247
+
248
+ out.write(frame)
249
+ j += 1
250
+
251
+ i += 1
252
+
253
+ cap.release()
254
+ out.release()
255
+
256
+ return video_outpath