|
7 | 7 | import logging |
8 | 8 | import math |
9 | 9 | from pathlib import Path |
10 | | -from typing import Dict, List, NamedTuple, Optional, Sequence, Tuple, Union |
| 10 | +from typing import ( |
| 11 | + Any, |
| 12 | + Dict, |
| 13 | + List, |
| 14 | + NamedTuple, |
| 15 | + Optional, |
| 16 | + Sequence, |
| 17 | + Tuple, |
| 18 | + TypeAlias, |
| 19 | + Union, |
| 20 | +) |
11 | 21 |
|
12 | 22 | import h5py |
13 | 23 | import numpy as np |
14 | 24 |
|
| 25 | +Self: TypeAlias = Any |
| 26 | + |
15 | 27 | try: |
16 | 28 | import pycolmap |
17 | 29 | except ImportError: |
@@ -199,6 +211,44 @@ def angular_error(self, other: "Pose", agg="max") -> torch.Tensor: |
199 | 211 | "dt": lambda x: x[..., 1], |
200 | 212 | }[agg](self.angular_drdt(other, stack=True)) |
201 | 213 |
|
| 214 | + def _opening_angle(self, return_cos: bool = False) -> float: |
| 215 | + v0 = torch.zeros_like(self.t) |
| 216 | + v0[..., -1] = 1.0 |
| 217 | + v1 = self.R[..., 2, :] |
| 218 | + v01 = self.inv().t |
| 219 | + v01 = v01 / v01.norm() |
| 220 | + n = v0.cross(v01, dim=-1) |
| 221 | + n = n / n.norm() |
| 222 | + cos = n.dot(v1) |
| 223 | + return cos if return_cos else (90 - torch.acos(cos) * 180.0 / 3.1415).abs() |
| 224 | + |
| 225 | + def opening_angle(self, return_cos: bool = False) -> float: |
| 226 | + return 0.5 * ( |
| 227 | + self._opening_angle(return_cos=return_cos) |
| 228 | + + self.inv()._opening_angle(return_cos=return_cos) |
| 229 | + ) |
| 230 | + |
| 231 | + @classmethod |
| 232 | + def exp(cls, delta: torch.Tensor): |
| 233 | + dr, dt = delta.split(3, dim=-1) |
| 234 | + return cls.from_aa(dr, dt) |
| 235 | + |
| 236 | + def manifold(self, delta: torch.Tensor | None = None) -> torch.Tensor: |
| 237 | + if delta is None: |
| 238 | + delta = torch.zeros_like(self._data[..., :6]) |
| 239 | + return delta |
| 240 | + |
| 241 | + def update(self, delta: torch.Tensor | Self, inplace: bool = False) -> "Pose": |
| 242 | + if not isinstance(delta, self.__class__): |
| 243 | + delta = Pose.exp(delta) |
| 244 | + |
| 245 | + updated_pose = delta @ self |
| 246 | + if inplace: |
| 247 | + self._data = updated_pose._data |
| 248 | + return self |
| 249 | + else: |
| 250 | + return updated_pose |
| 251 | + |
202 | 252 | def __repr__(self): |
203 | 253 | return f"Pose: {self.shape} {self.dtype} {self.device}" |
204 | 254 |
|
@@ -377,13 +427,32 @@ def J_world2image(self, p3d: torch.Tensor): |
377 | 427 | J = self.J_denormalize() @ self.J_distort(p2d_dist) @ self.J_project(p3d) |
378 | 428 | return J, valid |
379 | 429 |
|
380 | | - @tensor.autocast |
381 | | - def image2cam(self, p2d: torch.Tensor) -> torch.Tensor: |
| 430 | + def image2cam(self, p2d: torch.Tensor, homogeneous: bool = True) -> torch.Tensor: |
382 | 431 | """Convert 2D pixel corrdinates to 3D points with z=1""" |
383 | 432 | assert self._data.shape |
384 | 433 | p2d = self.normalize(p2d) |
385 | 434 | # iterative undistortion |
386 | | - return gtr.to_homogeneous(p2d) |
| 435 | + if homogeneous: |
| 436 | + return gtr.to_homogeneous(p2d) |
| 437 | + else: |
| 438 | + return p2d |
| 439 | + |
| 440 | + def manifold(self, delta: torch.Tensor | None = None) -> torch.Tensor: |
| 441 | + if delta is None: |
| 442 | + delta = torch.zeros_like(self._data[..., 2:4]) |
| 443 | + return delta |
| 444 | + |
| 445 | + def update(self, delta: torch.Tensor | Self, inplace=False) -> "Camera": |
| 446 | + if isinstance(delta, self.__class__): |
| 447 | + delta = delta._data[..., 2:4] |
| 448 | + |
| 449 | + if inplace: |
| 450 | + self._data[..., 2:4] += delta |
| 451 | + return self |
| 452 | + else: |
| 453 | + cam_copy = self.clone() |
| 454 | + cam_copy._data[..., 2:4] += delta |
| 455 | + return cam_copy |
387 | 456 |
|
388 | 457 | def to_cameradict(self, camera_model: Optional[str] = None) -> List[Dict]: |
389 | 458 | data = self._data.clone() |
|
0 commit comments