{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "7ed71dc7-d4b0-45fb-82fe-33824e4ceea7",
   "metadata": {},
   "source": [
    "# Load Example Data (see 01_getting_started.ipnb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "2230d54e-987e-4a70-b7de-b3526264c6e7",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(ProjectionMatrix:\n",
       " [[-5.06148e+02 -0.00000e+00 -3.53297e+03  3.76726e+05]\n",
       "  [-3.84000e+02 -3.53297e+03 -0.00000e+00  2.85811e+05]\n",
       "  [-1.00000e+00 -0.00000e+00 -0.00000e+00  7.44300e+02]]\n",
       " Image Size: [1024  760]\n",
       " Pixel Spacing: 0.308,\n",
       " ProjectionMatrix:\n",
       " [[-1.97544e+03 -0.00000e+00  2.97249e+03  3.76726e+05]\n",
       "  [ 2.86441e+02 -3.53297e+03  2.55749e+02  2.85811e+05]\n",
       "  [ 7.45941e-01 -0.00000e+00  6.66012e-01  7.44300e+02]]\n",
       " Image Size: [1024  760]\n",
       " Pixel Spacing: 0.308)"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import nrrd\n",
    "import numpy as np\n",
    "from PIL import Image\n",
    "from ProjectiveGeometry23.central_projection import ProjectionMatrix\n",
    "\n",
    "\n",
    "def to_gray_image(img, scale=1.0, fast_axis_is_x=True):\n",
    "    if fast_axis_is_x:\n",
    "        img = img.T  # convert (x, y) → (y, x)\n",
    "    img = (img - img.min()) / (img.max() - img.min())\n",
    "    img = (img * 255).astype(np.uint8)\n",
    "    im = Image.fromarray(img, mode=\"L\")\n",
    "    if scale != 1.0:\n",
    "        new_size = (int(im.width * scale), int(im.height * scale))\n",
    "        im = im.resize(new_size, Image.BILINEAR)\n",
    "    return im\n",
    "    \n",
    "def parse_matrix(s):\n",
    "    s = s.strip().replace('[', '').replace(']', '')\n",
    "    rows = s.split(';')\n",
    "    return np.array([[float(x) for x in row.split()] for row in rows])\n",
    "\n",
    "\n",
    "I0, meta0 = nrrd.read(\"input/proj000.nrrd\")\n",
    "I1, meta1 = nrrd.read(\"input/proj040.nrrd\")\n",
    "\n",
    "P0_np = parse_matrix(meta0['Projection Matrix'])\n",
    "P1_np = parse_matrix(meta1['Projection Matrix'])\n",
    "\n",
    "spacing = meta0['spacings']\n",
    "\n",
    "\n",
    "P0 = ProjectionMatrix(P0_np, I0.shape, pixel_spacing=spacing[0])\n",
    "P1 = ProjectionMatrix(P1_np, I1.shape, pixel_spacing=spacing[0])\n",
    "\n",
    "P0, P1"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d4daeaa2-3ce6-41bb-a09f-6f593e80b663",
   "metadata": {},
   "source": [
    "# Visualizing Projection Geometry"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "11a0730e-e320-479c-bbd7-583664a79ede",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "\u001b[31mSignature:\u001b[39m svg_world_geometry(P, composer=\u001b[38;5;28;01mNone\u001b[39;00m, **kwargs)\n",
       "\u001b[31mDocstring:\u001b[39m\n",
       "Draw a coordinate system of size 100 and a wire cube\n",
       "of the same size centered in the origin.\n",
       "\u001b[31mFile:\u001b[39m      ~/.virtualenvs/jupyter/lib/python3.12/site-packages/ProjectiveGeometry23/svg_utils.py\n",
       "\u001b[31mType:\u001b[39m      function"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from ProjectiveGeometry23.svg_utils import svg_world_geometry, svg_coordinate_frame\n",
    "svg_world_geometry?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "25657733-ed9b-4f2c-b905-eccf47ee0ba2",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "\u001b[31mInit signature:\u001b[39m\n",
       "SourceDetectorGeometry(\n",
       "    projection: ProjectiveGeometry23.central_projection.ProjectionMatrix,\n",
       ")\n",
       "\u001b[31mDocstring:\u001b[39m      <no docstring>\n",
       "\u001b[31mInit docstring:\u001b[39m\n",
       "From a 3x4 projection matrix and pixel spacing, compute physical location of detector.\n",
       "This allows for a full visualization of a source-detector geometry as a pyramid or frustum.\n",
       "In case you have a left-handed coordinate frame, use negative pixel spacing to swap the\n",
       "viewing direction of the system (i.e. negate pixel spacing if projection looks backwards.\n",
       "\u001b[31mFile:\u001b[39m           ~/.virtualenvs/jupyter/lib/python3.12/site-packages/ProjectiveGeometry23/source_detector_geometry.py\n",
       "\u001b[31mType:\u001b[39m           type\n",
       "\u001b[31mSubclasses:\u001b[39m     "
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from ProjectiveGeometry23.source_detector_geometry import SourceDetectorGeometry\n",
    "SourceDetectorGeometry?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "1fce66c4-d64e-423d-b0bc-e0ac9653c5ce",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "\u001b[31mSignature:\u001b[39m\n",
       "svg_source_detector(\n",
       "    P,\n",
       "    projection: ProjectiveGeometry23.central_projection.ProjectionMatrix,\n",
       "    draw_on_detector=\u001b[38;5;28;01mNone\u001b[39;00m,\n",
       "    composer=\u001b[38;5;28;01mNone\u001b[39;00m,\n",
       "    **kwargs,\n",
       ")\n",
       "\u001b[31mDocstring:\u001b[39m\n",
       "Draw X-ray source-detector geometry.\n",
       "Define draw_on_detector as any SVG drawing function to project\n",
       "additional 3D geometry to the detector plane.\n",
       "\u001b[31mFile:\u001b[39m      ~/.virtualenvs/jupyter/lib/python3.12/site-packages/ProjectiveGeometry23/svg_utils.py\n",
       "\u001b[31mType:\u001b[39m      function"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from ProjectiveGeometry23.svg_utils import svg_source_detector\n",
    "svg_source_detector?"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e3a7d20b-6933-459e-ac61-a2d1976e70fe",
   "metadata": {},
   "source": [
    "# Visualizing a projectoin image with overlay"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "d2d0c71d-c6c3-48ad-996a-58dc7692e76a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<svg  width=\"512.0\" height=\"380.0\" viewBox=\"0 0 1024 760\" xmlns=\"http://www.w3.org/2000/svg\">\n",
       "  <image x=\"0.00px\" y=\"0.00px\"  href=\"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/wAALCAL4BAABAREA/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/9oACAEBAAA/APn+iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiilAoopKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKB1p2aOtIaSiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii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qIrzS7TTGBFM5oBOaeWAFR9TUi9asxxjqanUhelK0hqF2xUW7NNLVTuj8lZ5kxThJmng09eaCueoqJoFPaq7wbarOMVE65FU5kxVRuDTSeDWVdcuazpxkGs5xzUL1VlqhOODWbKOtZk/3qhooooooooooooooooooooooooooooooooooooooooooooooooooooopyoTUiwFugq7BpryEfKa1rfSWUD5a1LbTTkfLWxBp4AGatfY1A4FRm2OelWbbTnlYccV02n6IAoJWtyHRwcfLWzZaPHHgsM1rLbRquAKQqq9qjYj0qMnmlAJqVEyavRBVWnPjFV3HpUdGaa1M25pNpFKBmnBDT1XBzip1PFOBzxQYlaoJIMVVkTBpg607g00x+lGCKN1MJ5pd2abwaQ4poGT0pSlJt9KkRcHmpt+KTzKUPmopGpgams9VbhhsOazyMmnIDUgp44p4NBqJ6rSRA1WeMrVKfpVCTrUTk7azZ+prPmOAaznPJqB6qy1SuB8prKlPWsuY5eo6KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKcqEmr1vaSSkAKa6DT9FzhnFb0NhHGPuircduPSrMcAB4FWlhb0qVYCasR2oPUVuaZp+9gdvFdPbWqqBxWhFEARxV6OLC805l4qu5qE80m2pEXmphxUq5xSkmoyaaRTWBFAGacEoK8UqoB2qUJntUyw5HNBgxTNuKcKR8EVTmGaqsMGm7iKlRxTiQagYYJphpMmkGSakERNTJCMUPEAM0xQtKSMcVEz0zdTvMAFQyS56VF5hFIJDUFyx2VVB4qRDUuKcOlKKKawqFhUbpkGsq6GCazZOtMb7tZs/esq5bHFUWqFqrS9Kzrtwq1izydaoMcmkoooooooooooooooooooooooooooooooooooooooooooooooooooqeCEuQAK2rPSy+CwrpLHTUjUHbWtHBjgLVhYDVmK0JNXY7YL2qwsXoKmS2J5xVqG0LMABXVadYlIwNta8dsFHNToig8VLuwKaWqFxk1HspdlOAxUirk1YVcCg1E688U3BoKHFNC81IBxSbaeq81ZjUYqTbQRxUL4qEtg0wvVSR8Mc1ETk01lpnIp2/ijJJp6xE0phwaesarzSk0bsUx3yKrlsGgvUJfJppbFML5FNxTSDmjGKiuP9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FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFf/2Q==\" />\n",
       "  <g>\n",
       "    <!-- Shaded Cube -->\n",
       "    <polygon points=\"485.19,646.21 172.83,620.07 172.83,147.93 485.19,121.79\" fill=\"#00ff4080\" style=\"filter:brightness(83%)\" />\n",
       "    <polygon points=\"485.19,646.21 485.19,121.79 843.06,145.38 843.06,622.62\" fill=\"#00ff4080\" style=\"filter:brightness(87%)\" />\n",
       "  </g>\n",
       "  <g>\n",
       "    <!-- World Geometry -->\n",
       "    <line x1=\"506.15\" y1=\"384.00\" x2=\"218.81\" y2=\"384.00\" stroke=\"red\" stroke-width=\"2.00\" marker-end=\"url(#arrow-large)\" />\n",
       "    <line x1=\"506.15\" y1=\"384.00\" x2=\"506.15\" y2=\"-90.67\" stroke=\"green\" stroke-width=\"2.00\" marker-end=\"url(#arrow-large)\" />\n",
       "    <line x1=\"506.15\" y1=\"384.00\" x2=\"831.14\" y2=\"384.00\" stroke=\"blue\" stroke-width=\"2.00\" marker-end=\"url(#arrow-large)\" />\n",
       "    <g>\n",
       "      <!-- Wireframe Cube -->\n",
       "      <line x1=\"485.19\" y1=\"646.21\" x2=\"172.83\" y2=\"620.07\" stroke=\"black\" stroke-width=\"2.00\" />\n",
       "      <line x1=\"485.19\" y1=\"646.21\" x2=\"485.19\" y2=\"121.79\" stroke=\"black\" stroke-width=\"2.00\" />\n",
       "      <line x1=\"485.19\" y1=\"646.21\" x2=\"843.06\" y2=\"622.62\" stroke=\"black\" stroke-width=\"2.00\" />\n",
       "      <line x1=\"172.83\" y1=\"620.07\" x2=\"485.19\" y2=\"646.21\" stroke=\"black\" stroke-width=\"2.00\" />\n",
       "      <line x1=\"172.83\" y1=\"620.07\" x2=\"172.83\" y2=\"147.93\" stroke=\"black\" stroke-width=\"2.00\" />\n",
       "      <line x1=\"172.83\" y1=\"620.07\" x2=\"523.47\" y2=\"600.77\" stroke=\"black\" stroke-width=\"2.00\" />\n",
       "      <line x1=\"485.19\" y1=\"121.79\" x2=\"485.19\" y2=\"646.21\" stroke=\"black\" stroke-width=\"2.00\" />\n",
       "      <line x1=\"485.19\" y1=\"121.79\" x2=\"172.83\" y2=\"147.93\" stroke=\"black\" stroke-width=\"2.00\" />\n",
       "      <line x1=\"485.19\" y1=\"121.79\" x2=\"843.06\" y2=\"145.38\" stroke=\"black\" stroke-width=\"2.00\" />\n",
       "      <line x1=\"172.83\" y1=\"147.93\" x2=\"172.83\" y2=\"620.07\" stroke=\"black\" stroke-width=\"2.00\" />\n",
       "      <line x1=\"172.83\" y1=\"147.93\" x2=\"485.19\" y2=\"121.79\" stroke=\"black\" stroke-width=\"2.00\" />\n",
       "      <line x1=\"172.83\" y1=\"147.93\" x2=\"523.47\" y2=\"167.23\" stroke=\"black\" stroke-width=\"2.00\" />\n",
       "      <line x1=\"843.06\" y1=\"622.62\" x2=\"485.19\" y2=\"646.21\" stroke=\"black\" stroke-width=\"2.00\" />\n",
       "      <line x1=\"843.06\" y1=\"622.62\" x2=\"523.47\" y2=\"600.77\" stroke=\"black\" stroke-width=\"2.00\" />\n",
       "      <line x1=\"843.06\" y1=\"622.62\" x2=\"843.06\" y2=\"145.38\" stroke=\"black\" stroke-width=\"2.00\" />\n",
       "      <line x1=\"523.47\" y1=\"600.77\" x2=\"172.83\" y2=\"620.07\" stroke=\"black\" stroke-width=\"2.00\" />\n",
       "      <line x1=\"523.47\" y1=\"600.77\" x2=\"843.06\" y2=\"622.62\" stroke=\"black\" stroke-width=\"2.00\" />\n",
       "      <line x1=\"523.47\" y1=\"600.77\" x2=\"523.47\" y2=\"167.23\" stroke=\"black\" stroke-width=\"2.00\" />\n",
       "      <line x1=\"843.06\" y1=\"145.38\" x2=\"485.19\" y2=\"121.79\" stroke=\"black\" stroke-width=\"2.00\" />\n",
       "      <line x1=\"843.06\" y1=\"145.38\" x2=\"843.06\" y2=\"622.62\" stroke=\"black\" stroke-width=\"2.00\" />\n",
       "      <line x1=\"843.06\" y1=\"145.38\" x2=\"523.47\" y2=\"167.23\" stroke=\"black\" stroke-width=\"2.00\" />\n",
       "      <line x1=\"523.47\" y1=\"167.23\" x2=\"172.83\" y2=\"147.93\" stroke=\"black\" stroke-width=\"2.00\" />\n",
       "      <line x1=\"523.47\" y1=\"167.23\" x2=\"523.47\" y2=\"600.77\" stroke=\"black\" stroke-width=\"2.00\" />\n",
       "      <line x1=\"523.47\" y1=\"167.23\" x2=\"843.06\" y2=\"145.38\" stroke=\"black\" stroke-width=\"2.00\" />\n",
       "    </g>\n",
       "  </g>\n",
       "</svg>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from svg_snip.Composer import Composer\n",
    "import svg_snip.Elements as e2d\n",
    "import svg_snip.Elements3D as e3d\n",
    "\n",
    "svg = Composer(to_gray_image(I1))\n",
    "svg.add(e3d.cube, min=[-50,-50,-50], max=[50,50,50])\n",
    "svg.add(svg_world_geometry)\n",
    "svg.scale=0.5\n",
    "svg.display(P=P1.P, stroke_width=2, head='arrow-large')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "ff4adf52-c848-4839-9881-2751b98c4ef0",
   "metadata": {},
   "outputs": [],
   "source": [
    "with open('output/example_overlay.svg', 'w') as file:\n",
    "    file.write(svg.render(P=P1.P, stroke_width=2, head='arrow-large'))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7b899055-ea9c-4960-97fc-df8fd299da61",
   "metadata": {},
   "source": [
    "# Visualize Geometry of Both Projections"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "63907b66-b471-4481-a80d-7992929bf064",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "fc14a9967e1740e39272c80244b14ec6",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "FloatSlider(value=0.0, description='angle', max=3.14, min=-3.14, step=0.06343434343434344)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "5156efe40c694dd8ac0f1dbcff6ec81a",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "VBox(children=(HTML(value='\\n<style>\\n  .dimension {\\n    top: 0;\\n    left: 0;\\n    width: 1000px;\\n    heigh…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from svg_snip.Jupyter import CanvasWithOverlay\n",
    "from svg_snip.Composer import Composer, Group\n",
    "import svg_snip.Elements as e2d\n",
    "import svg_snip.Elements3D as e3d\n",
    "import ipywidgets as w\n",
    "\n",
    "from ProjectiveGeometry23.homography import rotation_x, rotation_y, rotation_z, scale\n",
    "from ProjectiveGeometry23.svg_utils import (\n",
    "    svg_source_detector,\n",
    "    svg_world_geometry,\n",
    "    svg_coordinate_frame\n",
    ")\n",
    "\n",
    "import copy\n",
    "\n",
    "# create a 3D visualization\n",
    "vis = CanvasWithOverlay(1000, 500)\n",
    "\n",
    "P_display = ProjectionMatrix.perspective_look_at(\n",
    "    eye=np.array([0, 0, 250]),\n",
    "    center=np.array([0, 0, 0]),\n",
    "    image_size=(1000, 500),\n",
    "    fovy_rad=0.7\n",
    ")\n",
    "\n",
    "# slider for detector rotation\n",
    "angle = w.FloatSlider(\n",
    "    value=0,\n",
    "    min=-3.14,\n",
    "    max=3.14,\n",
    "    step=(3.14 - (-3.14)) / 99,\n",
    "    description='angle'\n",
    ")\n",
    "\n",
    "# these parameters are used to rotate and zoom\n",
    "ax = 3.3\n",
    "az = 6.6\n",
    "s = 0.2\n",
    "\n",
    "def world(wireframe=False):\n",
    "    svg = Group()\n",
    "    svg.add(svg_coordinate_frame)\n",
    "    svg.add(e3d.wire_cube if wireframe else e3d.cube,\n",
    "            min=[-50,-50,-50], max=[50,50,50], stroke='black')\n",
    "    return svg\n",
    "    \n",
    "def draw():\n",
    "    svg = Composer((vis.w, vis.h))\n",
    "    svg.add(e2d.rect, width=vis.w, height=vis.h, fill='white')\n",
    "    \n",
    "    svg.add(world())\n",
    "\n",
    "    P_rotated = copy.deepcopy(P0)\n",
    "    P_rotated.P = P0.P @ rotation_y(angle.value)\n",
    "\n",
    "    svg.add(\n",
    "        svg_source_detector,\n",
    "        projection=P_rotated,\n",
    "        draw_on_detector=world(wireframe=True),\n",
    "        label_source=f'{np.degrees(angle.value):.1f}°',\n",
    "        label_detector='I0(u,v)'\n",
    "    )\n",
    "\n",
    "    C0 = P0.getCenterOfProjection()\n",
    "    for a in np.linspace(-3.14, 3.14, 100):\n",
    "        svg.add(e3d.point, X=rotation_y(a) @ C0)\n",
    "\n",
    "    svg.add(e2d.text, x=10, y=20,\n",
    "            content=f'ax={ax:.3f} az={az:.3f}')\n",
    "\n",
    "    T = scale(s) @ rotation_x(ax) @ rotation_z(az)\n",
    "\n",
    "    vis.html_overlay.value = svg.render(\n",
    "        P=P_display.P @ T\n",
    "    )\n",
    "\n",
    "\n",
    "def handle_draw(vis):\n",
    "    global ax\n",
    "    global az\n",
    "\n",
    "    if vis.mouse_state.clicked:\n",
    "        az += vis.mouse_state.dx * 0.01\n",
    "        ax += vis.mouse_state.dy * 0.01\n",
    "\n",
    "    draw()\n",
    "\n",
    "\n",
    "vis.handle_draw = handle_draw\n",
    "\n",
    "# redraw when slider changes\n",
    "angle.observe(lambda _: draw(), names='value')\n",
    "\n",
    "display(angle)\n",
    "vis.display()\n",
    "draw()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f6387766-002b-4945-b8c4-3f3d757b1abe",
   "metadata": {},
   "source": [
    "# Saving Graphics to File"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "362d3735-b044-4d68-bc39-3914c5f903a6",
   "metadata": {},
   "outputs": [],
   "source": [
    "# print(vis.html_overlay.value)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "6cc1c341-3828-4b15-b79f-3c30c0589439",
   "metadata": {},
   "outputs": [],
   "source": [
    "with open('output/trajectory.svg', 'w') as file:\n",
    "    file.write(vis.html_overlay.value)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "88ac882f-0ce1-4d19-b195-0a7e68062812",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
