Buckets:
| import json | |
| from pathlib import Path | |
| import cv2 | |
| import numpy as np | |
| from .. import video | |
| def load_reconstruction(folder): | |
| from .mapanything import Reconstruction | |
| folder = Path(folder) | |
| meta = json.loads((folder / "manifest.json").read_text()) | |
| views = [] | |
| for index in range(meta["views"]): | |
| with np.load(folder / f"view_{index:04d}.npz") as data: | |
| views.append({key: data[key] for key in data.files}) | |
| return Reconstruction( | |
| views, | |
| np.asarray(meta["frame_indices"]), | |
| np.asarray(meta["timestamps"]), | |
| meta["model_id"], | |
| ) | |
| def _label(image, text): | |
| out = image.copy() | |
| cv2.rectangle(out, (0, 0), (out.shape[1], 34), (18, 22, 30), -1) | |
| cv2.putText( | |
| out, | |
| text, | |
| (12, 24), | |
| cv2.FONT_HERSHEY_SIMPLEX, | |
| 0.55, | |
| (240, 245, 255), | |
| 1, | |
| cv2.LINE_AA, | |
| ) | |
| return out | |
| def _colormap(values, mask, bounds, palette): | |
| normalized = np.clip((values - bounds[0]) / max(bounds[1] - bounds[0], 1e-8), 0, 1) | |
| normalized = np.nan_to_num(normalized) | |
| colored = cv2.cvtColor( | |
| cv2.applyColorMap((normalized * 255).astype(np.uint8), palette), | |
| cv2.COLOR_BGR2RGB, | |
| ) | |
| colored[~mask] = [18, 22, 30] | |
| return colored | |
| def _project(points, colors, pose, intrinsics, size): | |
| width, height = size | |
| camera_points = (points - pose[:3, 3]) @ pose[:3, :3] | |
| valid = np.isfinite(camera_points).all(axis=1) & (camera_points[:, 2] > 0) | |
| camera_points, colors = camera_points[valid], colors[valid] | |
| pixels = camera_points @ intrinsics.T | |
| pixels = np.rint(pixels[:, :2] / pixels[:, 2:]).astype(int) | |
| valid = ( | |
| (pixels[:, 0] >= 0) | |
| & (pixels[:, 0] < width) | |
| & (pixels[:, 1] >= 0) | |
| & (pixels[:, 1] < height) | |
| ) | |
| pixels, colors, depth = pixels[valid], colors[valid], camera_points[valid, 2] | |
| order = np.argsort(-depth) | |
| out = np.full((height, width, 3), [18, 22, 30], dtype=np.uint8) | |
| out[pixels[order, 1], pixels[order, 0]] = colors[order] | |
| return out | |
| def export_preview(result, folder): | |
| folder = Path(folder) | |
| masks = [v["mask"] for v in result.views] | |
| depths = np.concatenate( | |
| [v["depth_z"].reshape(m.shape)[m] for v, m in zip(result.views, masks)] | |
| ) | |
| confidences = np.concatenate( | |
| [v["conf"].reshape(m.shape)[m] for v, m in zip(result.views, masks)] | |
| ) | |
| if not len(depths): | |
| raise ValueError("Reconstruction has no valid geometry") | |
| depth_range = np.percentile(depths, [2, 98]) | |
| confidence_range = np.percentile(confidences, [2, 98]) | |
| points = np.concatenate( | |
| [v["pts3d"][::3, ::3][m[::3, ::3]] for v, m in zip(result.views, masks)] | |
| ) | |
| colors = np.concatenate( | |
| [v["img_no_norm"][::3, ::3][m[::3, ::3]] for v, m in zip(result.views, masks)] | |
| ) | |
| panels, metrics = [], [] | |
| for i, (view, mask) in enumerate(zip(result.views, masks)): | |
| height, width = mask.shape | |
| depth = _colormap( | |
| view["depth_z"].reshape(mask.shape), mask, depth_range, cv2.COLORMAP_TURBO | |
| ) | |
| confidence = _colormap( | |
| view["conf"].reshape(mask.shape), | |
| mask, | |
| confidence_range, | |
| cv2.COLORMAP_VIRIDIS, | |
| ) | |
| reprojection = _project( | |
| points, colors, view["camera_poses"], view["intrinsics"], (width, height) | |
| ) | |
| panel = np.concatenate( | |
| ( | |
| np.concatenate( | |
| (_label(view["img_no_norm"], "RGB"), _label(depth, "Metric depth")), | |
| axis=1, | |
| ), | |
| np.concatenate( | |
| ( | |
| _label(confidence, "Confidence / validity"), | |
| _label(reprojection, "Multi-view point cloud reprojection"), | |
| ), | |
| axis=1, | |
| ), | |
| ), | |
| axis=0, | |
| ) | |
| panels.append(panel) | |
| metrics.append( | |
| { | |
| "frame": int(result.frame_indices[i]), | |
| "timestamp": float(result.timestamps[i]), | |
| "valid_fraction": float(mask.mean()), | |
| "median_depth_m": float( | |
| np.median(view["depth_z"].reshape(mask.shape)[mask]) | |
| ), | |
| } | |
| ) | |
| step = np.median(np.diff(result.timestamps)) if len(result.timestamps) > 1 else 1 | |
| video.write(folder / "reconstruction.mp4", panels, 1 / step) | |
| cv2.imwrite( | |
| str(folder / "preview.png"), | |
| cv2.cvtColor(panels[len(panels) // 2], cv2.COLOR_RGB2BGR), | |
| ) | |
| center = np.median(points, axis=0) | |
| radius = max(float(np.percentile(np.linalg.norm(points - center, axis=1), 90)), 0.1) | |
| orbit = [] | |
| for angle in np.linspace(0, 2 * np.pi, 90, endpoint=False): | |
| eye = center + radius * np.array( | |
| [1.8 * np.cos(angle), -0.9, 1.8 * np.sin(angle)] | |
| ) | |
| forward = (center - eye) / np.linalg.norm(center - eye) | |
| right = np.cross(forward, [0, -1, 0]) | |
| right /= np.linalg.norm(right) | |
| down = np.cross(forward, right) | |
| pose = np.eye(4) | |
| pose[:3, :3] = np.column_stack((right, down, forward)) | |
| pose[:3, 3] = eye | |
| rendered = _project( | |
| points, | |
| colors, | |
| pose, | |
| np.array([[600, 0, 480], [0, 600, 320], [0, 0, 1]]), | |
| (960, 640), | |
| ) | |
| orbit.append(_label(rendered, "MapAnything multi-view geometry")) | |
| video.write(folder / "orbit.mp4", orbit, 30) | |
| cv2.imwrite( | |
| str(folder / "overview.png"), cv2.cvtColor(orbit[10], cv2.COLOR_RGB2BGR) | |
| ) | |
| (folder / "metrics.json").write_text( | |
| json.dumps({"depth_range_m": depth_range.tolist(), "views": metrics}, indent=2) | |
| + "\n" | |
| ) | |
Xet Storage Details
- Size:
- 5.78 kB
- Xet hash:
- 0f40707a011d22a6a52352a6cecc24b05076b8f7a68847677185c43718de5631
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.