Spaces:
Running
on
Zero
Running
on
Zero
| # Copyright (c) TAPIP3D team(https://tapip3d.github.io/) | |
| import os | |
| import numpy as np | |
| import cv2 | |
| import json | |
| import struct | |
| import zlib | |
| import argparse | |
| from einops import rearrange | |
| from pathlib import Path | |
| import shutil | |
| from tempfile import TemporaryDirectory | |
| import http.server | |
| import socketserver | |
| import socket | |
| import sys | |
| from http.server import SimpleHTTPRequestHandler | |
| from socketserver import ThreadingTCPServer | |
| import base64 | |
| viz_html_path = Path(__file__).parent / "viz.html" | |
| DEFAULT_PORT = 8000 | |
| def compress_and_write(filename, header, blob): | |
| header_bytes = json.dumps(header).encode("utf-8") | |
| header_len = struct.pack("<I", len(header_bytes)) | |
| with open(filename, "wb") as f: | |
| f.write(header_len) | |
| f.write(header_bytes) | |
| f.write(blob) | |
| def process_point_cloud_data(npz_file, output_file, static_html_file=None, width=256, height=192, fps=4): | |
| fixed_size = (width, height) | |
| data = np.load(npz_file) | |
| extrinsics = data["extrinsics"] | |
| intrinsics = data["intrinsics"] | |
| trajs = data["coords"] | |
| T, C, H, W = data["video"].shape | |
| fx = intrinsics[0, 0, 0] | |
| fy = intrinsics[0, 1, 1] | |
| fov_y = 2 * np.arctan(H / (2 * fy)) * (180 / np.pi) | |
| fov_x = 2 * np.arctan(W / (2 * fx)) * (180 / np.pi) | |
| original_aspect_ratio = (W / fx) / (H / fy) | |
| rgb_video = (rearrange(data["video"], "T C H W -> T H W C") * 255).astype(np.uint8) | |
| rgb_video = np.stack([cv2.resize(frame, fixed_size, interpolation=cv2.INTER_AREA) | |
| for frame in rgb_video]) | |
| depth_video = data["depths"].astype(np.float32) | |
| depth_video = np.stack([cv2.resize(frame, fixed_size, interpolation=cv2.INTER_NEAREST) | |
| for frame in depth_video]) | |
| scale_x = fixed_size[0] / W | |
| scale_y = fixed_size[1] / H | |
| intrinsics = intrinsics.copy() | |
| intrinsics[:, 0, :] *= scale_x | |
| intrinsics[:, 1, :] *= scale_y | |
| min_depth = float(depth_video.min()) * 0.8 | |
| max_depth = float(depth_video.max()) * 1.5 | |
| depth_normalized = (depth_video - min_depth) / (max_depth - min_depth) | |
| depth_int = (depth_normalized * ((1 << 16) - 1)).astype(np.uint16) | |
| depths_rgb = np.zeros((T, fixed_size[1], fixed_size[0], 3), dtype=np.uint8) | |
| depths_rgb[:, :, :, 0] = (depth_int & 0xFF).astype(np.uint8) | |
| depths_rgb[:, :, :, 1] = ((depth_int >> 8) & 0xFF).astype(np.uint8) | |
| first_frame_inv = np.linalg.inv(extrinsics[0]) | |
| normalized_extrinsics = np.array([first_frame_inv @ ext for ext in extrinsics]) | |
| normalized_trajs = np.zeros_like(trajs) | |
| for t in range(T): | |
| homogeneous_trajs = np.concatenate([trajs[t], np.ones((trajs.shape[1], 1))], axis=1) | |
| transformed_trajs = (first_frame_inv @ homogeneous_trajs.T).T | |
| normalized_trajs[t] = transformed_trajs[:, :3] | |
| # Get conf data from npz file | |
| conf_data = data["conf"].item() if "conf" in data else {} | |
| arrays = { | |
| "rgb_video": rgb_video, | |
| "depths_rgb": depths_rgb, | |
| "intrinsics": intrinsics, | |
| "extrinsics": normalized_extrinsics, | |
| "inv_extrinsics": np.linalg.inv(normalized_extrinsics), | |
| "trajectories": normalized_trajs.astype(np.float32), | |
| "cameraZ": 0.0, | |
| "visibs": data["visibs"] if "visibs" in data else None, | |
| "confs": data["confs"] if "confs" in data else None | |
| } | |
| header = {} | |
| blob_parts = [] | |
| offset = 0 | |
| for key, arr in arrays.items(): | |
| if arr is not None: | |
| arr = np.ascontiguousarray(arr) | |
| arr_bytes = arr.tobytes() | |
| header[key] = { | |
| "dtype": str(arr.dtype), | |
| "shape": arr.shape, | |
| "offset": offset, | |
| "length": len(arr_bytes) | |
| } | |
| blob_parts.append(arr_bytes) | |
| offset += len(arr_bytes) | |
| raw_blob = b"".join(blob_parts) | |
| compressed_blob = zlib.compress(raw_blob, level=9) | |
| header["meta"] = { | |
| "depthRange": [min_depth, max_depth], | |
| "totalFrames": int(T), | |
| "resolution": fixed_size, | |
| "baseFrameRate": fps, | |
| "numTrajectoryPoints": normalized_trajs.shape[1], | |
| "fov": float(fov_y), | |
| "fov_x": float(fov_x), | |
| "original_aspect_ratio": float(original_aspect_ratio), | |
| "fixed_aspect_ratio": float(fixed_size[0]/fixed_size[1]), | |
| "depthFilter": conf_data.get("depthFilter", {}) | |
| } | |
| compress_and_write(output_file, header, compressed_blob) | |
| if static_html_file is not None: | |
| # encode the .bin file to a base64 string | |
| with open(output_file, "rb") as f: | |
| encoded_blob = base64.b64encode(f.read()).decode("ascii") | |
| with open(viz_html_path, "r", encoding="utf-8") as f: | |
| html_template = f.read() | |
| injected_html = html_template.replace( | |
| "<head>", | |
| f"<head>\n<script>window.embeddedBase64 = `{encoded_blob}`;</script>" | |
| ) | |
| with open(static_html_file, "w", encoding="utf-8") as f: | |
| f.write(injected_html) | |
| return None | |
| def main(): | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument('input_file', help='Path to the input .result.npz file') | |
| parser.add_argument('--width', '-W', type=int, default=256, help='Target width') | |
| parser.add_argument('--height', '-H', type=int, default=192, help='Target height') | |
| parser.add_argument('--fps', type=int, default=4, help='Base frame rate for playback') | |
| parser.add_argument('--port', '-p', type=int, default=DEFAULT_PORT, help=f'Port to serve the visualization (default: {DEFAULT_PORT})') | |
| parser.add_argument('--static-html', '-s', type=str, default=None, help='Path to the static HTML file') | |
| args = parser.parse_args() | |
| with TemporaryDirectory() as temp_dir: | |
| temp_path = Path(temp_dir) | |
| process_point_cloud_data( | |
| args.input_file, | |
| temp_path / "data.bin", | |
| args.static_html, | |
| width=args.width, | |
| height=args.height, | |
| fps=args.fps | |
| ) | |
| if args.static_html is not None: | |
| return | |
| shutil.copy(viz_html_path, temp_path / "index.html") | |
| os.chdir(temp_path) | |
| host = "0.0.0.0" | |
| port = int(args.port) | |
| Handler = SimpleHTTPRequestHandler | |
| httpd = None | |
| try: | |
| httpd = ThreadingTCPServer((host, port), Handler) | |
| except OSError as e: | |
| if e.errno == socket.errno.EADDRINUSE: | |
| print(f"Port {port} is already in use, trying a random port...") | |
| try: | |
| httpd = ThreadingTCPServer((host, 0), Handler) | |
| port = httpd.server_address[1] # Get the assigned port | |
| except OSError as e2: | |
| print(f"Failed to bind to a random port: {e2}", file=sys.stderr) | |
| sys.exit(1) | |
| else: | |
| print(f"Failed to start server: {e}", file=sys.stderr) | |
| sys.exit(1) | |
| if httpd: | |
| print(f"Serving at http://{host}:{port}") | |
| try: | |
| httpd.serve_forever() | |
| except KeyboardInterrupt: | |
| print("\nServer stopped.") | |
| finally: | |
| httpd.server_close() | |
| if __name__ == "__main__": | |
| main() |