(self, pano_name: str)
| 218 | self._rays_in_cam: npt.NDArray[np.floating] | None = None |
| 219 | |
| 220 | def process(self, pano_name: str) -> None: |
| 221 | pano_path = self.pano_image_dir / pano_name |
| 222 | try: |
| 223 | pano_pil_image = PIL.Image.open(pano_path) |
| 224 | except PIL.Image.UnidentifiedImageError: |
| 225 | logging.info(f"Skipping file {pano_path} as it cannot be read.") |
| 226 | return |
| 227 | |
| 228 | pano_exif = pano_pil_image.getexif() |
| 229 | gpsonly_exif = PIL.Image.Exif() |
| 230 | gpsonly_exif[PIL.ExifTags.IFD.GPSInfo] = pano_exif.get_ifd( |
| 231 | PIL.ExifTags.IFD.GPSInfo |
| 232 | ) |
| 233 | |
| 234 | pano_image = np.asarray(pano_pil_image) |
| 235 | pano_height, pano_width, *_ = pano_image.shape |
| 236 | if pano_width != pano_height * 2: |
| 237 | raise ValueError("Only 360° panoramas are supported.") |
| 238 | |
| 239 | with self._lock: |
| 240 | if self._camera is None: # First image, precompute rays once. |
| 241 | self._camera = create_virtual_camera( |
| 242 | pano_width=pano_width, |
| 243 | pano_height=pano_height, |
| 244 | hfov_deg=self.render_options.hfov_deg, |
| 245 | vfov_deg=self.render_options.vfov_deg, |
| 246 | ) |
| 247 | for rig_camera in self.rig_config.cameras: |
| 248 | rig_camera.camera = self._camera |
| 249 | self._pano_size = (pano_width, pano_height) |
| 250 | self._rays_in_cam = get_virtual_camera_rays(self._camera) |
| 251 | else: # Later images, verify consistent panoramas. |
| 252 | if (pano_width, pano_height) != self._pano_size: |
| 253 | raise ValueError( |
| 254 | "Panoramas of different sizes are not supported." |
| 255 | ) |
| 256 | |
| 257 | for cam_idx, cam_from_pano_r in enumerate(self.cams_from_pano_rotation): |
| 258 | assert self._rays_in_cam is not None |
| 259 | rays_in_pano = self._rays_in_cam @ cam_from_pano_r |
| 260 | xy_in_pano = spherical_img_from_cam(self._pano_size, rays_in_pano) |
| 261 | xy_in_pano = xy_in_pano.reshape( |
| 262 | self._camera.width, self._camera.height, 2 |
| 263 | ).astype(np.float32) |
| 264 | xy_in_pano -= 0.5 # COLMAP to OpenCV pixel origin. |
| 265 | x_coords, y_coords = np.moveaxis(xy_in_pano, [0, 1, 2], [2, 1, 0]) |
| 266 | image = cv2.remap( |
| 267 | pano_image, |
| 268 | x_coords, |
| 269 | y_coords, |
| 270 | cv2.INTER_LINEAR, |
| 271 | borderMode=cv2.BORDER_WRAP, |
| 272 | ) |
| 273 | # We define a mask such that each pixel of the panorama has its |
| 274 | # features extracted only in a single virtual camera. |
| 275 | closest_camera = np.argmax( |
| 276 | rays_in_pano @ self.cam_centers_in_pano.T, -1 |
| 277 | ) |
nothing calls this directly
no test coverage detected