diff --git a/discussion/probabilistic_bundle_adjustment/ProbBundle.ipynb b/discussion/probabilistic_bundle_adjustment/ProbBundle.ipynb
new file mode 100644
index 0000000000..fdbe3b8a7c
--- /dev/null
+++ b/discussion/probabilistic_bundle_adjustment/ProbBundle.ipynb
@@ -0,0 +1,3960 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "07c1d969-5e2f-47d6-b49d-e639f8f0ec4c",
+ "metadata": {},
+ "source": [
+ "#### Copyright 2024 The TensorFlow Probability Authors.\n",
+ "\n",
+ "```none\n",
+ "Licensed under the Apache License, Version 2.0 (the \"License\");\n",
+ "you may not use this file except in compliance with the License.\n",
+ "You may obtain a copy of the License at\n",
+ "\n",
+ "https://www.apache.org/licenses/LICENSE-2.0\n",
+ "\n",
+ "Unless required by applicable law or agreed to in writing, software\n",
+ "distributed under the License is distributed on an \"AS IS\" BASIS,\n",
+ "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
+ "See the License for the specific language governing permissions and\n",
+ "limitations under the License.\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b3110834-ae1d-46ca-bfef-4fcfd2d27638",
+ "metadata": {},
+ "source": [
+ "# Probabilistic Bundle Adjustment"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2f0fda86-5b77-4182-ae35-af8969b72565",
+ "metadata": {},
+ "source": [
+ "This notebook shows how to use probabilistic modeling and inference to solve the bundle adjustment problem. Given a video of keypoints, we construct a probabilistic generative model that can reconstruct that video given camera poses and keypoint world positions. We then use Markov Chain Monte Carlo (MCMC) to perform probabilistic inference on this model to infer the unknown camera poses and keypoint world positions.\n",
+ "\n",
+ "In an uncommon choice, this notebook illustrates how to construct an interactive inference controller that enables monitoring and adjusting the model and inference hyperparameters to gain a better understanding of the model and inference procedure. As a result, **this notebook must be run in Jupyter Lab**, with a GPU. Running on Google Colab will cause the UI elements to not function correctly (the batch inference should be fine however).\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a2e8bc90-66ec-41a4-9b07-93e60fac16b8",
+ "metadata": {},
+ "source": [
+ "# Setup"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "e58b2aff-cc94-443b-a124-e8698fc99160",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import jax\n",
+ "import numpy as np\n",
+ "if False:\n",
+ " jax.config.update(\"jax_enable_x64\", True)\n",
+ " DTYPE = np.float64\n",
+ "else:\n",
+ " DTYPE = np.float32"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "5f74aac1-e643-453a-ac52-f241d6528158",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import abc\n",
+ "import asyncio\n",
+ "import collections\n",
+ "from collections.abc import Callable\n",
+ "import copy\n",
+ "import contextlib\n",
+ "import dataclasses\n",
+ "import functools\n",
+ "import io\n",
+ "import time\n",
+ "from typing import Any, NamedTuple, Optional, TypeVar\n",
+ "import traceback\n",
+ "\n",
+ "import fun_mc.using_jax as fun_mc\n",
+ "import ipywidgets\n",
+ "import jax.numpy as jnp\n",
+ "from jax.scipy.spatial.transform import Rotation\n",
+ "import matplotlib.pyplot as plt\n",
+ "import mediapy\n",
+ "import plotly.graph_objects as pgo\n",
+ "import pythreejs as p3\n",
+ "import tqdm.notebook\n",
+ "import tensorflow_probability.substrates.jax as tfp\n",
+ "import warnings\n",
+ "\n",
+ "tfd = tfp.distributions\n",
+ "tfb = tfp.bijectors"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "c8f52054-3aec-4a83-b3c5-6f19e655063b",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "INTERACTIVE_INFERENCE = None"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e1877db8-8b06-40ef-b548-fe42e1bacfdd",
+ "metadata": {},
+ "source": [
+ "# Utils"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0aedd388-5339-4f5a-b29c-c7a02e3bf2bb",
+ "metadata": {},
+ "source": [
+ "## Misc"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "ef2db63c-c851-4122-9268-00e0478e0fca",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def shape(*args):\n",
+ " if len(args) == 1:\n",
+ " args = args[0]\n",
+ " return jax.tree.map(jnp.shape, args)\n",
+ "\n",
+ "\n",
+ "def dtype(*args):\n",
+ " if len(args) == 1:\n",
+ " args = args[0]\n",
+ " return jax.tree.map(jnp.dtype, args)\n",
+ "\n",
+ "\n",
+ "def cast_floats(x, dtype=DTYPE):\n",
+ " def one_part(x):\n",
+ " if jnp.issubdtype(x.dtype, jnp.floating):\n",
+ " return x.astype(dtype)\n",
+ " else:\n",
+ " return x\n",
+ "\n",
+ " return jax.tree.map(one_part, x)\n",
+ "\n",
+ "\n",
+ "def to_html(color):\n",
+ " return (\n",
+ " f\"#{int(255 * color[0]):02X}{int(255 * color[1]):02X}{int(255 * color[2]):02X}\"\n",
+ " )\n",
+ "\n",
+ "\n",
+ "COLORS = [ # From https://mikemol.github.io/technique/colorblind/2018/02/11/color-safe-palette.html\n",
+ " \"#E69F00\",\n",
+ " \"#56B4E9\",\n",
+ " \"#009E73\",\n",
+ " \"#0072B2\",\n",
+ " \"#D55E00\",\n",
+ " \"#F0E442\",\n",
+ " \"#CC79A7\",\n",
+ "]\n",
+ "\n",
+ "new_order = [2, 3, 4, 0, 1, 5] # from luminance, reversed\n",
+ "COLORS = list(np.array(COLORS)[new_order])\n",
+ "\n",
+ "COLORS_NP = (\n",
+ " np.stack(\n",
+ " [\n",
+ " np.stack(\n",
+ " [\n",
+ " int(c[1:3], base=16),\n",
+ " int(c[3:5], base=16),\n",
+ " int(c[5:7], base=16),\n",
+ " ]\n",
+ " )\n",
+ " for c in COLORS\n",
+ " ]\n",
+ " )\n",
+ " / 255.0\n",
+ ")\n",
+ "\n",
+ "import cycler\n",
+ "\n",
+ "plt.rcParams[\"axes.prop_cycle\"] = cycler.cycler(color=COLORS)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d457f010-8658-4ef9-8fc4-1bee1412b958",
+ "metadata": {},
+ "source": [
+ "## Pose"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "7a1034bd-49d2-4e70-a628-4d958fbbadf8",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Pose(NamedTuple):\n",
+ " position: jax.Array\n",
+ " quaternion: jax.Array\n",
+ "\n",
+ " def rotation(self) -> Rotation:\n",
+ " return Rotation.from_quat(self.quaternion)\n",
+ "\n",
+ " def normalize(self) -> \"Pose\":\n",
+ " return Pose(self.position, self.rotation().as_quat())\n",
+ "\n",
+ " def apply(self, vec: jax.Array) -> jax.Array:\n",
+ " return self.rotation().apply(vec) + self.position\n",
+ "\n",
+ " def compose(self, other: \"Pose\") -> \"Pose\":\n",
+ " new_position = self.apply(other.position)\n",
+ " new_quaternion = (self.rotation() * other.rotation()).as_quat()\n",
+ " return Pose(new_position, new_quaternion)\n",
+ "\n",
+ " def inv(self) -> \"Pose\":\n",
+ " inv_rot = self.rotation().inv()\n",
+ " return Pose(-inv_rot.apply(self.position), inv_rot.as_quat())\n",
+ "\n",
+ " @classmethod\n",
+ " def identity(cls) -> \"Pose\":\n",
+ " return cls(position=jnp.zeros(3), quaternion=jnp.array([0.0, 0.0, 0.0, 1.0]))\n",
+ "\n",
+ " def __getitem__(self, idx):\n",
+ " return jax.tree.map(lambda x: x[idx], self)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "4a11e5be-521a-4c6f-b30b-372697a99527",
+ "metadata": {},
+ "source": [
+ "## Camera"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "df60ca03-b1b0-432b-a6cd-e241b407cd8a",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "@functools.partial(jax.vmap, in_axes=(0, None))\n",
+ "def screen_from_camera(xyz, camera_pose):\n",
+ " with jax.default_matmul_precision(\"float32\"):\n",
+ " cam_xyz = camera_pose.inv().apply(xyz)\n",
+ " x, y, z = cam_xyz\n",
+ " # HACK: Why does this work?\n",
+ " z = jnp.where(z < 1e-3, 1e-3, z)\n",
+ " u = x / z\n",
+ " v = y / z\n",
+ " return jnp.stack([u, v], axis=-1)\n",
+ "\n",
+ "\n",
+ "def homogeneous_coordinates(uv, z=np.array(1.0, DTYPE)):\n",
+ " return (\n",
+ " jnp.concatenate([uv, jnp.ones_like(uv[..., :1])], axis=-1) * z[..., jnp.newaxis]\n",
+ " )\n",
+ "\n",
+ "\n",
+ "def look_at_quat(position, target, up=np.array([0.0, 0.0, 1.0], DTYPE)):\n",
+ " z = target - position\n",
+ " z = z / jnp.linalg.norm(z)\n",
+ "\n",
+ " x = jnp.cross(z, up)\n",
+ " x = x / jnp.linalg.norm(x)\n",
+ "\n",
+ " y = jnp.cross(z, x)\n",
+ " y = y / jnp.linalg.norm(y)\n",
+ "\n",
+ " rotation_matrix = jnp.hstack([x.reshape(-1, 1), y.reshape(-1, 1), z.reshape(-1, 1)])\n",
+ " return Rotation.from_matrix(rotation_matrix).as_quat()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8eb317d4-d0d2-4341-8cc7-e18c394c146c",
+ "metadata": {},
+ "source": [
+ "## Scene"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "2e1a7cc3-a78c-4b5f-b9f9-375cf31e87a7",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "@dataclasses.dataclass\n",
+ "class Scene:\n",
+ " keypoint_world_positions: jax.Array\n",
+ " keypoint_colors: jax.Array\n",
+ " keypoint_screen_positions: jax.Array\n",
+ " camera_poses: jax.Array\n",
+ " keypoint_visibility: jax.Array\n",
+ "\n",
+ " @property\n",
+ " def num_frames(self) -> int:\n",
+ " return self.keypoint_screen_positions.shape[0]\n",
+ "\n",
+ " @property\n",
+ " def num_keypoints(self) -> int:\n",
+ " return self.keypoint_screen_positions.shape[1]\n",
+ "\n",
+ "\n",
+ "def make_scene(obj, camera_poses, max_num_points):\n",
+ " points, rgbs = scene_obj.spawn_points(max_num_points, jax.random.key(0))\n",
+ " visibility = ~jax.lax.map(\n",
+ " lambda camera_pos: cast_ray_one_frame(camera_pos, points, scene_obj),\n",
+ " camera_positions,\n",
+ " )\n",
+ " uvs = jax.lax.map(\n",
+ " lambda camera_pose: screen_from_camera(points, camera_pose), camera_poses\n",
+ " )\n",
+ " visibility &= jnp.linalg.norm(uvs, axis=-1, ord=jnp.inf) < 1\n",
+ " valid_keypoints = visibility.any(0)\n",
+ "\n",
+ " return Scene(\n",
+ " keypoint_world_positions=points[valid_keypoints],\n",
+ " keypoint_colors=rgbs[valid_keypoints],\n",
+ " keypoint_screen_positions=uvs[:, valid_keypoints],\n",
+ " keypoint_visibility=visibility[:, valid_keypoints],\n",
+ " camera_poses=camera_poses,\n",
+ " )\n",
+ "\n",
+ "\n",
+ "@dataclasses.dataclass\n",
+ "class SceneInfo:\n",
+ " object_mask: jax.Array\n",
+ " object_positions: jax.Array"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a5ef967e-7b37-4597-ac05-47f5d35ea91e",
+ "metadata": {},
+ "source": [
+ "## Scene Rendering"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "id": "8cf10595-e57e-4689-9f43-0a8ff0881d54",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "POINT_VERTEX = \"\"\"\n",
+ "attribute float size;\n",
+ "attribute vec3 color;\n",
+ "\n",
+ "varying vec3 var_color;\n",
+ "varying float var_size;\n",
+ "\n",
+ "void main() {\n",
+ " var_color = color;\n",
+ " gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n",
+ " gl_PointSize = size;\n",
+ " var_size = size;\n",
+ "}\n",
+ "\"\"\"\n",
+ "\n",
+ "POINT_FRAGMENT = \"\"\"\n",
+ "varying vec3 var_color;\n",
+ "varying float var_size;\n",
+ "\n",
+ "void main() {\n",
+ " gl_FragColor = vec4(var_color, 1.0);\n",
+ " if (var_size < 0.0001)\n",
+ " discard;\n",
+ "}\n",
+ "\"\"\"\n",
+ "\n",
+ "\n",
+ "def octahedron():\n",
+ " vertices = np.array(\n",
+ " [\n",
+ " [0, 1, 0],\n",
+ " [1, 0, 0],\n",
+ " [0, 0, 1],\n",
+ " [-1, 0, 0],\n",
+ " [0, 0, -1],\n",
+ " [0, -1, 0],\n",
+ " ]\n",
+ " ).astype(np.float32)\n",
+ "\n",
+ " indices = (\n",
+ " np.array(\n",
+ " [\n",
+ " [1, 0, 2],\n",
+ " [2, 0, 3],\n",
+ " [3, 0, 4],\n",
+ " [4, 0, 1],\n",
+ " [2, 5, 1],\n",
+ " [3, 5, 2],\n",
+ " [4, 5, 3],\n",
+ " [1, 5, 4],\n",
+ " ]\n",
+ " )\n",
+ " .astype(np.uint32)\n",
+ " .ravel()\n",
+ " )\n",
+ " return vertices, indices\n",
+ "\n",
+ "\n",
+ "def camera_frustum():\n",
+ " vertices = np.array(\n",
+ " [\n",
+ " [0, 0, 0],\n",
+ " [1, 1, 1],\n",
+ " [-1, 1, 1],\n",
+ " [-1, -1, 1],\n",
+ " [1, -1, 1],\n",
+ " [-1, -1.1, 1],\n",
+ " [0, -1.5, 1],\n",
+ " [1, -1.1, 1],\n",
+ " ]\n",
+ " ).astype(np.float32)\n",
+ "\n",
+ " indices = (\n",
+ " np.array(\n",
+ " [\n",
+ " [0, 1, 2],\n",
+ " [0, 2, 3],\n",
+ " [0, 3, 4],\n",
+ " [0, 4, 1],\n",
+ " [5, 6, 7],\n",
+ " ]\n",
+ " )\n",
+ " .astype(np.uint32)\n",
+ " .ravel()\n",
+ " )\n",
+ " return vertices, indices\n",
+ "\n",
+ "\n",
+ "@jax.jit\n",
+ "@functools.partial(jax.vmap, in_axes=(None, 0, 0))\n",
+ "def transform(xs, cov, loc):\n",
+ " v, s, _ = jnp.linalg.svd(cov)\n",
+ " return (v @ (jnp.sqrt(s) * xs).T).T + loc\n",
+ "\n",
+ "\n",
+ "@jax.jit\n",
+ "@functools.partial(jax.vmap, in_axes=(None, 0, 0))\n",
+ "def transform_pose(xs, quat, loc):\n",
+ " return Rotation(quat).apply(xs) + loc\n",
+ "\n",
+ "\n",
+ "@jax.jit\n",
+ "@functools.partial(jax.vmap, in_axes=(1,))\n",
+ "def get_loc_cov(xs):\n",
+ " return (\n",
+ " xs.mean(0),\n",
+ " jnp.cov(xs, rowvar=False),\n",
+ " )\n",
+ "\n",
+ "\n",
+ "@dataclasses.dataclass\n",
+ "class PointsDisplay:\n",
+ " def __init__(self, positions, colors, point_size=4.0):\n",
+ " self.point_size = point_size\n",
+ "\n",
+ " point_material = p3.ShaderMaterial(\n",
+ " vertexShader=POINT_VERTEX,\n",
+ " fragmentShader=POINT_FRAGMENT,\n",
+ " )\n",
+ "\n",
+ " self.points_geometry = p3.BufferGeometry(\n",
+ " attributes={\n",
+ " \"position\": p3.BufferAttribute(array=positions),\n",
+ " \"color\": p3.BufferAttribute(array=colors),\n",
+ " \"size\": p3.BufferAttribute(\n",
+ " array=self.point_size * np.ones(colors.shape[0], dtype=np.float32)\n",
+ " ),\n",
+ " }\n",
+ " )\n",
+ " self.points = p3.Points(\n",
+ " self.points_geometry,\n",
+ " point_material,\n",
+ " )\n",
+ "\n",
+ " def set_state(\n",
+ " self,\n",
+ " positions,\n",
+ " size=None,\n",
+ " ):\n",
+ " self.points_geometry.attributes[\"position\"].array = positions\n",
+ "\n",
+ " def set_mask(self, mask):\n",
+ " self.points_geometry.attributes[\"size\"].array = self.point_size * mask.astype(\n",
+ " np.float32\n",
+ " )\n",
+ "\n",
+ " @property\n",
+ " def objects(self):\n",
+ " return [self.points]\n",
+ "\n",
+ "\n",
+ "@dataclasses.dataclass\n",
+ "class CameraDisplay:\n",
+ " def __init__(\n",
+ " self,\n",
+ " positions,\n",
+ " quaternions,\n",
+ " color,\n",
+ " size=0.05,\n",
+ " ):\n",
+ " self.vertices, self.indices = camera_frustum()\n",
+ " self.vertices = size * self.vertices\n",
+ " point_material = p3.ShaderMaterial(\n",
+ " vertexShader=POINT_VERTEX,\n",
+ " fragmentShader=POINT_FRAGMENT,\n",
+ " wireframe=True,\n",
+ " )\n",
+ "\n",
+ " self.cameras_geometry = p3.BufferGeometry(\n",
+ " attributes={\n",
+ " \"position\": p3.BufferAttribute(\n",
+ " array=transform_pose(\n",
+ " self.vertices,\n",
+ " quaternions,\n",
+ " positions,\n",
+ " )\n",
+ " ),\n",
+ " \"color\": p3.BufferAttribute(\n",
+ " array=np.repeat(\n",
+ " np.asarray(color)[np.newaxis],\n",
+ " positions.shape[0] * len(self.vertices),\n",
+ " axis=0,\n",
+ " ).astype(np.float32)\n",
+ " ),\n",
+ " \"size\": p3.BufferAttribute(\n",
+ " array=np.ones(\n",
+ " positions.shape[0] * len(self.vertices),\n",
+ " dtype=np.float32,\n",
+ " )\n",
+ " ),\n",
+ " \"index\": p3.BufferAttribute(\n",
+ " array=(\n",
+ " self.indices[np.newaxis]\n",
+ " + len(self.vertices)\n",
+ " * np.arange(positions.shape[0])[:, jnp.newaxis]\n",
+ " )\n",
+ " .astype(np.uint32)\n",
+ " .ravel(),\n",
+ " ),\n",
+ " }\n",
+ " )\n",
+ " self.cameras = p3.Mesh(\n",
+ " self.cameras_geometry,\n",
+ " point_material,\n",
+ " )\n",
+ "\n",
+ " def set_state(\n",
+ " self,\n",
+ " positions,\n",
+ " quaternions,\n",
+ " ):\n",
+ " self.cameras_geometry.attributes[\"position\"].array = transform_pose(\n",
+ " self.vertices,\n",
+ " quaternions,\n",
+ " positions,\n",
+ " )\n",
+ "\n",
+ " def set_mask(self, mask):\n",
+ " self.cameras_geometry.attributes[\"size\"].array = np.repeat(\n",
+ " mask.astype(np.float32),\n",
+ " np.full(mask.shape[0], len(self.vertices)),\n",
+ " axis=0,\n",
+ " )\n",
+ "\n",
+ " @property\n",
+ " def objects(self):\n",
+ " return [self.cameras]\n",
+ "\n",
+ "\n",
+ "@dataclasses.dataclass\n",
+ "class BlobDisplay:\n",
+ " def __init__(\n",
+ " self,\n",
+ " positions,\n",
+ " covariances,\n",
+ " colors,\n",
+ " ):\n",
+ " self.vertices, self.indices = octahedron()\n",
+ " point_material = p3.ShaderMaterial(\n",
+ " vertexShader=POINT_VERTEX,\n",
+ " fragmentShader=POINT_FRAGMENT,\n",
+ " )\n",
+ "\n",
+ " self.blobs_geometry = p3.BufferGeometry(\n",
+ " attributes={\n",
+ " \"position\": p3.BufferAttribute(\n",
+ " array=transform(\n",
+ " self.vertices,\n",
+ " covariances,\n",
+ " positions,\n",
+ " )\n",
+ " ),\n",
+ " \"color\": p3.BufferAttribute(\n",
+ " array=np.repeat(\n",
+ " colors,\n",
+ " np.full(colors.shape[0], len(self.vertices)),\n",
+ " axis=0,\n",
+ " )\n",
+ " ),\n",
+ " \"size\": p3.BufferAttribute(\n",
+ " array=np.ones(\n",
+ " colors.shape[0] * len(self.vertices),\n",
+ " dtype=np.float32,\n",
+ " )\n",
+ " ),\n",
+ " \"index\": p3.BufferAttribute(\n",
+ " array=(\n",
+ " self.indices[np.newaxis]\n",
+ " + len(self.vertices)\n",
+ " * np.arange(colors.shape[0])[:, jnp.newaxis]\n",
+ " )\n",
+ " .astype(np.uint32)\n",
+ " .ravel(),\n",
+ " ),\n",
+ " }\n",
+ " )\n",
+ " self.blobs = p3.Mesh(\n",
+ " self.blobs_geometry,\n",
+ " point_material,\n",
+ " )\n",
+ "\n",
+ " def set_state(\n",
+ " self,\n",
+ " positions,\n",
+ " covariances,\n",
+ " ):\n",
+ " self.blobs_geometry.attributes[\"position\"].array = transform(\n",
+ " self.vertices,\n",
+ " covariances,\n",
+ " positions,\n",
+ " )\n",
+ "\n",
+ " def set_mask(self, mask):\n",
+ " self.blobs_geometry.attributes[\"size\"].array = np.repeat(\n",
+ " mask.astype(np.float32),\n",
+ " np.full(mask.shape[0], len(self.vertices)),\n",
+ " axis=0,\n",
+ " )\n",
+ "\n",
+ " @property\n",
+ " def objects(self):\n",
+ " return [self.blobs]\n",
+ "\n",
+ "\n",
+ "@dataclasses.dataclass\n",
+ "class SceneRenderer:\n",
+ " def __init__(self, objects, up=[0, 0, 1]):\n",
+ " width = 800\n",
+ " height = 600\n",
+ "\n",
+ " camera = p3.PerspectiveCamera(\n",
+ " position=[5, 5, 5],\n",
+ " up=up,\n",
+ " aspect=width / height,\n",
+ " )\n",
+ " # grid = p3.GridHelper(10, 10)\n",
+ " # grid.rotateX(np.pi / 2)\n",
+ " self.scene = p3.Scene(\n",
+ " children=[camera, p3.AxesHelper(1)] + objects, background=\"black\"\n",
+ " )\n",
+ " self.renderer = p3.Renderer(\n",
+ " camera=camera,\n",
+ " scene=self.scene,\n",
+ " controls=[p3.OrbitControls(controlling=camera)],\n",
+ " width=width,\n",
+ " height=height,\n",
+ " )\n",
+ "\n",
+ " def _ipython_display_(self):\n",
+ " display(self.renderer)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b1aa9d0d-ab50-49ba-9fdf-0fb9d814e149",
+ "metadata": {},
+ "source": [
+ "## DirectionRadius"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "id": "95220046-9a0d-49fc-b569-9da736c79566",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class DirectionRadius(tfd.Distribution):\n",
+ " def __init__(\n",
+ " self,\n",
+ " ndims,\n",
+ " sphere_dist,\n",
+ " radius_dist,\n",
+ " allow_nan_stats=True,\n",
+ " validate_args=False,\n",
+ " name=\"DirectionRadius\",\n",
+ " ):\n",
+ " parameters = dict(locals())\n",
+ " self.ndims = ndims\n",
+ " self.sphere_dist = sphere_dist\n",
+ " self.radius_dist = radius_dist\n",
+ " super().__init__(\n",
+ " dtype=radius_dist.dtype,\n",
+ " reparameterization_type=tfd.FULLY_REPARAMETERIZED,\n",
+ " allow_nan_stats=allow_nan_stats,\n",
+ " validate_args=validate_args,\n",
+ " parameters=parameters,\n",
+ " name=name,\n",
+ " )\n",
+ "\n",
+ " def _parameter_properties(self, dtype=None, num_classes=None):\n",
+ " return dict(\n",
+ " sphere_dist=tfp.util.BatchedComponentProperties(),\n",
+ " radius_dist=tfp.util.BatchedComponentProperties(),\n",
+ " )\n",
+ "\n",
+ " def _sample_n(self, n, seed):\n",
+ " pos_seed, rad_seed = jax.random.split(seed)\n",
+ " if self.sphere_dist is None:\n",
+ " pos = jax.random.normal(\n",
+ " pos_seed,\n",
+ " (\n",
+ " n,\n",
+ " self.ndims,\n",
+ " ),\n",
+ " self.dtype,\n",
+ " )\n",
+ " # TODO: Properly handle sampling the origin.\n",
+ " pos /= jnp.maximum(\n",
+ " jnp.finfo(self.dtype).eps, jnp.linalg.norm(pos, axis=-1, keepdims=True)\n",
+ " )\n",
+ " else:\n",
+ " pos = self.sphere_dist.sample(n, seed=pos_seed)\n",
+ " rad = self.radius_dist.sample(n, seed=rad_seed)\n",
+ " return rad[:, jnp.newaxis] * pos\n",
+ "\n",
+ " def _log_prob(self, x):\n",
+ " rad = jnp.linalg.norm(x, axis=-1)\n",
+ " n = self.ndims\n",
+ " if self.sphere_dist is None:\n",
+ " log_z = (\n",
+ " jnp.log(2).astype(self.dtype)\n",
+ " + n / 2.0 * jnp.log(jnp.pi).astype(self.dtype)\n",
+ " - jax.scipy.special.gammaln(n / 2)\n",
+ " )\n",
+ " sphere_lp = -log_z\n",
+ " else:\n",
+ " sphere_lp = self.sphere_dist.log_prob(x / rad[..., jnp.newaxis])\n",
+ " # TODO: Properly handle evaluating at the origin.\n",
+ " res = (\n",
+ " self.radius_dist.log_prob(rad)\n",
+ " - (n - 1) * jnp.log(rad).astype(self.dtype)\n",
+ " + sphere_lp\n",
+ " )\n",
+ " return res\n",
+ "\n",
+ " @property\n",
+ " def event_shape(self):\n",
+ " return tfp.tf2jax.TensorShape([self.ndims])\n",
+ "\n",
+ " def event_shape_tensor(self):\n",
+ " return jnp.asarray(self.event_shape)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d5ff0ac0-f6d5-4eb0-b2b4-5e5056191065",
+ "metadata": {},
+ "source": [
+ "## DonutDist"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "id": "be549d2e-2a79-4b33-988d-19c5770e87a6",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def make_donut_dist(n, loc, dir, conc, r, k):\n",
+ " dd = tfd.PowerSpherical(dir, conc)\n",
+ " # rd = tfd.Chi2(k)\n",
+ " rd = tfd.Gamma(k, 1.0)\n",
+ " return tfb.Chain([tfb.Shift(loc), tfb.Scale(r / rd.mode())])(\n",
+ " DirectionRadius(n, dd, rd)\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d579234f-1501-49e5-bbe1-19e6a43146a5",
+ "metadata": {},
+ "source": [
+ "## InverseGamma"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "id": "7e82592a-211d-436c-b1e4-64997cbb0719",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "d = tfd.InverseGamma(jnp.array(2.0, DTYPE), 1.0)\n",
+ "inverse_gamma_bij = d.experimental_default_event_space_bijector()\n",
+ "\n",
+ "\n",
+ "def make_unc_inverse_gamma(*args):\n",
+ " return tfb.Invert(inverse_gamma_bij)(tfd.InverseGamma(*args))\n",
+ "\n",
+ "\n",
+ "observation_noise_scale_bij = inverse_gamma_bij"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b73fa2bc-ae62-4326-ac9d-9bee6de6ffc9",
+ "metadata": {},
+ "source": [
+ "## Objects"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "id": "f0d0805b-1f60-4140-8c11-0dadd8f93b7d",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "COLLISION_EPS = 1e-3\n",
+ "\n",
+ "\n",
+ "class Cuboid(NamedTuple):\n",
+ " position: jax.Array\n",
+ " size: jax.Array\n",
+ " color: jax.Array\n",
+ "\n",
+ " def min(self):\n",
+ " return self.position - self.size / 2\n",
+ "\n",
+ " def max(self):\n",
+ " return self.position + self.size / 2\n",
+ "\n",
+ " def contains(self, point: jax.Array) -> jax.Array:\n",
+ " return (\n",
+ " (point > (self.min() + COLLISION_EPS))\n",
+ " & (point < (self.max() - COLLISION_EPS))\n",
+ " ).all()\n",
+ "\n",
+ " def spawn_points(self, num_points, seed):\n",
+ " points = jax.random.normal(seed, [num_points, 3])\n",
+ " # This isn't a great way to do this, density is not uniform on each face.\n",
+ " points /= jnp.linalg.norm(points, axis=-1, ord=jnp.inf, keepdims=True)\n",
+ " return self.position + self.size / 2 * points, jnp.broadcast_to(\n",
+ " self.color, [num_points, 3]\n",
+ " )\n",
+ "\n",
+ "\n",
+ "class Sphere(NamedTuple):\n",
+ " position: jax.Array\n",
+ " size: jax.Array\n",
+ " color: jax.Array\n",
+ "\n",
+ " def contains(self, point: jax.Array) -> jax.Array:\n",
+ " return (\n",
+ " jnp.linalg.norm(point - self.position, axis=-1)\n",
+ " < self.size / 2 - COLLISION_EPS\n",
+ " )\n",
+ "\n",
+ " def spawn_points(self, num_points, seed):\n",
+ " points = jax.random.normal(seed, [num_points, 3])\n",
+ " points /= jnp.linalg.norm(points, axis=-1, keepdims=True)\n",
+ " return self.position + self.size / 2 * points, jnp.broadcast_to(\n",
+ " self.color, [num_points, 3]\n",
+ " )\n",
+ "\n",
+ "\n",
+ "class MultiObject(NamedTuple):\n",
+ " objects: Any\n",
+ "\n",
+ " def contains(self, point: jax.Array) -> jax.Array:\n",
+ " contains = jnp.array(False)\n",
+ " for obj in self.objects:\n",
+ " contains |= obj.contains(point)\n",
+ " return contains\n",
+ "\n",
+ " def spawn_points(self, num_points, seed):\n",
+ " all_points = []\n",
+ " all_rgbs = []\n",
+ " for i, obj in enumerate(self.objects):\n",
+ " n = (i + 1) * num_points // (len(self.objects)) - i * num_points // (\n",
+ " len(self.objects)\n",
+ " )\n",
+ " points, rgbs = obj.spawn_points(n, jax.random.fold_in(seed, i))\n",
+ " all_points.append(points)\n",
+ " all_rgbs.append(rgbs)\n",
+ " return jnp.concatenate(all_points, 0), jnp.concatenate(all_rgbs, 0)\n",
+ "\n",
+ "\n",
+ "def cast_ray(source, target, obj, num_points=50):\n",
+ " t = jnp.linspace(0.0, 1.0, num_points)\n",
+ " points = source + (target - source) * t[:, jnp.newaxis]\n",
+ " hit = jax.vmap(obj.contains)(points)\n",
+ " return hit.any()\n",
+ "\n",
+ "\n",
+ "cast_ray_one_frame = jax.vmap(cast_ray, in_axes=(None, 0, None))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8a5ed230-5a5e-4052-bf97-930925f64e55",
+ "metadata": {},
+ "source": [
+ "## Effect Handling"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "id": "b6dfce97-903e-4365-8e0a-acd4b4b72bcc",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "T = TypeVar(\"T\")\n",
+ "\n",
+ "\n",
+ "@dataclasses.dataclass\n",
+ "class Effect:\n",
+ " name: str\n",
+ "\n",
+ " def set_value(self, value: Any) -> Any:\n",
+ " raise NotImplementedError(f\"{type(self)}\")\n",
+ "\n",
+ " def value(self) -> Any:\n",
+ " raise NotImplementedError(f\"{type(self)}\")\n",
+ "\n",
+ "\n",
+ "@dataclasses.dataclass\n",
+ "class SampleEffect(Effect):\n",
+ " dist: tfd.Distribution\n",
+ "\n",
+ " def set_value(self, value: Any) -> \"SampleValueEffect\":\n",
+ " return SampleValueEffect(name=self.name, dist=self.dist, value_=value)\n",
+ "\n",
+ "\n",
+ "@dataclasses.dataclass\n",
+ "class SampleValueEffect(Effect):\n",
+ " dist: tfd.Distribution\n",
+ " value_: Optional[Any] = None\n",
+ "\n",
+ " def set_value(self, value: Any) -> \"SampleValueEffect\":\n",
+ " return dataclasses.replace(self, value_=value)\n",
+ "\n",
+ " def value(self):\n",
+ " return self.value_\n",
+ "\n",
+ "\n",
+ "@dataclasses.dataclass\n",
+ "class Handler(metaclass=abc.ABCMeta):\n",
+ " @abc.abstractmethod\n",
+ " def __call__(self, effect: Effect) -> tuple[Any, Effect]:\n",
+ " pass\n",
+ "\n",
+ " def result(self) -> dict[str, Any]:\n",
+ " return {}\n",
+ "\n",
+ "\n",
+ "@dataclasses.dataclass\n",
+ "class LogProb(Handler):\n",
+ " log_prob: jnp.ndarray = dataclasses.field(\n",
+ " default_factory=lambda: jnp.zeros([], DTYPE)\n",
+ " )\n",
+ "\n",
+ " def __call__(self, effect: Effect) -> tuple[Any, Effect]:\n",
+ " res = self\n",
+ " if isinstance(effect, SampleValueEffect):\n",
+ " res = dataclasses.replace(\n",
+ " res, log_prob=res.log_prob + effect.dist.log_prob(effect.value())\n",
+ " )\n",
+ " return res, effect\n",
+ "\n",
+ " def result(self):\n",
+ " return {\"log_prob\": self.log_prob}\n",
+ "\n",
+ "\n",
+ "@dataclasses.dataclass\n",
+ "class Sample(Handler):\n",
+ " seed: jax.Array\n",
+ "\n",
+ " def __call__(self, effect: Effect) -> tuple[Any, Effect]:\n",
+ " res = self\n",
+ " if isinstance(effect, SampleEffect):\n",
+ " new_seed, seed = jax.random.split(self.seed)\n",
+ " res = dataclasses.replace(res, seed=new_seed)\n",
+ " effect = SampleValueEffect(\n",
+ " name=effect.name,\n",
+ " dist=effect.dist,\n",
+ " value_=effect.dist.sample(seed=seed),\n",
+ " )\n",
+ " return res, effect\n",
+ "\n",
+ "\n",
+ "@dataclasses.dataclass\n",
+ "class SetValues(Handler):\n",
+ " values: dict[str, jnp.ndarray]\n",
+ "\n",
+ " def __call__(self, effect: Effect) -> tuple[Any, Effect]:\n",
+ " value = self.values.get(effect.name)\n",
+ " if value is not None:\n",
+ " effect = effect.set_value(value)\n",
+ " return self, effect\n",
+ "\n",
+ "\n",
+ "@dataclasses.dataclass\n",
+ "class Collect(Handler):\n",
+ " get_fn: Callable[[Effect], Any] = lambda x: x\n",
+ " filter_fn: Callable[[Effect], bool] = lambda x: True\n",
+ " results: dict[str, Any] = dataclasses.field(default_factory=dict)\n",
+ "\n",
+ " def __call__(self, effect: Effect) -> tuple[Any, Effect]:\n",
+ " res = self\n",
+ " if self.filter_fn(effect):\n",
+ " res = dataclasses.replace(\n",
+ " res, results={effect.name: self.get_fn(effect), **res.results}\n",
+ " )\n",
+ " return res, effect\n",
+ "\n",
+ " def result(self):\n",
+ " return {\"results\": self.results}\n",
+ "\n",
+ "\n",
+ "_handler_stack = []\n",
+ "\n",
+ "\n",
+ "@contextlib.contextmanager\n",
+ "def apply_handlers(*handlers: Handler):\n",
+ " global _handler_stack\n",
+ " old_len = len(_handler_stack)\n",
+ " _handler_stack.extend(handlers)\n",
+ " res = {}\n",
+ " try:\n",
+ " yield res\n",
+ " finally:\n",
+ " for handler in _handler_stack[old_len:]:\n",
+ " res.update(handler.result())\n",
+ " _handler_stack = _handler_stack[:old_len]\n",
+ "\n",
+ "\n",
+ "def effect(effect: Effect) -> Any:\n",
+ " for i in range(len(_handler_stack)):\n",
+ " _handler_stack[i], effect = _handler_stack[i](effect)\n",
+ " return effect.value()\n",
+ "\n",
+ "\n",
+ "def sample(name: str, dist: tfd.Distribution) -> jnp.ndarray:\n",
+ " return effect(SampleEffect(name=name, dist=dist))\n",
+ "\n",
+ "\n",
+ "def model_sample(\n",
+ " model_fn: Callable[[], T], seed: jax.Array\n",
+ ") -> tuple[dict[str, Any], T]:\n",
+ " with apply_handlers(Sample(seed), Collect(lambda e: e.value())) as trace:\n",
+ " res = model_fn()\n",
+ "\n",
+ " return trace[\"results\"], res\n",
+ "\n",
+ "\n",
+ "def model_cond_sample(\n",
+ " model_fn: Callable[[], T], value: dict[str, Any], seed: jax.Array\n",
+ ") -> tuple[dict[str, Any], T]:\n",
+ " with apply_handlers(\n",
+ " SetValues(value), Sample(seed), Collect(lambda e: e.value())\n",
+ " ) as trace:\n",
+ " res = model_fn()\n",
+ "\n",
+ " return trace[\"results\"], res\n",
+ "\n",
+ "\n",
+ "def model_log_prob(\n",
+ " model_fn: Callable[[], T], value: dict[str, Any]\n",
+ ") -> tuple[jnp.ndarray, T]:\n",
+ " with apply_handlers(SetValues(value), LogProb()) as trace:\n",
+ " res = model_fn()\n",
+ "\n",
+ " return trace[\"log_prob\"], res\n",
+ "\n",
+ "\n",
+ "def model_log_prob_ratio(\n",
+ " model_fn: Callable[[], T],\n",
+ " value1: dict[str, Any],\n",
+ " value2: dict[str, Any],\n",
+ ") -> tuple[jnp.ndarray, T]:\n",
+ " with apply_handlers(\n",
+ " SetValues(value1), Collect(lambda e: (e.dist, e.value()))\n",
+ " ) as trace1:\n",
+ " res1 = model_fn()\n",
+ "\n",
+ " with apply_handlers(\n",
+ " SetValues(value2), Collect(lambda e: (e.dist, e.value()))\n",
+ " ) as trace2:\n",
+ " res2 = model_fn()\n",
+ "\n",
+ " log_prob_ratio = 0\n",
+ " for k in trace1[\"results\"].keys():\n",
+ " d1, x1 = trace1[\"results\"][k]\n",
+ " d2, x2 = trace2[\"results\"][k]\n",
+ " log_prob_ratio += tfp.experimental.distributions.log_prob_ratio(d1, x1, d2, x2)\n",
+ "\n",
+ " return log_prob_ratio, [res1, res2]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "id": "ac77ac27-ac35-4803-8e44-64be52c387c4",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "-2.7931314 -3.092115 0.29898357\n",
+ "0.2989837\n"
+ ]
+ }
+ ],
+ "source": [
+ "def model():\n",
+ " x = sample(\"x\", tfd.Normal(0.0, 1.0))\n",
+ " y = sample(\"y\", tfd.Normal(x, 1.0))\n",
+ " return [x, y]\n",
+ "\n",
+ "\n",
+ "s1, _ = model_sample(model, jax.random.key(0))\n",
+ "s2, _ = model_sample(model, jax.random.key(1))\n",
+ "lp1 = model_log_prob(model, s1)[0]\n",
+ "lp2 = model_log_prob(model, s2)[0]\n",
+ "print(lp1, lp2, lp1 - lp2)\n",
+ "print(model_log_prob_ratio(model, s1, s2)[0])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8d3e3546-21d7-49c3-9dcc-17860d1ca5df",
+ "metadata": {},
+ "source": [
+ "## UI Hyperparameter"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "id": "edac84b0-a3c6-42b3-9ef1-d22bc34ff8a3",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "@dataclasses.dataclass\n",
+ "class Hyperparameter:\n",
+ " value: Any\n",
+ " min: Any = None\n",
+ " max: Any = None\n",
+ " log_scale: bool = False\n",
+ "\n",
+ "\n",
+ "def make_hyperparameter_widget(\n",
+ " hparam,\n",
+ " name,\n",
+ " output=contextlib.nullcontext(),\n",
+ " callback_fn=lambda _: None,\n",
+ " toggle_style=\"\",\n",
+ " toggle_icons=(\"check-circle-o\", \"circle-o\"),\n",
+ "):\n",
+ " integer = np.issubdtype(np.array(hparam.value).dtype, np.integer)\n",
+ " boolean = np.issubdtype(np.array(hparam.value).dtype, np.bool_)\n",
+ "\n",
+ " def toggle_icon(val):\n",
+ " if val:\n",
+ " return toggle_icons[0]\n",
+ " else:\n",
+ " return toggle_icons[1]\n",
+ "\n",
+ " def on_value_change(change):\n",
+ " try:\n",
+ " hparam.value = change[\"new\"]\n",
+ " if boolean:\n",
+ " widget.icon = toggle_icon(hparam.value)\n",
+ " callback_fn(hparam.value)\n",
+ " except Exception:\n",
+ " with output:\n",
+ " print(traceback.format_exc())\n",
+ "\n",
+ " if boolean:\n",
+ " widget = ipywidgets.ToggleButton(\n",
+ " value=hparam.value,\n",
+ " description=name,\n",
+ " icon=toggle_icon(hparam.value),\n",
+ " button_style=toggle_style,\n",
+ " )\n",
+ "\n",
+ " elif integer:\n",
+ " widget = ipywidgets.IntSlider(\n",
+ " value=hparam.value,\n",
+ " min=hparam.min,\n",
+ " max=hparam.max,\n",
+ " description=name,\n",
+ " layout=ipywidgets.Layout(width=\"500px\"),\n",
+ " style={\"description_width\": \"200px\"},\n",
+ " )\n",
+ " elif hparam.log_scale:\n",
+ " widget = ipywidgets.FloatLogSlider(\n",
+ " value=hparam.value,\n",
+ " base=10,\n",
+ " min=np.log10(hparam.min),\n",
+ " max=np.log10(hparam.max),\n",
+ " step=0.05,\n",
+ " description=name,\n",
+ " layout=ipywidgets.Layout(width=\"500px\"),\n",
+ " style={\"description_width\": \"200px\"},\n",
+ " )\n",
+ " else:\n",
+ " widget = ipywidgets.FloatSlider(\n",
+ " value=hparam.value,\n",
+ " min=hparam.min,\n",
+ " max=hparam.max,\n",
+ " description=name,\n",
+ " layout=ipywidgets.Layout(width=\"500px\"),\n",
+ " style={\"description_width\": \"200px\"},\n",
+ " )\n",
+ " widget.observe(on_value_change, names=\"value\")\n",
+ " return widget"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "84f2c557-aa3a-4358-b966-0e67f7bbae2c",
+ "metadata": {},
+ "source": [
+ "# Synthetic Data"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "id": "b72b1f21-1893-41d4-9acd-078603fd0e3a",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "SCENE.num_frames=100, SCENE.num_keypoints=558\n",
+ "num object points 4\n"
+ ]
+ }
+ ],
+ "source": [
+ "scene_obj = MultiObject(\n",
+ " [\n",
+ " Cuboid(\n",
+ " position=jnp.array([0.0, 0.0, 1.0]),\n",
+ " size=jnp.array([1.0, 2.0, 3.0]),\n",
+ " color=COLORS_NP[0],\n",
+ " ),\n",
+ " Cuboid(\n",
+ " position=jnp.array([0.0, 0.0, 1.0]),\n",
+ " size=jnp.array([3.0, 1.0, 1.0]),\n",
+ " color=COLORS_NP[1],\n",
+ " ),\n",
+ " Sphere(\n",
+ " position=jnp.array([0.0, 1.0, 0.0]),\n",
+ " size=2.0,\n",
+ " color=COLORS_NP[2],\n",
+ " ),\n",
+ " ]\n",
+ ")\n",
+ "\n",
+ "num_frames = 100\n",
+ "raw_t = jnp.linspace(-1.0, 1.0, num_frames)\n",
+ "t = (1 + jnp.sign(raw_t) * jnp.abs(raw_t) ** 0.7) / 2\n",
+ "theta = jnp.pi / 4 + t * 3 * jnp.pi / 4\n",
+ "r = jnp.linspace(3.0, 4.0, num_frames)\n",
+ "x = r * jnp.cos(theta)\n",
+ "y = r * jnp.sin(theta)\n",
+ "z = -0.5 + t + jnp.cos(theta * 3)\n",
+ "camera_positions = jnp.stack([x, y, z], -1)\n",
+ "camera_quaternions = jax.vmap(look_at_quat, in_axes=(0, None))(\n",
+ " camera_positions, jnp.zeros(3)\n",
+ ")\n",
+ "camera_poses = Pose(camera_positions, camera_quaternions)\n",
+ "\n",
+ "SCENE = make_scene(scene_obj, camera_poses, 1000)\n",
+ "print(f\"{SCENE.num_frames=}, {SCENE.num_keypoints=}\")\n",
+ "\n",
+ "object_mask = SCENE.keypoint_visibility[0]\n",
+ "uv = SCENE.keypoint_screen_positions[0]\n",
+ "r = 0.07\n",
+ "object_mask = (\n",
+ " object_mask & (uv[:, 0] > -r) & (uv[:, 0] < +r) & (uv[:, 1] > -r) & (uv[:, 1] < +r)\n",
+ ")\n",
+ "object_positions = jnp.where(\n",
+ " object_mask[:, jnp.newaxis], SCENE.keypoint_world_positions, jnp.nan\n",
+ ")\n",
+ "print(\"num object points\", object_mask.sum())\n",
+ "\n",
+ "SCENE_INFO = SceneInfo(object_mask=object_mask, object_positions=object_positions)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "68385fef-8ab1-4b19-a78d-7e6768c2e468",
+ "metadata": {},
+ "source": [
+ "## Visualization"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "id": "907ec92f-3a10-4961-a53a-1a581d164880",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "79316223f395456a9b519b9307d0179b",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ " 0%| | 0/100 [00:00, ?it/s]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "
|
"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "frames = []\n",
+ "\n",
+ "for f in tqdm.notebook.tqdm(range(SCENE.num_frames)):\n",
+ " fig, ax = plt.subplots()\n",
+ " ax.set_xlim(-1, 1)\n",
+ " ax.set_ylim(-1, 1)\n",
+ " xy = SCENE.keypoint_screen_positions[f, SCENE.keypoint_visibility[f]]\n",
+ " c = np.where(SCENE.keypoint_visibility[f])[0]\n",
+ " ax.scatter(\n",
+ " xy[:, 0],\n",
+ " xy[:, 1],\n",
+ " c=SCENE.keypoint_colors[SCENE.keypoint_visibility[f]],\n",
+ " s=2,\n",
+ " )\n",
+ " ax.set_title(f\"Frame {f}\")\n",
+ " ax.set_aspect(\"equal\")\n",
+ " fig.canvas.draw()\n",
+ " frames.append(np.array(fig.canvas.buffer_rgba())[..., :3])\n",
+ " plt.close(fig)\n",
+ "mediapy.show_video(frames, fps=5)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "id": "35808b77-4515-45bc-b69b-e262c54063a3",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "fc8a042cc55e44ec88efb0010589f417",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ "Output()"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "e769f91833204616b6a1eab3b13e5a16",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ "HBox(children=(Label(value='Frame'), IntSlider(value=100, min=1)))"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "d22dfd988cfb49308f44742c25215d29",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ "Renderer(camera=PerspectiveCamera(aspect=1.3333333333333333, position=(5.0, 5.0, 5.0), projectionMatrix=(1.0, …"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "scene_points = PointsDisplay(\n",
+ " positions=SCENE.keypoint_world_positions,\n",
+ " colors=SCENE.keypoint_colors,\n",
+ " point_size=4,\n",
+ ")\n",
+ "\n",
+ "scene_blobs = BlobDisplay(\n",
+ " positions=SCENE.keypoint_world_positions,\n",
+ " covariances=np.repeat(0.01 * np.eye(3)[np.newaxis], SCENE.num_keypoints, axis=0),\n",
+ " colors=SCENE.keypoint_colors,\n",
+ ")\n",
+ "scene_blobs.set_mask(SCENE_INFO.object_mask)\n",
+ "\n",
+ "scene_camera = CameraDisplay(\n",
+ " positions=SCENE.camera_poses.position,\n",
+ " quaternions=SCENE.camera_poses.quaternion,\n",
+ " color=np.array([1.0, 0.0, 0.0]),\n",
+ ")\n",
+ "\n",
+ "scene_renderer = SceneRenderer(\n",
+ " scene_points.objects + scene_camera.objects + scene_blobs.objects\n",
+ ")\n",
+ "output = ipywidgets.Output()\n",
+ "slider = ipywidgets.IntSlider(\n",
+ " value=SCENE.num_frames,\n",
+ " min=1,\n",
+ " max=SCENE.num_frames,\n",
+ ")\n",
+ "\n",
+ "\n",
+ "def on_value_change(change):\n",
+ " try:\n",
+ " scene_points.set_mask(SCENE.keypoint_visibility[: change[\"new\"]].any(0))\n",
+ " scene_camera.set_mask(np.arange(SCENE.num_frames) < change[\"new\"])\n",
+ " except Exception as e:\n",
+ " with output:\n",
+ " print(e)\n",
+ "\n",
+ "\n",
+ "on_value_change({\"new\": SCENE.num_frames})\n",
+ "\n",
+ "slider.observe(on_value_change, names=\"value\")\n",
+ "\n",
+ "display(\n",
+ " output,\n",
+ " ipywidgets.HBox([ipywidgets.Label(\"Frame\"), slider]),\n",
+ " scene_renderer.renderer,\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "65ae8b68-8099-44d2-b6c3-2c837d278d7d",
+ "metadata": {},
+ "source": [
+ "# Model"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 89,
+ "id": "b31f3bc8-048c-470a-9ba6-fa699aed8c61",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class ModelArgs(NamedTuple):\n",
+ " camera_visibility: jax.Array\n",
+ " keypoint_visibility: jax.Array\n",
+ " observation_noise_degrees: jax.Array\n",
+ " center: jax.Array\n",
+ " radius: jax.Array\n",
+ " first_camera_pose: Pose\n",
+ " object_mask: jax.Array\n",
+ " object_positions: jax.Array\n",
+ " object_prior_scale: jax.Array\n",
+ "\n",
+ "\n",
+ "@dataclasses.dataclass(frozen=True)\n",
+ "class Model:\n",
+ " world_prior: str\n",
+ " camera_prior: str\n",
+ " num_frames: int\n",
+ " num_keypoints: int\n",
+ "\n",
+ " def model(self, args):\n",
+ " match self.world_prior:\n",
+ " case \"scale_free\":\n",
+ " n = self.num_keypoints * 3\n",
+ " raw_xyz = sample(\n",
+ " \"raw_keypoint_world_positions\",\n",
+ " make_donut_dist(\n",
+ " n,\n",
+ " jnp.zeros(n, DTYPE), # loc\n",
+ " jnp.zeros(n, DTYPE), # dir\n",
+ " jnp.array(0.0, DTYPE), # conc\n",
+ " jnp.array(args.radius, DTYPE), # rad\n",
+ " jnp.array(50.0, DTYPE), # k\n",
+ " ),\n",
+ " )\n",
+ " xyz = args.radius * raw_xyz / jnp.linalg.norm(raw_xyz, axis=-1)\n",
+ " xyz = xyz.reshape([self.num_keypoints, 3])\n",
+ " case \"pseudo_object\":\n",
+ " loc = jnp.zeros((self.num_keypoints, 3), DTYPE)\n",
+ " scale = jnp.full((self.num_keypoints, 3), args.radius)\n",
+ " loc = jnp.where(\n",
+ " args.object_mask[:, jnp.newaxis], args.object_positions, loc\n",
+ " )\n",
+ " # TODO: Constrain based on disparity?\n",
+ " scale = jnp.where(\n",
+ " args.object_mask[:, jnp.newaxis], args.object_prior_scale, scale\n",
+ " )\n",
+ "\n",
+ " xyz = sample(\n",
+ " \"keypoint_world_positions\",\n",
+ " tfd.Independent(tfd.Normal(loc, scale), 2),\n",
+ " )\n",
+ " match self.camera_prior:\n",
+ " case \"relative_noncentered\":\n",
+ " raise NotImplementedError()\n",
+ " case \"relative_centered\":\n",
+ " raise NotImplementedError()\n",
+ " case \"independent\":\n",
+ " camera_position = sample(\n",
+ " \"camera_positions\",\n",
+ " tfd.Independent(\n",
+ " tfd.Normal(\n",
+ " jnp.zeros((self.num_frames - 1, 3), DTYPE),\n",
+ " 20.0 * jnp.ones((self.num_frames - 1, 3), DTYPE),\n",
+ " ),\n",
+ " 2,\n",
+ " experimental_use_kahan_sum=True,\n",
+ " ),\n",
+ " )\n",
+ "\n",
+ " camera_raw_quaternion = sample(\n",
+ " \"camera_raw_quaternions\",\n",
+ " tfd.Sample(\n",
+ " make_donut_dist(\n",
+ " 4,\n",
+ " jnp.zeros(4, DTYPE), # loc\n",
+ " jnp.array([0.0, 0.0, 0.0, 1.0], DTYPE), # dir\n",
+ " jnp.array(0.0, DTYPE), # conc\n",
+ " jnp.array(1.0, DTYPE), # rad\n",
+ " jnp.array(20.0, DTYPE), # k\n",
+ " ),\n",
+ " self.num_frames - 1,\n",
+ " experimental_use_kahan_sum=True,\n",
+ " ),\n",
+ " )\n",
+ " camera_poses = Pose(camera_position, camera_raw_quaternion).normalize()\n",
+ "\n",
+ " camera_poses = jax.tree.map(\n",
+ " lambda x, y: jnp.concatenate([x[jnp.newaxis].astype(DTYPE), y], 0),\n",
+ " args.first_camera_pose,\n",
+ " camera_poses,\n",
+ " )\n",
+ "\n",
+ " if self.camera_prior == \"relative_noncentered\":\n",
+ "\n",
+ " def body(cur_pose, pose):\n",
+ " cur_pose = cur_pose.compose(pose)\n",
+ " return cur_pose, cur_pose\n",
+ "\n",
+ " _, camera_poses = jax.lax.scan(body, Pose.identity(), camera_poses)\n",
+ "\n",
+ " uv_loc = jax.vmap(lambda cp: screen_from_camera(xyz, cp))(camera_poses)\n",
+ "\n",
+ " raw_observation_noise_scale = sample(\n",
+ " \"raw_observation_noise_scale\",\n",
+ " make_unc_inverse_gamma(jnp.array(2.0, DTYPE), 0.01),\n",
+ " )\n",
+ " observation_noise_scale = observation_noise_scale_bij(\n",
+ " raw_observation_noise_scale\n",
+ " )\n",
+ "\n",
+ " uv = sample(\n",
+ " \"keypoint_screen_positions\",\n",
+ " tfd.Independent(\n",
+ " tfd.Masked(\n",
+ " tfd.StudentT(\n",
+ " args.observation_noise_degrees,\n",
+ " uv_loc,\n",
+ " observation_noise_scale,\n",
+ " ),\n",
+ " (args.keypoint_visibility)[..., jnp.newaxis],\n",
+ " ),\n",
+ " 3,\n",
+ " experimental_use_kahan_sum=True,\n",
+ " ),\n",
+ " )\n",
+ "\n",
+ " return {\n",
+ " \"keypoint_world_positions\": xyz,\n",
+ " \"camera_poses\": camera_poses,\n",
+ " \"keypoint_screen_positions\": uv,\n",
+ " \"observation_noise_scale\": observation_noise_scale,\n",
+ " \"l1_errors\": jnp.linalg.norm(uv - uv_loc, axis=-1, ord=1),\n",
+ " }\n",
+ "\n",
+ " @functools.partial(jax.jit, static_argnums=(0,))\n",
+ " def eval_model(self, latents, model_args):\n",
+ " with apply_handlers(SetValues(latents), Sample(jax.random.key(0))) as trace:\n",
+ " return self.model(model_args)\n",
+ "\n",
+ " @functools.partial(jax.jit, static_argnums=(0,))\n",
+ " def target_log_prob_fn(self, latents, cond_latents, cond_mask, model_args):\n",
+ " new_cond_latents = latents.copy()\n",
+ " # Condition the latents\n",
+ " for k, v in list(cond_latents.items()):\n",
+ " mask = cond_mask.get(k)\n",
+ " if mask is None:\n",
+ " new_cond_latents[k] = v\n",
+ " else:\n",
+ " new_cond_latents[k] = jnp.where(mask, v, latents[k])\n",
+ "\n",
+ " # Add the observations\n",
+ " new_cond_latents[\"keypoint_screen_positions\"] = jnp.where(\n",
+ " jnp.isfinite(\n",
+ " new_cond_latents[\"keypoint_screen_positions\"]\n",
+ " ), # & model_args.keypoint_visibility[..., jnp.newaxis],\n",
+ " new_cond_latents[\"keypoint_screen_positions\"],\n",
+ " 0.0,\n",
+ " )\n",
+ " log_prob, retval = model_log_prob(\n",
+ " functools.partial(self.model, model_args),\n",
+ " new_cond_latents,\n",
+ " )\n",
+ " extra = dict(retval)\n",
+ "\n",
+ " extra[\"latents\"] = latents\n",
+ " return log_prob.astype(DTYPE), extra"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7219607a-fb71-4daf-89f9-8404fdb6c3e9",
+ "metadata": {},
+ "source": [
+ "## Construct"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 90,
+ "id": "be781a4c-b628-4a94-ba5a-f7a9a03dd37b",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "DEFAULT_MODEL_ARGS = ModelArgs(\n",
+ " radius=jnp.array(20.0, DTYPE),\n",
+ " keypoint_visibility=SCENE.keypoint_visibility,\n",
+ " camera_visibility=jnp.ones(SCENE.num_frames, dtype=bool),\n",
+ " observation_noise_degrees=jnp.array(30.0, DTYPE),\n",
+ " object_mask=SCENE_INFO.object_mask,\n",
+ " object_positions=SCENE_INFO.object_positions.astype(DTYPE),\n",
+ " object_prior_scale=jnp.array(0.001, DTYPE),\n",
+ " first_camera_pose=SCENE.camera_poses[0],\n",
+ " center=SCENE.camera_poses[0].position,\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 91,
+ "id": "2a9ecd9f-40d6-4eff-b63e-862b5f21a115",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "MODEL = Model(\n",
+ " world_prior=\"pseudo_object\",\n",
+ " camera_prior=\"independent\",\n",
+ " num_frames=SCENE.num_frames,\n",
+ " num_keypoints=SCENE.num_keypoints,\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f88e87a9-76e4-4703-b1b0-8545040edb15",
+ "metadata": {},
+ "source": [
+ "## Tests"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 51,
+ "id": "e8d96611-f00d-42d0-8788-d60d55610c9c",
+ "metadata": {
+ "scrolled": true
+ },
+ "outputs": [],
+ "source": [
+ "with warnings.catch_warnings(action=\"ignore\"):\n",
+ " prior_sample, retval = model_sample(\n",
+ " functools.partial(MODEL.model, DEFAULT_MODEL_ARGS), jax.random.key(0)\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 52,
+ "id": "f1f4cd2c-0b8f-4230-8d6f-e30ed4ad68c3",
+ "metadata": {
+ "scrolled": true
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{'keypoint_screen_positions': Array([[[ 9.8028034e-01, 1.2025309e+00],\n",
+ " [ 3.9625895e+04, -1.9633994e+04],\n",
+ " [-6.3550001e-01, 1.4189218e+00],\n",
+ " ...,\n",
+ " [-1.4539595e+04, -1.3471026e+04],\n",
+ " [ 3.8712141e+03, 2.0664500e+04],\n",
+ " [-8.3644120e-03, -2.7873570e-01]],\n",
+ " \n",
+ " [[ 5.5588574e+03, -5.1534917e+03],\n",
+ " [-4.9692202e+00, 6.0318656e+00],\n",
+ " [ 1.9295523e+00, -3.1589095e-02],\n",
+ " ...,\n",
+ " [ 3.9367503e-01, 9.8350120e-01],\n",
+ " [ 1.3383020e+01, 1.4039549e+01],\n",
+ " [-7.9537235e-02, 1.3372885e-02]],\n",
+ " \n",
+ " [[ 7.0432847e+03, -4.4489027e+04],\n",
+ " [-2.5543211e+04, 3.3735547e+02],\n",
+ " [ 3.6154266e+04, -2.9825172e+04],\n",
+ " ...,\n",
+ " [ 2.4886656e+04, 1.4443941e+04],\n",
+ " [ 1.4470686e+04, -2.0205463e+04],\n",
+ " [ 6.3659363e+00, -3.0551364e+00]],\n",
+ " \n",
+ " ...,\n",
+ " \n",
+ " [[-3.4972364e-01, 4.1383820e+00],\n",
+ " [-3.0609584e-01, 4.6538246e-01],\n",
+ " [ 6.6649146e+03, 5.6897340e+04],\n",
+ " ...,\n",
+ " [ 9.0580049e+03, 1.1718721e+04],\n",
+ " [ 2.0873942e+00, 4.5663228e+00],\n",
+ " [-2.2924969e+04, 3.6215348e+04]],\n",
+ " \n",
+ " [[-8.2352705e+03, -4.2877172e+04],\n",
+ " [ 2.1795264e+04, 3.9726606e+03],\n",
+ " [-2.4623354e+01, -2.4011900e+01],\n",
+ " ...,\n",
+ " [-1.6865485e+00, 7.2222811e-01],\n",
+ " [-6.2754864e-01, -1.9276551e+00],\n",
+ " [-2.8546660e+04, -6.2597007e+03]],\n",
+ " \n",
+ " [[-4.7001494e+03, -2.3233176e+03],\n",
+ " [-1.3646544e+04, -2.6081701e+04],\n",
+ " [ 2.7502744e+04, 6.7807358e+03],\n",
+ " ...,\n",
+ " [ 3.7030660e+04, -5.4291821e+03],\n",
+ " [ 1.1672313e+04, 1.5654638e+04],\n",
+ " [ 2.5000754e+04, -2.8065387e+04]]], dtype=float32),\n",
+ " 'raw_observation_noise_scale': Array(131.7358, dtype=float32),\n",
+ " 'camera_raw_quaternions': Array([[-1.51927993e-01, -3.74685913e-01, 3.71003836e-01,\n",
+ " -9.43984687e-01],\n",
+ " [ 2.65967995e-01, -1.29529819e-01, -4.60676938e-01,\n",
+ " 7.04387844e-01],\n",
+ " [ 1.09782897e-01, 5.06159663e-01, 5.18138967e-02,\n",
+ " -6.41531467e-01],\n",
+ " [ 2.84732223e-01, -1.77640960e-01, 7.49149203e-01,\n",
+ " 7.00852931e-01],\n",
+ " [ 5.98554432e-01, 1.87838171e-02, 3.75454485e-01,\n",
+ " 4.62541729e-01],\n",
+ " [ 9.26769257e-01, -2.21802413e-01, -3.50857615e-01,\n",
+ " -1.87504500e-01],\n",
+ " [-9.44253802e-01, 2.42163181e-01, -3.21839541e-01,\n",
+ " -5.00077248e-01],\n",
+ " [ 6.60627902e-01, 6.96577847e-01, 2.22797647e-01,\n",
+ " -3.83844674e-01],\n",
+ " [ 4.45689499e-01, 2.73988694e-01, 5.38428605e-01,\n",
+ " 1.28328875e-02],\n",
+ " [ 6.27044380e-01, 1.46558329e-01, -1.09853148e+00,\n",
+ " -3.61354947e-01],\n",
+ " [ 6.32355452e-01, 6.96397662e-01, -2.53592789e-01,\n",
+ " -3.60669672e-01],\n",
+ " [-3.34680408e-01, 6.64863527e-01, -8.98923650e-02,\n",
+ " 8.33435655e-01],\n",
+ " [-6.23005033e-01, 6.29542232e-01, -1.31270781e-01,\n",
+ " 4.20008272e-01],\n",
+ " [-6.99095964e-01, -4.58210886e-01, -5.41906178e-01,\n",
+ " 3.13478820e-02],\n",
+ " [ 1.52476653e-01, -4.54365194e-01, -5.18706024e-01,\n",
+ " -7.80884385e-01],\n",
+ " [-7.93749571e-01, 9.14382815e-01, -1.45689696e-01,\n",
+ " 1.92474772e-03],\n",
+ " [-7.39528656e-01, -1.93379194e-01, 7.26059258e-01,\n",
+ " 5.50567508e-01],\n",
+ " [ 5.60432136e-01, -6.71000302e-01, -3.90102953e-01,\n",
+ " -7.69878253e-02],\n",
+ " [ 3.09779733e-01, 3.63405138e-01, 7.75692046e-01,\n",
+ " -3.85419935e-01],\n",
+ " [-8.22284818e-02, 3.58133316e-01, 5.26112080e-01,\n",
+ " 3.90142232e-01],\n",
+ " [ 5.26768155e-02, 3.64896238e-01, -1.12756062e+00,\n",
+ " 4.11089361e-01],\n",
+ " [ 2.51371592e-01, -5.90860903e-01, -3.09902430e-01,\n",
+ " -3.17731649e-01],\n",
+ " [ 3.05886179e-01, -4.80466485e-01, -8.82623255e-01,\n",
+ " 4.69050199e-01],\n",
+ " [-1.11482859e-01, -5.36642015e-01, 4.67536718e-01,\n",
+ " 2.37206489e-01],\n",
+ " [-2.06154689e-01, -2.28394255e-01, 6.38471544e-01,\n",
+ " -3.54722291e-01],\n",
+ " [ 7.64642358e-01, 6.33536100e-01, 1.93505526e-01,\n",
+ " -2.99535953e-02],\n",
+ " [-8.27196479e-01, 1.16980076e-01, 4.29756522e-01,\n",
+ " 1.39285713e-01],\n",
+ " [ 2.25784853e-01, 6.08236074e-01, -3.49070996e-01,\n",
+ " -1.06403983e+00],\n",
+ " [-4.53328848e-01, 4.69567388e-01, 4.57290590e-01,\n",
+ " -2.50392497e-01],\n",
+ " [-4.18305039e-01, -3.36433589e-01, -5.62098801e-01,\n",
+ " -7.08624050e-02],\n",
+ " [ 5.42308748e-01, 9.70694125e-01, -3.19721669e-01,\n",
+ " -7.72981048e-01],\n",
+ " [-9.53135371e-01, -2.83963114e-01, -5.31364083e-01,\n",
+ " 1.07515812e+00],\n",
+ " [-5.16796052e-01, -8.94382298e-01, 4.99305934e-01,\n",
+ " 3.55352849e-01],\n",
+ " [ 3.43881994e-01, 7.83711255e-01, 6.37095034e-01,\n",
+ " 4.55072701e-01],\n",
+ " [ 6.92560077e-01, 4.43663061e-01, 7.05655754e-01,\n",
+ " 1.59616515e-01],\n",
+ " [ 2.20697910e-01, 5.57565629e-01, 5.37921727e-01,\n",
+ " 1.01681697e+00],\n",
+ " [-3.62388432e-01, 4.92087066e-01, 8.82719271e-03,\n",
+ " 8.77826571e-01],\n",
+ " [ 1.18506873e+00, -6.90732181e-01, 2.73033887e-01,\n",
+ " 8.20948303e-01],\n",
+ " [-7.83699825e-02, 5.33433259e-01, -7.81056643e-01,\n",
+ " 8.31216872e-01],\n",
+ " [ 1.86200991e-01, -6.24620840e-02, -7.69225776e-01,\n",
+ " -2.43534133e-01],\n",
+ " [-6.57404184e-01, -6.12133622e-01, -3.63545686e-01,\n",
+ " 7.71833882e-02],\n",
+ " [-6.75333679e-01, 4.03312027e-01, -1.53848976e-01,\n",
+ " 2.13673621e-01],\n",
+ " [ 3.91451657e-01, 9.91384506e-01, 3.89287800e-01,\n",
+ " -3.86244096e-02],\n",
+ " [-6.30521715e-01, -1.71269160e-02, -6.67595625e-01,\n",
+ " -2.95937479e-01],\n",
+ " [ 3.66410673e-01, 5.22439301e-01, 8.28420877e-01,\n",
+ " -2.15471476e-01],\n",
+ " [ 4.98802483e-01, 7.69210905e-02, 3.56308609e-01,\n",
+ " 4.06433165e-01],\n",
+ " [-4.56649840e-01, 2.08892629e-01, -4.06378418e-01,\n",
+ " 9.12917376e-01],\n",
+ " [-2.10445970e-01, -1.16775863e-01, -9.56291020e-01,\n",
+ " -5.21333754e-01],\n",
+ " [ 3.17570865e-01, 8.98765385e-01, 4.37842965e-01,\n",
+ " -3.07674527e-01],\n",
+ " [-3.77784744e-02, -9.44907889e-02, -3.38173717e-01,\n",
+ " -2.17673585e-01],\n",
+ " [-6.65738285e-01, -6.94405556e-01, -9.99994874e-02,\n",
+ " -2.30312720e-02],\n",
+ " [ 3.49179834e-01, -1.23764396e+00, -5.05725324e-01,\n",
+ " 2.40004674e-01],\n",
+ " [-2.65043855e-01, -2.10349128e-01, -8.79187956e-02,\n",
+ " 1.01988864e+00],\n",
+ " [-1.75777644e-01, 3.83677214e-01, -2.58249193e-02,\n",
+ " -4.77984935e-01],\n",
+ " [ 4.32381814e-04, -7.81954288e-01, 3.01555812e-01,\n",
+ " 4.89282519e-01],\n",
+ " [-3.22460294e-01, 1.58771258e-02, -8.34118664e-01,\n",
+ " 7.89358094e-02],\n",
+ " [-6.19108617e-01, -7.22704887e-01, 9.37339306e-01,\n",
+ " 7.13554084e-01],\n",
+ " [-6.00126743e-01, -6.51083767e-01, 7.02134371e-02,\n",
+ " -5.91366112e-01],\n",
+ " [ 4.64709044e-01, -3.83766919e-01, -7.34819174e-02,\n",
+ " 7.23340034e-01],\n",
+ " [ 1.11391373e-01, 2.46987566e-01, 2.59524018e-01,\n",
+ " -3.93819749e-01],\n",
+ " [ 1.00630522e+00, 1.44116566e-01, -4.92491275e-01,\n",
+ " -5.36585927e-01],\n",
+ " [ 2.48890325e-01, 5.19609511e-01, 1.87581316e-01,\n",
+ " 4.41554785e-01],\n",
+ " [ 6.63042963e-01, -2.08111316e-01, 1.56301618e-01,\n",
+ " 5.52442551e-01],\n",
+ " [-5.87123692e-01, -1.62575647e-01, 2.17956066e-01,\n",
+ " 9.69822764e-01],\n",
+ " [ 1.66674271e-01, 4.53080326e-01, -3.69119346e-01,\n",
+ " -2.39664912e-01],\n",
+ " [-3.86505932e-01, 6.10942423e-01, -5.00057817e-01,\n",
+ " 2.66106606e-01],\n",
+ " [-2.54347622e-01, 3.72354746e-01, 3.99116166e-02,\n",
+ " 6.20316327e-01],\n",
+ " [-4.96921465e-02, -9.18441892e-01, 3.44903976e-01,\n",
+ " 8.15902203e-02],\n",
+ " [-6.36725843e-01, 2.10371893e-03, -6.16860807e-01,\n",
+ " -7.29355335e-01],\n",
+ " [-4.36565757e-01, 5.90445995e-01, 2.89873779e-01,\n",
+ " -5.34469076e-02],\n",
+ " [-9.48990956e-02, -9.55137372e-01, 5.00165559e-02,\n",
+ " 1.42975301e-01],\n",
+ " [-3.14838707e-01, -3.89811456e-01, -6.74244702e-01,\n",
+ " 2.09302574e-01],\n",
+ " [ 4.07660156e-01, 7.14925647e-01, 5.40605724e-01,\n",
+ " -7.90047586e-01],\n",
+ " [ 1.31185979e-01, 8.02125454e-01, 1.10603094e-01,\n",
+ " 4.17865366e-01],\n",
+ " [ 2.20517084e-01, -4.18477237e-01, 2.90681362e-01,\n",
+ " -5.62349558e-01],\n",
+ " [ 4.90748197e-01, 1.25575587e-01, -3.97499144e-01,\n",
+ " -4.35987890e-01],\n",
+ " [-2.57198870e-01, -7.12255090e-02, 9.69296038e-01,\n",
+ " 8.77256930e-01],\n",
+ " [ 1.43123433e-01, -9.27430391e-03, -3.35099757e-01,\n",
+ " -1.34040296e+00],\n",
+ " [ 8.57407525e-02, -5.38109709e-03, 4.75591958e-01,\n",
+ " 8.74427974e-01],\n",
+ " [-5.44446826e-01, -3.00944634e-02, -2.06931546e-01,\n",
+ " 6.35489166e-01],\n",
+ " [ 9.17064846e-01, 6.46720648e-01, -4.41391259e-01,\n",
+ " 6.15672886e-01],\n",
+ " [-5.43026686e-01, 4.09820467e-01, 6.57507122e-01,\n",
+ " 1.57830968e-01],\n",
+ " [ 4.02954638e-01, -1.72210738e-01, 8.48076701e-01,\n",
+ " -7.82141149e-01],\n",
+ " [ 4.16139841e-01, -2.31744245e-01, -1.30540445e-01,\n",
+ " 5.55725634e-01],\n",
+ " [ 4.07916337e-01, -1.21158198e-01, 8.86895537e-01,\n",
+ " -3.80534410e-01],\n",
+ " [ 2.32394025e-01, 4.22892034e-01, 7.64891505e-02,\n",
+ " 9.25945401e-01],\n",
+ " [ 5.80566287e-01, -4.01611254e-03, 1.02383219e-01,\n",
+ " -1.30755424e-01],\n",
+ " [-1.15372789e+00, 3.62169057e-01, 3.64901572e-02,\n",
+ " -5.46632946e-01],\n",
+ " [ 2.71410376e-01, 1.65175200e-02, 7.98183307e-02,\n",
+ " -7.19187140e-01],\n",
+ " [-3.43314469e-01, 2.46294647e-01, -7.23167002e-01,\n",
+ " 6.08630121e-01],\n",
+ " [ 5.01465082e-01, -2.85367280e-01, 5.64095378e-01,\n",
+ " -2.47808158e-01],\n",
+ " [ 3.48528251e-02, -4.05251622e-01, -1.06104448e-01,\n",
+ " -3.78682733e-01],\n",
+ " [ 1.07953250e+00, 2.82175124e-01, -6.28056526e-02,\n",
+ " 5.19453824e-01],\n",
+ " [-2.11607907e-02, -7.86108553e-01, -3.92165542e-01,\n",
+ " -3.36899132e-01],\n",
+ " [ 8.74649942e-01, 1.78430989e-01, 3.35087627e-01,\n",
+ " -1.37732938e-01],\n",
+ " [-6.32407963e-01, 4.82729465e-01, 5.39978147e-01,\n",
+ " 6.33991420e-01],\n",
+ " [-1.03223252e+00, 2.31130883e-01, 5.93555510e-01,\n",
+ " 7.64781415e-01],\n",
+ " [-5.07121146e-01, -7.17001796e-01, -3.26852322e-01,\n",
+ " -3.22901100e-01],\n",
+ " [-7.04385102e-01, 1.66175783e-01, -4.56938595e-01,\n",
+ " 8.65859568e-01]], dtype=float32),\n",
+ " 'camera_positions': Array([[-3.12537613e+01, -1.42111874e+00, -8.51368523e+00],\n",
+ " [-1.52117157e+01, 9.60305786e+00, 2.31950455e+01],\n",
+ " [ 2.37923126e+01, 2.75534916e+00, -2.34064503e+01],\n",
+ " [ 4.84719515e+00, 3.43343582e+01, 1.08125620e-02],\n",
+ " [-1.41254654e+01, 2.44178791e+01, -4.90082932e+01],\n",
+ " [-4.42659950e+01, -2.08577461e+01, -4.66706848e+00],\n",
+ " [ 1.77259827e+01, 1.22100401e+01, 2.16972637e+00],\n",
+ " [ 2.37937050e+01, 2.21075649e+01, -3.71934950e-01],\n",
+ " [-1.01032877e+01, -2.67316008e+00, -3.28137207e+00],\n",
+ " [ 3.63336682e+00, 2.88324451e+01, 9.78923035e+00],\n",
+ " [-1.44270954e+01, 3.82882538e+01, 1.99081516e+01],\n",
+ " [-2.30130882e+01, -2.12023035e-02, 1.32187974e+00],\n",
+ " [ 2.16270866e+01, 5.76420879e+00, -3.84611969e+01],\n",
+ " [-1.15930262e+01, -2.61350746e+01, -1.12158413e+01],\n",
+ " [-7.81856728e+00, 8.34723854e+00, 3.40747528e+01],\n",
+ " [ 3.60359997e-01, 1.79351711e+01, -2.49783249e+01],\n",
+ " [-1.59563904e+01, -4.04742813e+01, 1.73114395e+01],\n",
+ " [ 1.17220011e+01, 2.58905449e+01, 2.72572899e+00],\n",
+ " [ 3.71205759e+00, 1.36219072e+00, -4.30522919e+01],\n",
+ " [-1.82478695e+01, 9.79732990e+00, -2.38393002e+01],\n",
+ " [-2.89208913e+00, 9.36000252e+00, -2.38623714e+01],\n",
+ " [-3.29532890e+01, 1.41449404e+01, 2.82875538e+01],\n",
+ " [-2.02991676e+01, 2.71329761e+00, -1.33434525e+01],\n",
+ " [ 1.15878057e+01, -1.53237267e+01, 1.55136833e+01],\n",
+ " [-1.83090553e+01, -2.31087875e+01, -1.33950367e+01],\n",
+ " [ 1.39383993e+01, -1.90647526e+01, 8.77674007e+00],\n",
+ " [ 1.81409912e+01, 1.59712200e+01, -2.82454610e+00],\n",
+ " [ 2.92930679e+01, -3.38720083e-02, -1.74807930e+01],\n",
+ " [ 1.28059540e+01, -3.12338495e+00, -7.35168648e+00],\n",
+ " [-2.80920486e+01, 1.34889326e+01, -1.57047853e+01],\n",
+ " [-8.40764332e+00, -1.99676018e+01, -2.74612255e+01],\n",
+ " [ 4.27074957e+00, -9.94537640e+00, 1.33726711e+01],\n",
+ " [-2.67352657e+01, -8.40588760e+00, -3.48194122e+01],\n",
+ " [ 6.41699362e+00, -1.26025333e+01, -4.02622747e+00],\n",
+ " [ 7.23208466e+01, 1.44248285e+01, 9.32802963e+00],\n",
+ " [ 5.56452560e+00, -5.45671749e+00, 1.74738765e+00],\n",
+ " [ 1.77072525e+01, 6.95184422e+00, 4.94926834e+00],\n",
+ " [ 2.53545475e+00, 1.23525391e+01, -2.23159294e+01],\n",
+ " [ 1.48137646e+01, 6.59744873e+01, 6.55005264e+00],\n",
+ " [ 2.81577873e+01, -1.11248131e+01, -2.86736698e+01],\n",
+ " [-3.59824982e+01, 1.19687214e+01, 1.07426825e+01],\n",
+ " [-3.57302971e+01, -9.74959469e+00, -3.67939091e+00],\n",
+ " [-3.91301632e+00, -2.44476166e+01, -2.21279831e+01],\n",
+ " [ 5.38754702e+00, 1.70687561e+01, 4.80260391e+01],\n",
+ " [-1.10787783e+01, 1.46290121e+01, -4.36676216e+00],\n",
+ " [-1.53195739e+00, 5.83427467e+01, -1.83959160e+01],\n",
+ " [-1.54909906e+01, 1.93601093e+01, 9.57220912e-01],\n",
+ " [-1.50919733e+01, -1.87660542e+01, -6.03623772e+00],\n",
+ " [ 1.26809072e+01, 1.25081139e+01, 5.11858988e+00],\n",
+ " [-3.99364758e+00, -2.01897507e+01, 3.75410614e+01],\n",
+ " [-1.26424074e+00, -4.05465078e+00, -1.72862682e+01],\n",
+ " [ 8.64319706e+00, 9.63397217e+00, -9.28374004e+00],\n",
+ " [ 3.51466179e+01, 1.93393707e+01, 1.58748779e+01],\n",
+ " [ 1.14144602e+01, 1.58284721e+01, 2.38412514e+01],\n",
+ " [-9.50970459e+00, -1.40029926e+01, 2.06051216e+01],\n",
+ " [-1.06816187e+01, 2.27507305e+00, -1.67601738e+01],\n",
+ " [-2.75502815e+01, 2.73173523e+01, 2.37726288e+01],\n",
+ " [ 5.46742725e+00, 2.08078575e+01, 2.19708538e+01],\n",
+ " [ 3.27124443e+01, -1.45728092e+01, -6.05527973e+00],\n",
+ " [-1.15781822e+01, 2.69317799e+01, 2.15549068e+01],\n",
+ " [-1.36414967e+01, -4.64170933e+00, -1.47975063e+00],\n",
+ " [ 1.79367256e+01, -2.14179592e+01, -2.16881008e+01],\n",
+ " [-6.22318935e+00, 7.08142233e+00, -1.74035110e+01],\n",
+ " [ 3.46656227e+01, 1.46850004e+01, 3.05535736e+01],\n",
+ " [-1.97505207e+01, -2.17132645e+01, -1.89957523e+01],\n",
+ " [-1.27118130e+01, -3.43927422e+01, -2.62008858e+00],\n",
+ " [ 1.18294573e+01, 2.76990147e+01, -1.10004129e+01],\n",
+ " [ 5.55038109e+01, -1.68828869e+00, -1.97196922e+01],\n",
+ " [-7.60153484e+00, -2.71198654e+01, -3.27173309e+01],\n",
+ " [-3.71011200e+01, 3.15668144e+01, -1.11750908e+01],\n",
+ " [-1.46946859e+01, -1.70589027e+01, -1.31439161e+01],\n",
+ " [ 9.10875511e+00, 3.43826447e+01, 1.61946182e+01],\n",
+ " [ 3.03332253e+01, -3.63300781e+01, 1.99262447e+01],\n",
+ " [-1.10537138e+01, -1.72484531e+01, 2.39434166e+01],\n",
+ " [-2.88254433e+01, 2.47118607e+01, -2.14406986e+01],\n",
+ " [ 1.37377825e+01, -1.02883186e+01, 1.70011730e+01],\n",
+ " [-5.88547993e+00, -1.46347561e+01, -6.08912945e+00],\n",
+ " [-1.28371315e+01, 1.20679073e+01, 1.32494440e+01],\n",
+ " [ 1.72086163e+01, 1.49413118e+01, -1.43691242e+00],\n",
+ " [ 1.80731316e+01, 2.17895436e+00, -1.42909985e+01],\n",
+ " [ 9.61150169e+00, -3.99987068e+01, 2.76481342e+01],\n",
+ " [-1.36546078e+01, -6.99765682e+00, -5.25201845e+00],\n",
+ " [-7.35935402e+00, 2.42879143e+01, -2.84734650e+01],\n",
+ " [ 1.98023548e+01, 9.74178505e+00, -1.20538530e+01],\n",
+ " [-7.01734304e+00, -2.29743198e-01, -1.69950790e+01],\n",
+ " [-2.18883095e+01, 2.74564152e+01, -2.12885456e+01],\n",
+ " [-4.38243198e+00, 4.87638426e+00, -4.52398634e+00],\n",
+ " [-1.03975430e+01, 5.06710529e+00, -7.36464918e-01],\n",
+ " [-8.42724609e+00, 2.09739151e+01, -4.62922134e+01],\n",
+ " [ 5.74010420e+00, 1.73246849e+00, 2.75797825e+01],\n",
+ " [-3.09604979e+00, 4.47556019e+00, 1.54312122e+00],\n",
+ " [ 3.67038689e+01, -3.76719742e+01, -4.87405396e+00],\n",
+ " [ 1.99358177e+01, 1.53715754e+01, -2.16905365e+01],\n",
+ " [-7.74785805e+00, 8.65913773e+00, 1.11403084e+01],\n",
+ " [-2.13938828e+01, -1.72895851e+01, -1.32886963e+01],\n",
+ " [ 2.24843478e+00, -8.28067541e-01, -3.69713287e+01],\n",
+ " [ 2.07762299e+01, 7.97154379e+00, 2.24146385e+01],\n",
+ " [-8.34892333e-01, 1.72959137e+01, 5.43206787e+00],\n",
+ " [ 6.86541080e+00, 3.67161751e+01, -1.07211030e+00]], dtype=float32),\n",
+ " 'keypoint_world_positions': Array([[-35.675045 , -4.2464643, -18.003294 ],\n",
+ " [-19.5758 , 36.46369 , 16.358862 ],\n",
+ " [ -8.156345 , -23.596035 , -17.684082 ],\n",
+ " ...,\n",
+ " [ 20.804333 , 0.2422315, 8.532504 ],\n",
+ " [ -1.3674471, 4.1072836, -23.054115 ],\n",
+ " [-15.435583 , -14.9414425, 17.47194 ]], dtype=float32)}"
+ ]
+ },
+ "execution_count": 52,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "prior_sample"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 53,
+ "id": "68b44fdb-f20e-4f28-ad1a-2742b6126c9a",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(-1.0, 1.0)"
+ ]
+ },
+ "execution_count": 53,
+ "metadata": {},
+ "output_type": "execute_result"
+ },
+ {
+ "data": {
+ "image/png": "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5OTkiNmzZ4sjR46k6jCIiKgPS/k4SSIiop6KuVuJiIhMMEgSERGZYJAkIiIywSBJRERkolcGyZ48TVes+9i9ezckSQr7eOmll7T1wr2+du3abis3AFRUVISU6ec//7lhnT179mDKlCkYOHAgCgsLceedd6Kzs7Pbyn3o0CH88pe/xMiRI3HaaafhzDPPxC233AKfz2dYL9nne8WKFRg6dCgGDBiAsrIyvPfeexHXf+mll3DOOedgwIABGDVqFOrr6w2vW/m+J0ssZX/22WdRXl6OM844A2eccQYqKytD1r/++utDzu2kSZO6tdxr1qwJKdOAAQMM66TrnMdS7nB/g5IkYcqUKdo66Tjfb731Fn784x+juLgYkiThz3/+c9T3bNq0CRdddBGysrIwfPhwrFmzJmSdWP9uwurWvrUpcv/994vHH39cLFy40FJydSGEWLp0qcjNzRV//vOfxYcffiiuuOIKMWzYMPHNN99o60yaNEmMHj1avPvuu6KxsVEMHz5cTJ8+Pallj3UfnZ2doqWlxfB44IEHhN1uNwyNgTLcRr+e/tjSXW4hhBg/fryYO3euoUw+n89wbOedd56orKwUH3zwgaivrxf5+fli0aJF3Vbujz76SLjdbvHKK6+IXbt2iY0bN4oRI0aIK6+80rBeMs/32rVrRf/+/UVVVZX45JNPxNy5c0VeXp7wer1h13/nnXeEzWYTjzzyiNi+fbu49957xamnnio++ugjbR0r3/dkiLXsM2bMECtWrBAffPCB2LFjh7j++utFbm6u2Ldvn7bOrFmzxKRJkwzn9tChQ91a7tWrV4ucnBxDmVpbWw3rpOOcx1rugwcPGsr88ccfC5vNZhial47zXV9fL379618Lj8cjAIiXX3454vpNTU1i4MCBYuHChWL79u3iqaeeEjabTWzYsEFbJ9ZzYaZXBkmV1RlITp48KYqKisSjjz6qLTt8+LDIysoSL774ohBCiO3btwsA4h//+Ie2zmuvvSYkSRL79+9PSnmTtY8LLrhAzJkzx7DMyhcvXvGWe/z48eLWW281fb2+vl7069fPcLF5+umnRU5Ojjh27Fi3lTvYunXrRP/+/cWJEye0Zck832PHjhU333yz9jwQCIji4mKxZMmSsOtfddVVYsqUKYZlZWVl4j//8z+FENa+78kSa9mDdXZ2itNPP10899xz2rJZs2aJqVOnJrWcwWItd7RrTbrOeaLn+4knnhCnn3668Pv92rJ0nG89K387d911l/jOd75jWHb11VeLiRMnas8TPReqXtncGqvm5ma0traisrJSW5abm4uysjJs3rwZALB582bk5eVp81gCQGVlJfr164ctW7YkpRzJ2MfWrVuxbds23HDDDSGv3XzzzcjPz8fYsWNRVVUFkaQhsomUu7q6Gvn5+TjvvPOwaNEifP3114btjho1yjAzzMSJE9He3o5PPvmkW8ut5/P5kJOTg1NOMWZ5TMb5Pn78OLZu3Wr4bvbr1w+VlZXadzPY5s2bDesD8nlT17fyfU+GeMoe7Ouvv8aJEydCZnrYtGkTCgsLMXLkSMyfPx8HDx7s9nL7/X6cddZZKCkpwdSpUw3f0XSc82Sc71WrVuGaa65Bdna2YXkqz3c8on3Hk3EuVBkzVVZ3SuU0XbGWI9F9rFq1Ct/+9rdx8cUXG5Y/+OCD+NGPfoSBAwfi9ddfx0033QS/349bbrml28o9Y8YMnHXWWSguLsa//vUv3H333fjss8/g8Xi07Yb7TNTXuqvcel999RUeeughzJs3z7A8Wef7q6++QiAQCHsePv3007DvMTtv+u+yusxsnWSIp+zB7r77bhQXFxsudpMmTYLb7cawYcPw+eef41e/+hUuv/xybN68GTabrVvKPXLkSFRVVeH888+Hz+fDY489hosvvhiffPIJXC5XWs55ouf7vffew8cff4xVq1YZlqf6fMfD7Dve3t6Ob775Bv/+978T/u6pekyQ7KnTdAHWy56ob775BjU1NbjvvvtCXtMvu/DCC9HR0YFHH3004kU71eXWB5ZRo0bB6XTisssuw+eff46zzz477u2m63y3t7djypQpOPfcc/Ff//VfhtfiOd9ktHTpUqxduxabNm0ydIK55pprtP+PGjUK559/Ps4++2xs2rQJl112WXcUFePGjcO4ceO05xdffDG+/e1v4/e//z0eeuihbilTrFatWoVRo0Zh7NixhuWZeL7TqccEyZ48TZfVsic6FVhtbS2+/vprXHfddVHXLSsrw0MPPYRjx46ZJghOV7n1ZQKAXbt24eyzz0ZRUVFIbzSv1wsAEbebjnIfOXIEkyZNwumnn46XX34Zp556asT1rZzvcPLz82Gz2bTjVnm9XtMyFhUVRVzfyvc9GeIpu+qxxx7D0qVL8eabb+L888+PuG5paSny8/Oxa9eupFy0Eym36tRTT8WFF16IXbt2AUjPOU+k3B0dHVi7di0efPDBqPtJ9vmOh9l3PCcnB6eddhpsNlvCn6EmpjuYPUysHXcee+wxbZnP5wvbceef//ynts5f//rXlHTciXcf48ePD+llaeY3v/mNOOOMM+Iuq16yzs3bb78tAIgPP/xQCNHVcUffG+33v/+9yMnJEUePHu22cvt8PvH9739fjB8/XnR0dFjaVyLne+zYseIXv/iF9jwQCIghQ4ZE7Ljz//7f/zMsGzduXEjHnUjf92SJtexCCPHwww+LnJwcsXnzZkv72Lt3r5AkSaxfvz7h8qriKbdeZ2enGDlypLjtttuEEOk75/GWe/Xq1SIrK0t89dVXUfeRivOtB4sdd8477zzDsunTp4d03EnkM9TKE9PaPcQXX3whPvjgA20oxAcffCA++OADw5CIkSNHCo/Hoz1funSpyMvLE+vXrxf/+te/xNSpU8MOAbnwwgvFli1bxNtvvy1GjBiRkiEgkfaxb98+MXLkSLFlyxbD+3bu3CkkSRKvvfZayDZfeeUV8eyzz4qPPvpI7Ny5U6xcuVIMHDhQ3H///d1W7l27dokHH3xQ/POf/xTNzc1i/fr1orS0VFxyySXae9QhIBMmTBDbtm0TGzZsEAUFBUkfAhJLuX0+nygrKxOjRo0Su3btMnSL7+zsFEIk/3yvXbtWZGVliTVr1ojt27eLefPmiby8PK3X77XXXivuuecebf133nlHnHLKKeKxxx4TO3bsEIsXLw47BCTa9z0ZYi370qVLRf/+/UVtba3h3Kp/u0eOHBF33HGH2Lx5s2hubhZvvvmmuOiii8SIESOS8sMp3nI/8MAD4q9//av4/PPPxdatW8U111wjBgwYID755BPDsaX6nMdabtUPf/hDcfXVV4csT9f5PnLkiHadBiAef/xx8cEHH4gvvvhCCCHEPffcI6699lptfXUIyJ133il27NghVqxYEXYISKRzYVWvDJI9eZquaPtobm4OORYhhFi0aJEoKSkRgUAgZJuvvfaauOCCC4TdbhfZ2dli9OjR4plnngm7brrKvWfPHnHJJZeIQYMGiaysLDF8+HBx5513GsZJCiHE7t27xeWXXy5OO+00kZ+fL26//XbDUIt0l7uhoSHsdwuAaG5uFkKk5nw/9dRT4swzzxT9+/cXY8eOFe+++6722vjx48WsWbMM669bt05861vfEv379xff+c53xKuvvmp43cr3PVliKftZZ50V9twuXrxYCCHE119/LSZMmCAKCgrEqaeeKs466ywxd+7cmC98yS73ggULtHUdDoeYPHmyeP/99w3bS9c5j/W78umnnwoA4vXXXw/ZVrrOt9nflVrWWbNmifHjx4e854ILLhD9+/cXpaWlhuu5KtK5sIpTZREREZngOEkiIiITDJJEREQmGCSJiIhMMEgSERGZYJAkIiIywSBJRERkgkGSiIjIBIMkERGRCQZJIiIiEwySREREJhgkiYiITPz/b18WrIZVgUgAAAAASUVORK5CYII=",
+ "text/plain": [
+ "