lightning-pose-app 1.8.1a17__py3-none-any.whl → 1.8.1a18__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lightning-pose-app
3
- Version: 1.8.1a17
3
+ Version: 1.8.1a18
4
4
  Summary:
5
5
  Requires-Python: >=3.10
6
6
  Classifier: Programming Language :: Python :: 3
@@ -32,11 +32,11 @@ litpose_app/routes/configs.py,sha256=k2Zok69l7CqEoprVQxqV5YHjQucaC7w6oS2qoKpx0UU
32
32
  litpose_app/routes/extract_frames.py,sha256=HFh6oC8yJg6fa-Ob5CTIsqMgReGn6vdrqLe_5HRYM0U,4084
33
33
  litpose_app/routes/ffprobe.py,sha256=UVKDu4ghXkYNuzI-91KPMNpQukHV9Goeps7ex_lG_SU,5526
34
34
  litpose_app/routes/labeler/__init__.py,sha256=HyXqJ9xNdXA2jEoKrpy3-5AZrob7NbaedzyIqkmLBZA,1387
35
- litpose_app/routes/labeler/bundle_adjust.py,sha256=4CVnicyFJUYdTOKJHeoxGEqE02_TpmkcuB7uHPqofKQ,7414
35
+ litpose_app/routes/labeler/bundle_adjust.py,sha256=axIKqPGbOdDlxFBdR75XF4R5NWOd63QLRMAJ5eb3ATE,8049
36
36
  litpose_app/routes/labeler/find_label_files.py,sha256=6jZeJ7lemAaRNukLMSu74kGQ3W9c80Q7BzQHDG42GBk,5060
37
37
  litpose_app/routes/labeler/multiview_autolabel.py,sha256=BpAH_tySDfn8wK6C-H8iLnZm4NW4DAM6NBXMwAp0e2E,5434
38
38
  litpose_app/routes/labeler/save_mvframe.py,sha256=toE5xW93SqQmWNFHFASrtI2Akqm_ghDNigXYngMS7KM,6248
39
- litpose_app/routes/labeler/test_bundle_adjust.ipynb,sha256=CuFAsG1adFRlVLE1WViF9xrKW2NawEp4tK_1f0QV7w8,3469
39
+ litpose_app/routes/labeler/test_bundle_adjust.ipynb,sha256=8ZK24gNj1ylH4Kh850BaKPdIFpjvGoE9noMhI9_WyKg,11221
40
40
  litpose_app/routes/labeler/test_multiview_autolabel.ipynb,sha256=IAs4MIeGscObOdeZxNB4uiLftKU4cLmg-NSaCRSneH8,5275
41
41
  litpose_app/routes/labeler/write_multifile.py,sha256=qHDzinIxbf94hyIhOIPv3yZU1M18H6Qz3WBIV5R0M1g,1733
42
42
  litpose_app/routes/models.py,sha256=TjL96nSHAww_6nN96VjbbHjF83Lsmh2Vn1wXLo2NlPM,4383
@@ -57,6 +57,6 @@ litpose_app/utils/mv_label_file.py,sha256=VarVDSF0Rx90WThLZfArS7nmKiGcTmONNWjOGl
57
57
  litpose_app/utils/video/__init__.py,sha256=dSc-yVjXmn7ajNR7WgkCUUFdn9RLS2NBM2CjH0ePQXE,464
58
58
  litpose_app/utils/video/export_frames.py,sha256=kAIqjMarqTe51mJd3jk9xw4MotEUV1UfxtgTepyw2mc,1738
59
59
  litpose_app/utils/video/frame_selection.py,sha256=2x_F6Ez7WHDmvC-FeD6lO31kPvG5oRLrDQIhndoRT-g,4736
60
- lightning_pose_app-1.8.1a17.dist-info/METADATA,sha256=OZrMwrE2CTJrcBOhh0A0VIgoRqWywQrjDw5DSjjQVhg,1464
61
- lightning_pose_app-1.8.1a17.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
62
- lightning_pose_app-1.8.1a17.dist-info/RECORD,,
60
+ lightning_pose_app-1.8.1a18.dist-info/METADATA,sha256=t2lcCQhIsl0259HOR26Zy1SOTSCOUv4-be5qMB59oXw,1464
61
+ lightning_pose_app-1.8.1a18.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
62
+ lightning_pose_app-1.8.1a18.dist-info/RECORD,,
@@ -57,9 +57,8 @@ def bundle_adjust(
57
57
  return BundleAdjustResponse.model_validate(result)
58
58
 
59
59
 
60
-
61
60
  def _bundle_adjust_impl(
62
- request: BundleAdjustRequest, project_info: ProjectInfo, config: Config
61
+ request: BundleAdjustRequest, project_info: ProjectInfo, config: Config
63
62
  ):
64
63
  camera_group_toml_path = find_calibration_file(
65
64
  request.sessionKey, project_info, config
@@ -76,7 +75,7 @@ def _bundle_adjust_impl(
76
75
  parts = framePath.split("/")
77
76
  if len(parts) < 3:
78
77
  return False
79
- sessionViewNameWithDots = parts[-2] # e.g. 05272019_fly1_0_R1C24_Cam-A_rot-ccw-0.06_sec
78
+ sessionViewNameWithDots = parts[-2] # e.g. 05272019_fly1_0_R1C24_Cam-A_rot-ccw-0.06_sec
80
79
 
81
80
  def processPart(sessionViewName):
82
81
  """Mirrors frame.model.ts get autolabelSessionKey()"""
@@ -142,20 +141,31 @@ def _bundle_adjust_impl(
142
141
  for view in views:
143
142
  df = dfs_by_view[view]
144
143
  dfs_by_view[view] = df.loc[df.index.to_series().apply(is_of_current_session)]
144
+ if len(dfs_by_view[view]) == 0:
145
+ raise RuntimeError(
146
+ f"Insufficient frames found after filtering for session {request.sessionKey}. Possible error in session extraction logic.")
145
147
 
146
- # Normalize columns: x, y alternating.
148
+ # Remove rows with NaN coordinates
147
149
  for view in views:
148
150
  df = dfs_by_view[view]
151
+ picked_columns = [c for c in df.columns if c[2] in ("x", "y")]
152
+ dfs_by_view[view] = df.loc[:, picked_columns].dropna()
153
+ if len(dfs_by_view[view]) == 0:
154
+ raise RuntimeError(
155
+ f"Insufficient frames found after dropping NaN rows for session {request.sessionKey}.")
149
156
 
157
+ # Normalize columns: x, y alternating.
158
+ for view in views:
159
+ df = dfs_by_view[view]
150
160
  picked_columns = [c for c in df.columns if c[2] in ("x", "y")]
151
161
  assert len(picked_columns) % 2 == 0
152
162
  assert (
153
- picked_columns[::2][0][2] == "x"
154
- and len(set(list(map(lambda t: t[2], picked_columns[::2])))) == 1
163
+ picked_columns[::2][0][2] == "x"
164
+ and len(set(map(lambda t: t[2], picked_columns[::2]))) == 1
155
165
  )
156
166
  assert (
157
- picked_columns[1::2][0][2] == "y"
158
- and len(set(list(map(lambda t: t[2], picked_columns[1::2])))) == 1
167
+ picked_columns[1::2][0][2] == "y"
168
+ and len(set(map(lambda t: t[2], picked_columns[1::2]))) == 1
159
169
  )
160
170
  dfs_by_view[view] = df.loc[:, picked_columns]
161
171
 
@@ -179,9 +189,9 @@ def _bundle_adjust_impl(
179
189
 
180
190
  if target_path.exists():
181
191
  backup_path = (
182
- project_info.data_dir
183
- / config.CALIBRATION_BACKUPS_DIRNAME
184
- / target_path.with_suffix(f".{time.time_ns()}.toml").name
192
+ project_info.data_dir
193
+ / config.CALIBRATION_BACKUPS_DIRNAME
194
+ / target_path.with_suffix(f".{time.time_ns()}.toml").name
185
195
  )
186
196
  backup_path.parent.mkdir(parents=True, exist_ok=True)
187
197
  os.rename(target_path, backup_path)
@@ -3,8 +3,8 @@
3
3
  {
4
4
  "metadata": {
5
5
  "ExecuteTime": {
6
- "end_time": "2025-09-04T22:29:30.548302Z",
7
- "start_time": "2025-09-04T22:29:28.207273Z"
6
+ "end_time": "2025-10-21T18:12:15.867772Z",
7
+ "start_time": "2025-10-21T18:12:12.121614Z"
8
8
  }
9
9
  },
10
10
  "cell_type": "code",
@@ -19,13 +19,139 @@
19
19
  ],
20
20
  "id": "426cdcedf0207a92",
21
21
  "outputs": [],
22
- "execution_count": 1
22
+ "execution_count": 2
23
+ },
24
+ {
25
+ "metadata": {
26
+ "ExecuteTime": {
27
+ "end_time": "2025-10-21T18:24:57.793056Z",
28
+ "start_time": "2025-10-21T18:24:57.786487Z"
29
+ }
30
+ },
31
+ "cell_type": "code",
32
+ "source": [
33
+ "import numpy as np\n",
34
+ "# Assuming Camera and CameraGroup are in a file named 'cameras.py'\n",
35
+ "from aniposelib.cameras import CameraGroup, Camera\n",
36
+ "\n",
37
+ "\n",
38
+ "def demonstrate_triangulate():\n",
39
+ " # --- 1. Create dummy Camera objects ---\n",
40
+ " # For demonstration, we'll create two cameras with simple parameters.\n",
41
+ " # In a real scenario, these would come from calibration.\n",
42
+ "\n",
43
+ " # Camera 1 parameters\n",
44
+ " camera_matrix_1 = np.array([[1000, 0, 640],\n",
45
+ " [0, 1000, 480],\n",
46
+ " [0, 0, 1]], dtype=np.float64)\n",
47
+ " dist_coeffs_1 = np.array([0, 0, 0, 0, 0], dtype=np.float64) # No distortion for simplicity\n",
48
+ " rvec_1 = np.array([[0.], [0.], [0.]], dtype=np.float64) # No rotation\n",
49
+ " tvec_1 = np.array([[0.], [0.], [0.]], dtype=np.float64) # Origin at world origin\n",
50
+ " cam1 = Camera(name='cam1',\n",
51
+ " matrix=camera_matrix_1,\n",
52
+ " dist=dist_coeffs_1,\n",
53
+ " rvec=rvec_1,\n",
54
+ " tvec=tvec_1,\n",
55
+ " size=(1280, 960)) # Image width, height\n",
56
+ "\n",
57
+ " # Camera 2 parameters - shifted and slightly rotated relative to Camera 1\n",
58
+ " camera_matrix_2 = np.array([[1050, 0, 650],\n",
59
+ " [0, 1050, 470],\n",
60
+ " [0, 0, 1]], dtype=np.float64)\n",
61
+ " dist_coeffs_2 = np.array([0.01, -0.02, 0, 0, 0], dtype=np.float64) # Some distortion\n",
62
+ " rvec_2 = np.array([[0.05], [-0.1], [0.03]], dtype=np.float64) # Small rotation\n",
63
+ " tvec_2 = np.array([[0.5], [0.1], [-0.2]], dtype=np.float64) # Translated from origin\n",
64
+ " cam2 = Camera(name='cam2',\n",
65
+ " matrix=camera_matrix_2,\n",
66
+ " dist=dist_coeffs_2,\n",
67
+ " rvec=rvec_2,\n",
68
+ " tvec=tvec_2,\n",
69
+ " size=(1280, 960))\n",
70
+ "\n",
71
+ " print(\"Cameras initialized.\")\n",
72
+ " print(f\"Camera 1 name: {cam1.get_name()}\")\n",
73
+ " print(f\"Camera 2 name: {cam2.get_name()}\")\n",
74
+ "\n",
75
+ " # --- 2. Create a CameraGroup ---\n",
76
+ " camera_group = CameraGroup(cameras=[cam1, cam2])\n",
77
+ " print(\"\\nCameraGroup created with 2 cameras.\")\n",
78
+ "\n",
79
+ " # --- 3. Define a sample 3D point (world coordinates) ---\n",
80
+ " # We will try to triangulate this point\n",
81
+ " true_3d_point = np.array([[1.0, 2.0, 5.0]], dtype=np.float64) # Shape (1, 3)\n",
82
+ "\n",
83
+ " print(f\"\\nTrue 3D point in world coordinates: {true_3d_point}\")\n",
84
+ "\n",
85
+ " # --- 4. Project the 3D point onto each camera to get 2D observations ---\n",
86
+ " # The 'project' method of CameraGroup expects points for each camera.\n",
87
+ " # The output of Camera.project is (N, 2) for N points.\n",
88
+ " points_2d_cam1 = cam1.project(true_3d_point)\n",
89
+ " points_2d_cam2 = cam2.project(true_3d_point)\n",
90
+ "\n",
91
+ " # Reshape the points to match expected format (num_cameras, num_points, 2)\n",
92
+ " points_2d_cam1 = points_2d_cam1.reshape(1, 2)\n",
93
+ " points_2d_cam2 = points_2d_cam2.reshape(1, 2)\n",
94
+ "\n",
95
+ " # TEST NAN\n",
96
+ " #points_2d_cam1[0, 0] = np.nan\n",
97
+ "\n",
98
+ " points_for_triangulation = np.stack([points_2d_cam1, points_2d_cam2])\n",
99
+ "\n",
100
+ " print(f\"\\nProjected 2D point for Camera 1:\\n{points_2d_cam1}\")\n",
101
+ " print(f\"Projected 2D point for Camera 2:\\n{points_2d_cam2}\")\n",
102
+ " print(f\"Shape of points for triangulation: {points_for_triangulation.shape}\")\n",
103
+ "\n",
104
+ " # --- 5. Use CameraGroup.triangulate to reconstruct the 3D point ---\n",
105
+ " # We'll set undistort=True because cam2 has distortion coefficients.\n",
106
+ " # The triangulate method returns a numpy array of shape (num_points, 3).\n",
107
+ " triangulated_points = camera_group.triangulate(points_for_triangulation, undistort=True)\n",
108
+ "\n",
109
+ " print(f\"\\nTriangulated 3D points:\\n{triangulated_points}\")\n",
110
+ "\n",
111
+ " # Compare the triangulated point with the original 3D point\n",
112
+ " if triangulated_points.shape[0] > 0:\n",
113
+ " reconstructed_point = triangulated_points[0]\n",
114
+ " error = np.linalg.norm(reconstructed_point - true_3d_point[0])\n",
115
+ " print(f\"\\nOriginal 3D point: {true_3d_point[0]}\")\n",
116
+ " print(f\"Reconstructed 3D point: {reconstructed_point}\")\n",
117
+ " print(f\"Reconstruction error (L2 norm): {error:.6f}\")\n"
118
+ ],
119
+ "id": "d57c636c870d94d4",
120
+ "outputs": [
121
+ {
122
+ "name": "stdout",
123
+ "output_type": "stream",
124
+ "text": [
125
+ "Cameras initialized.\n",
126
+ "Camera 1 name: cam1\n",
127
+ "Camera 2 name: cam2\n",
128
+ "\n",
129
+ "CameraGroup created with 2 cameras.\n",
130
+ "\n",
131
+ "True 3D point in world coordinates: [[1. 2. 5.]]\n",
132
+ "\n",
133
+ "Projected 2D point for Camera 1:\n",
134
+ "[[ nan 880.]]\n",
135
+ "Projected 2D point for Camera 2:\n",
136
+ "[[847.82290042 865.22061803]]\n",
137
+ "Shape of points for triangulation: (2, 1, 2)\n",
138
+ "\n",
139
+ "Triangulated 3D points:\n",
140
+ "[[nan nan nan]]\n",
141
+ "\n",
142
+ "Original 3D point: [1. 2. 5.]\n",
143
+ "Reconstructed 3D point: [nan nan nan]\n",
144
+ "Reconstruction error (L2 norm): nan\n"
145
+ ]
146
+ }
147
+ ],
148
+ "execution_count": 10
23
149
  },
24
150
  {
25
151
  "metadata": {
26
152
  "ExecuteTime": {
27
- "end_time": "2025-09-04T22:29:36.203750Z",
28
- "start_time": "2025-09-04T22:29:30.552170Z"
153
+ "end_time": "2025-10-21T18:12:16.051120Z",
154
+ "start_time": "2025-10-21T18:12:15.877447Z"
29
155
  }
30
156
  },
31
157
  "cell_type": "code",
@@ -47,46 +173,19 @@
47
173
  "id": "37a1ad52fe6211ac",
48
174
  "outputs": [
49
175
  {
50
- "name": "stdout",
51
- "output_type": "stream",
52
- "text": [
53
- "Skipping /home/ksikka/work/LightningPoseData/fly-anipose/calibrations.csv because no view name was present.\n",
54
- "Skipping /home/ksikka/work/LightningPoseData/fly-anipose/calibrations_new.csv because no view name was present.\n",
55
- "/home/ksikka/work/LightningPoseData/fly-anipose/CollectedData_Cam-A_new.csv\n",
56
- "/home/ksikka/work/LightningPoseData/fly-anipose/CollectedData_Cam-B_new.csv\n",
57
- "/home/ksikka/work/LightningPoseData/fly-anipose/CollectedData_Cam-C_new.csv\n",
58
- "/home/ksikka/work/LightningPoseData/fly-anipose/CollectedData_Cam-D_new.csv\n",
59
- "/home/ksikka/work/LightningPoseData/fly-anipose/CollectedData_Cam-E_new.csv\n",
60
- "/home/ksikka/work/LightningPoseData/fly-anipose/CollectedData_Cam-F_new.csv\n",
61
- "/home/ksikka/work/LightningPoseData/fly-anipose/CollectedData_Cam-A.csv\n",
62
- "/home/ksikka/work/LightningPoseData/fly-anipose/CollectedData_Cam-B.csv\n",
63
- "/home/ksikka/work/LightningPoseData/fly-anipose/CollectedData_Cam-C.csv\n",
64
- "/home/ksikka/work/LightningPoseData/fly-anipose/CollectedData_Cam-D.csv\n",
65
- "/home/ksikka/work/LightningPoseData/fly-anipose/CollectedData_Cam-E.csv\n",
66
- "/home/ksikka/work/LightningPoseData/fly-anipose/CollectedData_Cam-F.csv\n"
176
+ "ename": "ValidationError",
177
+ "evalue": "1 validation error for BundleAdjustRequest\nmvlabelfile\n Field required [type=missing, input_value={'sessionKey': '05272019_...1C24__rot-ccw-0.06_sec'}, input_type=dict]\n For further information visit https://errors.pydantic.dev/2.11/v/missing",
178
+ "output_type": "error",
179
+ "traceback": [
180
+ "\u001B[31m---------------------------------------------------------------------------\u001B[39m",
181
+ "\u001B[31mValidationError\u001B[39m Traceback (most recent call last)",
182
+ "\u001B[36mCell\u001B[39m\u001B[36m \u001B[39m\u001B[32mIn[3]\u001B[39m\u001B[32m, line 4\u001B[39m\n\u001B[32m 1\u001B[39m \u001B[38;5;28;01mfrom\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[34;01mcollections\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[38;5;28;01mimport\u001B[39;00m defaultdict\n\u001B[32m 2\u001B[39m \u001B[38;5;28;01mfrom\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[34;01mpathlib\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[38;5;28;01mimport\u001B[39;00m Path\n\u001B[32m----> \u001B[39m\u001B[32m4\u001B[39m request = \u001B[43mBundleAdjustRequest\u001B[49m\u001B[43m(\u001B[49m\u001B[43msessionKey\u001B[49m\u001B[43m=\u001B[49m\u001B[33;43m'\u001B[39;49m\u001B[33;43m05272019_fly1_0_R1C24__rot-ccw-0.06_sec\u001B[39;49m\u001B[33;43m'\u001B[39;49m\u001B[43m)\u001B[49m\n\u001B[32m 6\u001B[39m \u001B[38;5;28;01mfrom\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[34;01mlitpose_app\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[38;5;28;01mimport\u001B[39;00m deps\n\u001B[32m 7\u001B[39m config = deps.config()\n",
183
+ "\u001B[36mFile \u001B[39m\u001B[32m~/miniforge3/envs/lpv/lib/python3.12/site-packages/pydantic/main.py:253\u001B[39m, in \u001B[36mBaseModel.__init__\u001B[39m\u001B[34m(self, **data)\u001B[39m\n\u001B[32m 251\u001B[39m \u001B[38;5;66;03m# `__tracebackhide__` tells pytest and some other tools to omit this function from tracebacks\u001B[39;00m\n\u001B[32m 252\u001B[39m __tracebackhide__ = \u001B[38;5;28;01mTrue\u001B[39;00m\n\u001B[32m--> \u001B[39m\u001B[32m253\u001B[39m validated_self = \u001B[38;5;28;43mself\u001B[39;49m\u001B[43m.\u001B[49m\u001B[43m__pydantic_validator__\u001B[49m\u001B[43m.\u001B[49m\u001B[43mvalidate_python\u001B[49m\u001B[43m(\u001B[49m\u001B[43mdata\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mself_instance\u001B[49m\u001B[43m=\u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[43m)\u001B[49m\n\u001B[32m 254\u001B[39m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28mself\u001B[39m \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m validated_self:\n\u001B[32m 255\u001B[39m warnings.warn(\n\u001B[32m 256\u001B[39m \u001B[33m'\u001B[39m\u001B[33mA custom validator is returning a value other than `self`.\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m'\u001B[39m\n\u001B[32m 257\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mReturning anything other than `self` from a top level model validator isn\u001B[39m\u001B[33m'\u001B[39m\u001B[33mt supported when validating via `__init__`.\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m\n\u001B[32m 258\u001B[39m \u001B[33m'\u001B[39m\u001B[33mSee the `model_validator` docs (https://docs.pydantic.dev/latest/concepts/validators/#model-validators) for more details.\u001B[39m\u001B[33m'\u001B[39m,\n\u001B[32m 259\u001B[39m stacklevel=\u001B[32m2\u001B[39m,\n\u001B[32m 260\u001B[39m )\n",
184
+ "\u001B[31mValidationError\u001B[39m: 1 validation error for BundleAdjustRequest\nmvlabelfile\n Field required [type=missing, input_value={'sessionKey': '05272019_...1C24__rot-ccw-0.06_sec'}, input_type=dict]\n For further information visit https://errors.pydantic.dev/2.11/v/missing"
67
185
  ]
68
- },
69
- {
70
- "name": "stderr",
71
- "output_type": "stream",
72
- "text": [
73
- "2025-09-04 18:29:31,194 - INFO - finding looplift candidates\n",
74
- "2025-09-04 18:29:32,323 - INFO - finding looplift candidates\n",
75
- "2025-09-04 18:29:32,493 - INFO - finding looplift candidates\n"
76
- ]
77
- },
78
- {
79
- "data": {
80
- "text/plain": [
81
- "'ok'"
82
- ]
83
- },
84
- "execution_count": 2,
85
- "metadata": {},
86
- "output_type": "execute_result"
87
186
  }
88
187
  ],
89
- "execution_count": 2
188
+ "execution_count": 3
90
189
  }
91
190
  ],
92
191
  "metadata": {