{ "cells": [ { "cell_type": "markdown", "id": "0", "metadata": {}, "source": [ "# HyperLux\n", "\n", "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/rl-tools/rl-tools/blob/main/docs/hyperdrone/src/HyperLuxMaximal.ipynb)\n", "\n", "Overview over HyperLux features. Each section building on the previous one. The first run downloads scenes and JIT-compiles eight renderer configurations." ] }, { "cell_type": "code", "execution_count": null, "id": "1", "metadata": { "tags": [ "remove-output" ] }, "outputs": [], "source": [ "%pip install -q \"hyperlux[examples]\" torch matplotlib" ] }, { "cell_type": "code", "execution_count": null, "id": "2", "metadata": { "mystnb": { "code_prompt_hide": "Hide imports and helpers", "code_prompt_show": "Show imports and helpers" }, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "import os\n", "import time\n", "from dataclasses import replace\n", "\n", "import conta\n", "import imageio.v2 as imageio\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "import torch\n", "from IPython.display import Video\n", "from matplotlib_inline.backend_inline import set_matplotlib_formats\n", "\n", "import hyperlux\n", "from hyperlux.camera import CameraSpec, RGB, Depth, Segmentation, Normals, Flow, FlowStatus, Probes\n", "\n", "hyperlux.set_verbosity(\"warning\")\n", "set_matplotlib_formats(\"jpeg\", pil_kwargs={\"quality\": 90, \"optimize\": True})\n", "\n", "\n", "def show(images, titles=(), columns=4, size=3, layout=None, full_resolution=False):\n", " rows = -(-len(images) // columns)\n", " figure, axes = plt.subplots(rows, columns, figsize=(size * columns, size * rows), dpi=150, squeeze=False, layout=layout)\n", " for axis, image, title in zip(axes.flat, images, list(titles) + [\"\"] * len(images)):\n", " axis.imshow(image, interpolation=\"nearest\")\n", " axis.set_title(title)\n", " for axis in axes.flat:\n", " axis.axis(\"off\")\n", " if full_resolution:\n", " figure.canvas.draw()\n", " scale = max(max(image.shape[1] / axis.bbox.width, image.shape[0] / axis.bbox.height)\n", " for axis, image in zip(axes.flat, images))\n", " figure.set_dpi(np.ceil(figure.dpi * scale))\n", " plt.show()\n", "\n", "\n", "def video(frames, path):\n", " with imageio.get_writer(path, fps=30, codec=\"libx264\") as writer:\n", " for frame in frames:\n", " writer.append_data(np.asarray(frame)[..., :3])\n", " return Video(path, embed=True, width=768, html_attributes='controls loop style=\"max-width: 100%; height: auto;\"')" ] }, { "cell_type": "markdown", "id": "3", "metadata": {}, "source": [ "## Scene, camera, frame" ] }, { "cell_type": "code", "execution_count": null, "id": "4", "metadata": {}, "outputs": [], "source": [ "scene = hyperlux.Scene()\n", "scene.add_object(hyperlux.primitives.box(size=(6, 6, 0.1), color=(0.5, 0.5, 0.55)), hyperlux.make_transform(position=(0, 0, -0.55)))\n", "scene.add_object(hyperlux.primitives.box(size=(1, 1, 1), color=(0.9, 0.35, 0.1)), hyperlux.make_transform(orientation_wxyz=(0.92, 0, 0, 0.38)))\n", "scene.add_light(hyperlux.SceneLight.point((2, 1, 3), (4, 4, 4), radius=0.3))\n", "renderer = hyperlux.HyperLux(width=1024, height=1024)\n", "renderer.init(scene)\n", "renderer.set_cameras(renderer.camera(position=(3, 1.5, 1.5), look_at=(0, 0, 0), fov=50))\n", "renderer.render()\n", "show([renderer.outputs[RGB()].read()[0]], columns=1, size=8)" ] }, { "cell_type": "code", "execution_count": null, "id": "5", "metadata": {}, "outputs": [], "source": [ "print(renderer.backend, renderer.config.key())\n", "print(renderer.config)\n", "studio = hyperlux.HyperLux(config=renderer.config)" ] }, { "cell_type": "markdown", "id": "6", "metadata": {}, "source": [ "## Materials, lights, environment" ] }, { "cell_type": "code", "execution_count": null, "id": "7", "metadata": {}, "outputs": [], "source": [ "quad = np.array([[0, -0.4, -0.4], [0, 0.4, -0.4], [0, 0.4, 0.4], [0, -0.4, 0.4]], np.float32)\n", "faces = np.array([[0, 1, 2], [0, 2, 3]], np.int32)\n", "materials = hyperlux.Scene()\n", "materials.add_object(hyperlux.primitives.box(size=(8, 8, 0.1), color=(0.5, 0.5, 0.55)), hyperlux.make_transform(position=(0, 0, -1.5)))\n", "for row, roughness in enumerate((0.05, 0.4, 1.0)):\n", " for column, metallic in enumerate((0.0, 0.5, 1.0)):\n", " swatch = hyperlux.Object(f\"metallic {metallic} roughness {roughness}\")\n", " swatch.add_mesh(hyperlux.Mesh(quad, faces, color=(0.9, 0.5, 0.3), normals=np.tile(np.float32([1, 0, 0]), (4, 1)), tex_coords=quad[:, 1:] + 0.5, metallic=metallic, roughness=roughness))\n", " materials.add_object(swatch, hyperlux.make_transform(position=(0, 1 - column, 0.9 - 0.9 * row)))\n", "glow = hyperlux.Object(\"emissive\")\n", "glow.add_mesh(hyperlux.Mesh(quad * 0.6, faces, color=(0, 0, 0), emissive=(0.2, 1.5, 3)))\n", "glow.add_light(hyperlux.SceneLight.point((0.3, 0, 0), (0.1, 0.5, 1), attenuation=(1, 0, 1)))\n", "materials.add_object(glow, hyperlux.make_transform(position=(0, -2, 0)))\n", "materials.add_light(hyperlux.SceneLight.directional((0.8, 0.2, 0.57), (0.8, 0.8, 0.75), radius=0.05))\n", "materials.add_light(hyperlux.SceneLight.spot((3, -2, 2), (-0.73, 0.49, -0.49), (2.5, 2.5, 2.5), cos_inner_cone=0.95, cos_outer_cone=0.85, radius=0.2))\n", "materials.set_environment(ambient=(0.15, 0.15, 0.2), background=(0.02, 0.03, 0.05))\n", "studio.init(materials)\n", "studio.set_cameras(studio.camera(position=(4, 0, 0.3), look_at=(0, 0, 0), fov=60))\n", "studio.render()\n", "show([studio.outputs[RGB()].read()[0]], columns=1)" ] }, { "cell_type": "markdown", "id": "8", "metadata": {}, "source": [ "## Fidelity tiers" ] }, { "cell_type": "code", "execution_count": null, "id": "9", "metadata": {}, "outputs": [], "source": [ "tiers = []\n", "for fidelity in hyperlux.FIDELITY:\n", " tier = hyperlux.HyperLux(width=256, height=256, fidelity=fidelity)\n", " tier.init(materials)\n", " tier.set_cameras(studio.camera(position=(4, 0, 0.3), look_at=(0, 0, 0), fov=60))\n", " tier.render()\n", " tiers.append(tier.outputs[RGB()].read()[0])\n", "show(tiers, hyperlux.FIDELITY)" ] }, { "cell_type": "markdown", "id": "10", "metadata": {}, "source": [ "## Anti-aliasing and motion blur" ] }, { "cell_type": "code", "execution_count": null, "id": "11", "metadata": {}, "outputs": [], "source": [ "smooth = hyperlux.HyperLux(width=256, height=256, motion_blur_samples=8)\n", "smooth.init(scene)\n", "camera_open, camera_close = (renderer.camera(position=(3, y, 1.5), look_at=(0, 0, 0), fov=50) for y in (1.5, 1.9))\n", "aliased = hyperlux.HyperLux(width=256, height=256, anti_aliasing_grid=1)\n", "aliased.init(scene)\n", "aliased.set_cameras(camera_open)\n", "aliased.render()\n", "smooth.set_cameras(camera_open)\n", "smooth.render()\n", "still = smooth.outputs[RGB()].read()[0]\n", "smooth.set_motion_blur_cameras(camera_open, camera_close)\n", "smooth.render_launch()\n", "smooth.render_sync()\n", "crop = np.s_[72:136, 64:128]\n", "show([aliased.outputs[RGB()].read()[0][crop], still[crop], smooth.outputs[RGB()].read()[0][crop]], (\"aliased\", \"2x2 anti-aliasing\", \"+ 8-sample motion blur\"), columns=3)" ] }, { "cell_type": "markdown", "id": "12", "metadata": {}, "source": [ "## A real scene, a camera rig" ] }, { "cell_type": "code", "execution_count": null, "id": "13", "metadata": {}, "outputs": [], "source": [ "dataset = hyperlux.datasets.procthor.Train200()\n", "house = hyperlux.load_scene(dataset[\"ProcTHOR-Train-1\"].reference, preserve_instances=True)\n", "rig = hyperlux.HyperLux(width=2048, height=2048, batch_size=4, num_probes=32, cameras=(\n", " CameraSpec(\"front\", (RGB(), Depth(), Segmentation(), Segmentation(labels=\"semantic\"), Normals(), Flow(direction=\"forward\"), FlowStatus(direction=\"forward\"), Probes())),\n", " CameraSpec(\"preview\", (RGB(),))))\n", "rig.init(house)\n", "P = np.array([-3.92, -5.67, 1.2])\n", "yaws = np.deg2rad([0, 90, 180, 270])\n", "cameras = np.stack([hyperlux.make_camera(P, P + (np.cos(yaw), np.sin(yaw), 0), fov=90, aspect=rig.aspect) for yaw in yaws])\n", "rig.set_cameras(np.repeat(cameras, 2, axis=0))\n", "rig.render()\n", "print(rig.outputs[Depth()].layout)\n", "print(rig.outputs[RGB()].layout.cameras)\n", "show(rig.outputs[RGB()].read()[::2], [f\"front, yaw {int(np.degrees(yaw))}°\" for yaw in yaws], columns=2, layout=\"compressed\", full_resolution=True)" ] }, { "cell_type": "markdown", "id": "14", "metadata": {}, "source": [ "## Every output" ] }, { "cell_type": "code", "execution_count": null, "id": "15", "metadata": {}, "outputs": [], "source": [ "rig.advance_flow()\n", "cameras = np.repeat(np.stack([hyperlux.make_camera(P + (0.05, 0, 0), P + (np.cos(yaw + 0.02), np.sin(yaw + 0.02), 0), fov=90, aspect=rig.aspect) for yaw in yaws]), 2, axis=0)\n", "rig.set_cameras(cameras)\n", "rig.render()\n", "view = {descriptor: rig.outputs[descriptor].read()[0] for descriptor in rig.outputs if descriptor != Probes()}\n", "valid = view[FlowStatus(direction=\"forward\")] == hyperlux.FlowStatus.VALID\n", "show([np.where(np.isfinite(view[Depth()]), view[Depth()], np.nan), view[Normals()] * 0.5 + 0.5,\n", " np.ma.masked_equal(view[Segmentation()], 0xFFFFFFFF) % 20, np.ma.masked_equal(view[Segmentation(labels=\"semantic\")], 0xFFFFFFFF) % 20,\n", " np.ma.masked_where(~valid, np.linalg.norm(view[Flow(direction=\"forward\")], axis=-1))],\n", " (\"depth\", \"normals\", \"instances\", \"classes\", \"forward flow\"), columns=2, size=3.6, layout=\"compressed\", full_resolution=True)" ] }, { "cell_type": "markdown", "id": "16", "metadata": {}, "source": [ "## Labels" ] }, { "cell_type": "code", "execution_count": null, "id": "17", "metadata": {}, "outputs": [], "source": [ "classes, instances = view[Segmentation(labels=\"semantic\")], view[Segmentation()]\n", "print(house.segmentation_taxonomy, {house.segmentation_classes[int(label)]: int((classes == label).sum()) for label in np.unique(classes) if label != 0xFFFFFFFF})\n", "center = house.instance_object(int(instances[rig.height // 2, rig.width // 2]))\n", "print(\"center pixel:\", house.object_name(center))\n", "coarse = {label: {\"Undefined\": 0, \"Wall\": 1, \"Floor\": 2, \"Ceiling\": 2}.get(name, 3) for label, name in house.segmentation_classes.items()}\n", "house.remap_segmentation_classes(coarse, taxonomy=\"coarse\", classes={0: \"Undefined\", 1: \"Wall\", 2: \"Floor/Ceiling\", 3: \"Furniture\", 4: \"Target\"})\n", "house.set_object_segmentation_class(center, 4)\n", "rig = hyperlux.HyperLux(config=rig.config)\n", "rig.init(house)\n", "rig.set_cameras(cameras)\n", "rig.render()\n", "with plt.rc_context({\"axes.titlesize\": 4}):\n", " show([np.ma.masked_equal(rig.outputs[Segmentation(labels=\"semantic\")].read()[0], 0xFFFFFFFF)], [\"\\n\".join(f\"{label}: {name}\" for label, name in house.segmentation_classes.items())], columns=1, full_resolution=True)" ] }, { "cell_type": "markdown", "id": "18", "metadata": {}, "source": [ "## Probes and free space" ] }, { "cell_type": "code", "execution_count": null, "id": "19", "metadata": {}, "outputs": [], "source": [ "rig.generate_probe_directions()\n", "rig.probe()\n", "distance, hit = rig.outputs[Probes()].distance.read(), rig.outputs[Probes()].hit.read()\n", "free_space = hyperlux.FreeSpace(house, parameters=hyperlux.FreeSpaceParameters(min_clearance=0.6))\n", "points = free_space.sample(4, seed=0)" ] }, { "cell_type": "markdown", "id": "20", "metadata": {}, "source": [ "## Assets and overlays" ] }, { "cell_type": "code", "execution_count": null, "id": "21", "metadata": {}, "outputs": [], "source": [ "x500 = conta.resolve({\"description\": \"rigged/x500_optimized_scaled.glb\", \"hash\": \"c0fda2143b7a2f0422148aa8f6c4e24db3f8a6e6\"})\n", "drone = hyperlux.load_assembly(x500, part_nodes=[\"body\", \"prop_0\", \"prop_1\", \"prop_2\", \"prop_3\"])\n", "for prop in range(4):\n", " low, high = drone.object_bounds(prop + 1)\n", " drone.set_joint(f\"prop_{prop}\", \"revolute\", axis=(0, 0, 1), pivot=(low + high) / 2, coordinate=prop)\n", "payload = hyperlux.primitives.box(size=(0.08, 0.08, 0.12), color=(1, 0.8, 0))\n", "payload.name = \"payload\"\n", "drone.add_part(payload, hyperlux.make_transform(position=(0, 0, -0.15)), parent=\"body\")\n", "drone.set_joint(\"payload\", \"prismatic\", axis=(0, 0, -1), coordinate=4)\n", "drone.remap_segmentation_classes({0: 3}, taxonomy=\"coarse\", classes=house.segmentation_classes)\n", "print(drone.object_names(), drone.num_coordinates, drone.joint(\"prop_0\"), drone.part_parent(5))\n", "D = P + (0, 0, -0.2)\n", "house.add_assembly(drone, hyperlux.make_transform(position=D + (0.5, 0.9, -0.9)))\n", "pool = hyperlux.AssetPool()\n", "asset = pool.add_assembly(drone)\n", "overlay_config = replace(rig.config, width=1024, height=1024, batch_size=1, num_overlays=1, max_overlay_instances=36, max_overlays_per_camera=1, motion_blur_samples=8, dynamic_motion_blur=True)\n", "del rig\n", "overlay = hyperlux.HyperLux(config=overlay_config)\n", "overlay.init(house, asset_pool=pool)\n", "overlay.attach(0, 0)\n", "placement = overlay.spawn(0, asset, hyperlux.Pose(hyperlux.make_transform(position=D)), segmentation_class=hyperlux.AGENT_SEGMENTATION_CLASS)\n", "overlay.set_cameras(np.repeat(overlay.camera(position=D + (-1.2, -1.2, 0.7), look_at=D, fov=55)[None], 2, axis=0))\n", "overlay.update()\n", "overlay.render()\n", "ids = house.num_instances + placement.overlay * overlay.config.max_overlay_instances + placement.first_slot + np.arange(placement.num_parts)\n", "print(placement, ids)\n", "show([*overlay.outputs[RGB()].read(), np.isin(overlay.outputs[Segmentation()].read()[0], ids), overlay.outputs[Segmentation(labels=\"semantic\")].read()[0] == hyperlux.AGENT_SEGMENTATION_CLASS],\n", " (\"front: overlay attached\", \"preview: not attached\", \"overlay instance ids\", \"AGENT class\"), columns=2, layout=\"compressed\")" ] }, { "cell_type": "markdown", "id": "22", "metadata": {}, "source": [ "## Articulated motion blur" ] }, { "cell_type": "code", "execution_count": null, "id": "23", "metadata": {}, "outputs": [], "source": [ "def state(t):\n", " base = hyperlux.make_transform(position=D + (0, 0, 0.05 * np.sin(2 * t)), orientation_wxyz=(np.cos(t / 2), 0, 0, np.sin(t / 2)))\n", " coordinates = [150 * t, -150 * t, 150 * t, -150 * t, 0.1 + 0.1 * np.sin(3 * t)]\n", " camera = overlay.camera(position=D + (-1.2 * np.cos(t / 3), -1.2 * np.sin(t / 3), 0.7), look_at=D, fov=55)\n", " return base, coordinates, np.repeat(camera[None], 2, axis=0)\n", "\n", "\n", "def motion_frames():\n", " for t in np.arange(0, 4, 1 / 30):\n", " (base_open, coordinates_open, cameras_open), (base_close, coordinates_close, cameras_close) = state(t), state(t + 0.5 / 30)\n", " overlay.set_pose(placement, hyperlux.Pose(base_open), hyperlux.Pose(base_close), coordinates=coordinates_open, coordinates_close=coordinates_close)\n", " overlay.set_motion_blur_cameras(cameras_open, cameras_close)\n", " overlay.update()\n", " overlay.render()\n", " yield overlay.outputs[RGB()].read()[0]\n", "\n", "\n", "video(motion_frames(), \"hyperlux_motion.mp4\")" ] }, { "cell_type": "markdown", "id": "24", "metadata": {}, "source": [ "## Bulk posing on the GPU" ] }, { "cell_type": "code", "execution_count": null, "id": "25", "metadata": {}, "outputs": [], "source": [ "overlay.despawn(placement)\n", "placements = [overlay.spawn(0, asset) for _ in range(5)]\n", "batch = overlay.placement_batch(placements)\n", "device = torch.device(\"cuda\" if overlay.backend == \"optix\" else \"cpu\")\n", "stream = torch.cuda.current_stream(device).cuda_stream if device.type == \"cuda\" else 0\n", "angles = torch.linspace(0, 2 * torch.pi, 6, device=device)[:-1]\n", "rest = torch.eye(4, device=device)[:3].repeat(5, 1, 1)\n", "rest[:, :, 3] = torch.as_tensor(D, dtype=torch.float32, device=device) + 0.5 * torch.stack((angles.cos(), angles.sin(), 0.3 * (2 * angles).sin()), dim=1)\n", "spin = torch.tensor([150, -150, 150, -150, 0], device=device)\n", "overlay.set_cameras(torch.as_tensor(np.repeat(overlay.camera(position=D + (-1.8, -1.4, 0.9), look_at=D, fov=65)[None], 2, axis=0), device=device), stream=stream)\n", "\n", "\n", "def swarm_frames():\n", " frame = torch.empty(overlay.outputs[RGB()].layout.shape, dtype=torch.uint8, device=device)\n", " for i in range(120):\n", " t = i / 30\n", " bases = rest.clone()\n", " bases[:, 2, 3] += 0.1 * (2 * t + angles).sin()\n", " overlay.set_pose(batch, hyperlux.Pose(bases), coordinates=spin.expand(5, 5) * t, stream=stream)\n", " overlay.update_launch()\n", " overlay.render_launch()\n", " overlay.outputs[RGB()].read(out=frame, stream=stream)\n", " yield frame[0].cpu().numpy()\n", "\n", "\n", "start = time.perf_counter()\n", "swarm_video = video(swarm_frames(), \"hyperlux_swarm.mp4\")\n", "print(f\"{120 / (time.perf_counter() - start):.0f} frames/s including encoding on {device}, render stream {overlay.render_stream:#x}\")\n", "live = torch.from_dlpack(overlay.outputs[RGB()].blocks[0])\n", "print(live.shape, live.dtype, live.device, torch.from_dlpack(overlay.outputs[RGB()].packed.blocks[0]).dtype)\n", "swarm_video" ] }, { "cell_type": "markdown", "id": "26", "metadata": {}, "source": [ "## Scene catalogs" ] }, { "cell_type": "code", "execution_count": null, "id": "27", "metadata": {}, "outputs": [], "source": [ "train = hyperlux.datasets.procthor.Train200()\n", "validation = hyperlux.datasets.hssd.Val43()\n", "print(train.name, train.source, train.split, len(train), train[0].id, train[0].reference)\n", "print(validation.name, validation.source, validation.split, len(validation), validation[0].id, validation[0].reference)\n", "entries = (train[0], train[1], train[2], validation[0])\n", "rng = np.random.default_rng(0)\n", "figure, axes = plt.subplots(len(entries), 4, figsize=(12, 3 * len(entries)), dpi=150, squeeze=False)\n", "figure.subplots_adjust(left=0, right=1, bottom=0, top=1, wspace=0, hspace=0)\n", "for entry, row in zip(entries, axes):\n", " scene = hyperlux.load_scene(entry.reference)\n", " viewer = hyperlux.HyperLux(config=replace(renderer.config, batch_size=4))\n", " viewer.init(scene)\n", " positions = hyperlux.FreeSpace(scene, parameters=hyperlux.FreeSpaceParameters(min_clearance=0.8)).sample(4, seed=0)\n", " yaws = rng.uniform(-np.pi, np.pi, 4)\n", " viewer.set_cameras(np.stack([\n", " viewer.camera(position=position, look_at=position + (np.cos(yaw), np.sin(yaw), 0), fov=90)\n", " for position, yaw in zip(positions, yaws)\n", " ]))\n", " viewer.render()\n", " for axis, image in zip(row, viewer.outputs[RGB()].read()):\n", " axis.imshow(image, interpolation=\"nearest\")\n", " axis.axis(\"off\")\n", " figure.text(0.03, 0.97, entry.id, transform=row[0].transAxes, va=\"top\", color=\"white\", fontsize=32,\n", " bbox={\"facecolor\": \"black\", \"alpha\": 0.6, \"edgecolor\": \"none\", \"pad\": 3})\n", "with plt.rc_context({\"savefig.pad_inches\": 0}):\n", " plt.show()" ] } ], "metadata": { "accelerator": "GPU", "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 }