Spaces:
Running on Zero
Running on Zero
Download src/utils/camera_io.py from ritianyu/SplatAnything: direct link, hf CLI and curl.
- Browser
- Download file 5.36 kB
-
https://huggingface.co/spaces/ritianyu/SplatAnything/resolve/main/src/utils/camera_io.py
- Command line
-
hf download hf://spaces/ritianyu/SplatAnything/src/utils/camera_io.py
-
curl -L -o camera_io.py https://huggingface.co/spaces/ritianyu/SplatAnything/resolve/main/src/utils/camera_io.py
5.36 kB
| """Camera JSON validation shared by inference and the CPU-only conversion tool.""" | |
| import json | |
| from pathlib import Path | |
| import numpy as np | |
| from PIL import Image | |
| IMAGE_EXTENSIONS = {'.png', '.jpg', '.jpeg', '.bmp', '.tif', '.tiff', '.webp'} | |
| def list_image_files(image_dir): | |
| folder = Path(image_dir) | |
| if not folder.is_dir(): | |
| raise ValueError(f'Image directory not found: {folder}') | |
| images = sorted(p for p in folder.iterdir() | |
| if p.is_file() and p.suffix.lower() in IMAGE_EXTENSIONS) | |
| if not images: | |
| raise ValueError(f'No supported images found in {folder}') | |
| stems = [p.stem for p in images] | |
| if len(stems) != len(set(stems)): | |
| raise ValueError('Image filename stems must be unique for camera matching and output names.') | |
| return images | |
| def validate_pose(matrix, camera_id): | |
| if matrix.shape != (4, 4) or not np.isfinite(matrix).all(): | |
| raise ValueError(f'Camera {camera_id}: pose must be a finite 4x4 matrix.') | |
| rotation = matrix[:3, :3] | |
| if (not np.allclose(matrix[3], [0, 0, 0, 1], atol=1e-4) | |
| or not np.allclose(rotation.T @ rotation, np.eye(3), atol=1e-3) | |
| or not np.isclose(np.linalg.det(rotation), 1, atol=1e-3)): | |
| raise ValueError(f'Camera {camera_id}: expected a rigid transform with a proper rotation ' | |
| 'and last row [0, 0, 0, 1].') | |
| def validate_intrinsics(matrix, camera_id): | |
| if (matrix.shape != (3, 3) or not np.isfinite(matrix).all() | |
| or matrix[0, 0] <= 0 or matrix[1, 1] <= 0 | |
| or not np.allclose(matrix[2], [0, 0, 1])): | |
| raise ValueError(f'Camera {camera_id}: intrinsics must be a finite 3x3 pixel matrix ' | |
| 'with positive focal lengths and last row [0, 0, 1].') | |
| if not np.allclose([matrix[0, 1], matrix[1, 0]], 0, atol=1e-6): | |
| raise ValueError(f'Camera {camera_id}: skewed intrinsics are unsupported; ' | |
| 'use rectified pinhole images and their corresponding intrinsics.') | |
| def load_cameras_json(path): | |
| """Read OpenCV c2w poses and pixel intrinsics, indexed by image filename stem.""" | |
| with Path(path).open(encoding='utf-8') as handle: | |
| data = json.load(handle) | |
| if not isinstance(data, dict): | |
| raise ValueError('Camera JSON must be an object containing extrinsics and intrinsics lists.') | |
| result = [] | |
| for key, validator in [('extrinsics', validate_pose), ('intrinsics', validate_intrinsics)]: | |
| entries = data.get(key) | |
| if not isinstance(entries, list) or not entries: | |
| raise ValueError(f'Camera JSON requires a non-empty {key} list.') | |
| matrices = {} | |
| for entry in entries: | |
| if not isinstance(entry, dict): | |
| raise ValueError(f'Each {key} entry must contain camera_id and matrix.') | |
| camera_id = entry.get('camera_id') | |
| if not isinstance(camera_id, str) or not camera_id: | |
| raise ValueError(f'Each {key} camera_id must be a non-empty string filename stem.') | |
| if camera_id in matrices: | |
| raise ValueError(f'Duplicate camera_id {camera_id!r} in {key}.') | |
| try: | |
| matrix = np.asarray(entry['matrix'], dtype=np.float32) | |
| except (KeyError, TypeError, ValueError) as exc: | |
| raise ValueError(f'Camera {camera_id}: {key} requires a numeric matrix.') from exc | |
| validator(matrix, camera_id) | |
| matrices[camera_id] = matrix | |
| result.append(matrices) | |
| return tuple(result) | |
| def validate_cameras_for_images(images, poses, intrinsics): | |
| """Check coverage and principal points against the original, unprocessed images.""" | |
| missing = [p.name for p in images if p.stem not in poses or p.stem not in intrinsics] | |
| if missing: | |
| raise ValueError(f'Missing camera poses/intrinsics for {len(missing)} images: ' | |
| f'{", ".join(missing[:5])}. Every input image needs both entries.') | |
| for path in images: | |
| validate_pose(poses[path.stem], path.stem) | |
| matrix = intrinsics[path.stem] | |
| validate_intrinsics(matrix, path.stem) | |
| with Image.open(path) as image: | |
| width, height = image.size | |
| if not (0 < matrix[0, 2] < width and 0 < matrix[1, 2] < height): | |
| raise ValueError(f'Camera {path.stem}: principal point must lie inside the original ' | |
| f'{width}x{height} image. Supply pixel intrinsics at this resolution.') | |
| def write_cameras_json(path, images, poses, intrinsics, overwrite=False): | |
| """Validate all inputs before writing the JSON consumed by inference.py.""" | |
| validate_cameras_for_images(images, poses, intrinsics) | |
| data = {'num_cameras': len(images), 'extrinsics': [], 'intrinsics': []} | |
| for image in images: | |
| for key, matrices in [('extrinsics', poses), ('intrinsics', intrinsics)]: | |
| data[key].append({'camera_id': image.stem, 'matrix': matrices[image.stem].tolist()}) | |
| path = Path(path) | |
| path.parent.mkdir(parents=True, exist_ok=True) | |
| try: | |
| with path.open('w' if overwrite else 'x', encoding='utf-8') as handle: | |
| handle.write(json.dumps(data, indent=2, allow_nan=False) + '\n') | |
| except FileExistsError as exc: | |
| raise FileExistsError(f'{path} already exists. Use --overwrite to replace it.') from exc | |