Files
2026-07-13 13:05:14 +08:00

222 lines
7.8 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Example using an example depth dataset from NYU.
https://cs.nyu.edu/~fergus/datasets/nyu_depth_v2.html
"""
from __future__ import annotations
import argparse
import os
import sys
import zipfile
from datetime import datetime
from pathlib import Path
from typing import Any, Final
import cv2
import numpy as np
import numpy.typing as npt
import requests
from tqdm import tqdm
import rerun as rr # pip install rerun-sdk
import rerun.blueprint as rrb
DESCRIPTION = """
# RGBD
Visualizes an example recording from [the NYUD dataset](https://cs.nyu.edu/~silberman/datasets/nyu_depth_v2.html) with RGB and Depth channels.
The full source code for this example is available [on GitHub](https://github.com/rerun-io/rerun/blob/latest/examples/python/rgbd).
"""
DEPTH_IMAGE_SCALING: Final = 1e4
DATASET_DIR: Final = Path(os.path.dirname(__file__)) / "dataset"
DATASET_URL_BASE: Final = "https://static.rerun.io/rgbd_dataset"
DATASET_URL_BASE_ALTERNATE: Final = "https://cs.nyu.edu/~fergus/datasets/nyu_depth_v2.html"
AVAILABLE_RECORDINGS: Final = ["cafe", "basements", "studies", "office_kitchens", "playroooms"]
def parse_timestamp(filename: str) -> datetime:
"""Parse the timestamp portion of the filename."""
file_name_parts = filename.split("-")
time = file_name_parts[len(file_name_parts) - 2]
return datetime.fromtimestamp(float(time))
def read_image_bgr(buf: bytes) -> npt.NDArray[np.uint8]:
"""Decode an image provided in `buf`, and interpret it as RGB data."""
np_buf: npt.NDArray[np.uint8] = np.ndarray(shape=(1, len(buf)), dtype=np.uint8, buffer=buf)
img_bgr: npt.NDArray[Any] = cv2.imdecode(np_buf, cv2.IMREAD_COLOR)
return img_bgr
def read_depth_image(buf: bytes) -> npt.NDArray[Any]:
"""Decode an image provided in `buf`."""
np_buf: npt.NDArray[np.uint8] = np.ndarray(shape=(1, len(buf)), dtype=np.uint8, buffer=buf)
img: npt.NDArray[Any] = cv2.imdecode(np_buf, cv2.IMREAD_UNCHANGED)
return img
def log_nyud_data(recording_path: Path, subset_idx: int, frames: int) -> None:
rr.log("world", rr.ViewCoordinates.RIGHT_HAND_Y_DOWN, static=True)
with zipfile.ZipFile(recording_path, "r") as archive:
archive_dirs = [f.filename for f in archive.filelist if f.is_dir()]
print(f"Using recording subset {subset_idx} ([0 - {len(archive_dirs) - 1}] available).")
dir_to_log = archive_dirs[subset_idx]
subset = [
f
for f in archive.filelist
if f.filename.startswith(dir_to_log) and (f.filename.endswith(".ppm") or f.filename.endswith(".pgm"))
]
files_with_timestamps = [(parse_timestamp(f.filename), f) for f in subset]
files_with_timestamps.sort(key=lambda t: t[0])
if len(files_with_timestamps) > frames:
files_with_timestamps = files_with_timestamps[:frames]
for time, f in files_with_timestamps:
rr.set_time("time", timestamp=time.timestamp())
if f.filename.endswith(".ppm"):
buf = archive.read(f)
img_bgr = read_image_bgr(buf)
rr.log("world/camera/image/rgb", rr.Image(img_bgr, color_model="BGR").compress(jpeg_quality=95))
elif f.filename.endswith(".pgm"):
buf = archive.read(f)
img_depth = read_depth_image(buf)
# Log the camera transforms:
rr.log(
"world/camera/image",
rr.Pinhole(
resolution=[img_depth.shape[1], img_depth.shape[0]],
focal_length=0.7 * img_depth.shape[1],
# Intentionally off-center to demonstrate that we support it
principal_point=[0.45 * img_depth.shape[1], 0.55 * img_depth.shape[0]],
),
)
# Log the depth image to the cameras image-space:
rr.log("world/camera/image/depth", rr.DepthImage(img_depth, meter=DEPTH_IMAGE_SCALING))
def ensure_recording_downloaded(name: str) -> Path:
recording_filename = f"{name}.zip"
recording_path = DATASET_DIR / recording_filename
if recording_path.exists():
return recording_path
url = f"{DATASET_URL_BASE}/{recording_filename}"
alternate_url = f"{DATASET_URL_BASE_ALTERNATE}/{recording_filename}"
os.makedirs(DATASET_DIR, exist_ok=True)
try:
try:
print(f"downloading {url} to {recording_path}")
download_progress(url, recording_path)
except ValueError:
print(f"Failed to download from {url}, trying backup URL {alternate_url} instead")
download_progress(alternate_url, recording_path)
except BaseException:
recording_path.unlink(missing_ok=True)
raise
return recording_path
def download_progress(url: str, dst: Path) -> None:
"""
Download file with tqdm progress bar.
From: <https://gist.github.com/yanqd0/c13ed29e29432e3cf3e7c38467f42f51>
"""
resp = requests.get(url, stream=True)
if resp.status_code != 200:
raise ValueError(f"Failed to download file (status code: {resp.status_code})")
total = int(resp.headers.get("content-length", 0))
chunk_size = 1024 * 1024
# Can also replace 'file' with a io.BytesIO object
with (
open(dst, "wb") as file,
tqdm(
desc=dst.name,
total=total,
unit="iB",
unit_scale=True,
unit_divisor=1024,
) as bar,
):
for data in resp.iter_content(chunk_size=chunk_size):
size = file.write(data)
bar.update(size)
def main() -> None:
parser = argparse.ArgumentParser(description="Example using an example depth dataset from NYU.")
parser.add_argument(
"--recording",
type=str,
choices=AVAILABLE_RECORDINGS,
default=AVAILABLE_RECORDINGS[0],
help="Name of the NYU Depth Dataset V2 recording",
)
parser.add_argument("--subset-idx", type=int, default=0, help="The index of the subset of the recording to use.")
parser.add_argument(
"--frames",
type=int,
default=sys.maxsize,
help="If specified, limits the number of frames logged",
)
rr.script_add_args(parser)
args = parser.parse_args()
rr.script_setup(
args,
"rerun_example_rgbd",
default_blueprint=rrb.Horizontal(
rrb.Vertical(
rrb.Spatial3DView(name="3D", origin="world"),
rrb.TextDocumentView(name="Description", origin="/description"),
row_shares=[7, 3],
),
rrb.Vertical(
# Put the origin for both 2D spaces where the pinhole is logged. Doing so allows them to understand how they're connected to the 3D space.
# This enables interactions like clicking on a point in the 3D space to show the corresponding point in the 2D spaces and vice versa.
rrb.Spatial2DView(
name="RGB & Depth",
origin="world/camera/image",
overrides={"world/camera/image/rgb": rr.Image.from_fields(opacity=0.5)},
),
rrb.Tabs(
rrb.Spatial2DView(name="RGB", origin="world/camera/image", contents="world/camera/image/rgb"),
rrb.Spatial2DView(name="Depth", origin="world/camera/image", contents="world/camera/image/depth"),
),
name="2D",
row_shares=[3, 3, 2],
),
column_shares=[2, 1],
),
)
recording_path = ensure_recording_downloaded(args.recording)
rr.log("description", rr.TextDocument(DESCRIPTION, media_type=rr.MediaType.MARKDOWN), static=True)
log_nyud_data(
recording_path=recording_path,
subset_idx=args.subset_idx,
frames=args.frames,
)
rr.script_teardown(args)
if __name__ == "__main__":
main()