{ "cells": [ { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [], "source": [ "from dataclasses import dataclass\n", "import numpy as np\n", "from matplotlib import pyplot as plt\n", "\n", "NDArray = np.ndarray\n", "\n", "# Order of detection result\n", "# 0, 1, 2, 3\n", "# TL, TR, BR, BL\n", "# RED, GREEN, BLUE, YELLOW\n", "\n", "\n", "@dataclass\n", "class DiamondBoardParameter:\n", " marker_leghth: float\n", " \"\"\"\n", " the ArUco marker length in meter\n", " \"\"\"\n", " chess_length: float\n", " \"\"\"\n", " the length of the chess board in meter\n", " \"\"\"\n", " border_length: float = 0.01\n", " \"\"\"\n", " border_length in m, default is 1cm\n", " \"\"\"\n", "\n", " @property\n", " def marker_border_length(self):\n", " assert self.chess_length > self.marker_leghth\n", " return (self.chess_length - self.marker_leghth) / 2\n", "\n", " @property\n", " def total_side_length(self):\n", " assert self.chess_length > self.marker_leghth\n", " return self.marker_border_length * 2 + self.chess_length * 3\n", "\n", "\n", "# 9mm + 127mm + 127mm (97mm marker) + 127mm + 10mm\n", "# i.e. marker boarder = 127mm - 97mm = 30mm (15mm each side)\n", "Point2D = tuple[float, float]\n", "Quad2D = tuple[Point2D, Point2D, Point2D, Point2D]\n", "\n", "\n", "@dataclass\n", "class ArUcoMarker2D:\n", " id: int\n", " corners: Quad2D\n", " params: DiamondBoardParameter\n", "\n", " @property\n", " def np_corners(self):\n", " \"\"\"\n", " returns corners in numpy array\n", " (4, 2) shape\n", " \"\"\"\n", " return np.array(self.corners, dtype=np.float32)\n", "\n", "\n", "# let's let TL be the origin\n", "def generate_diamond_corners(\n", " ids: tuple[int, int, int, int], params: DiamondBoardParameter\n", "):\n", " \"\"\"\n", " A diamond chess board, which could be count as a kind of ChArUco board\n", "\n", " C | 0 | C\n", " ---------\n", " 1 | C | 2\n", " ---------\n", " C | 3 | C\n", "\n", " where C is the chess box, and 0, 1, 2, 3 are the markers (whose ids are passed in order)\n", "\n", " Args:\n", " ids: a tuple of 4 ids of the markers\n", " params: DiamondBoardParameter\n", " \"\"\"\n", "\n", " def tl_to_square(tl_x: float, tl_y: float, side_length: float) -> Quad2D:\n", " return (\n", " (tl_x, tl_y),\n", " (tl_x + side_length, tl_y),\n", " (tl_x + side_length, tl_y + side_length),\n", " (tl_x, tl_y + side_length),\n", " )\n", "\n", " tl_0_x = params.border_length + params.chess_length + params.marker_border_length\n", " tl_0_y = params.border_length + params.marker_border_length\n", "\n", " tl_1_x = params.border_length + params.marker_border_length\n", " tl_1_y = params.border_length + params.chess_length + params.marker_border_length\n", "\n", " tl_2_x = (\n", " params.border_length + params.chess_length * 2 + params.marker_border_length\n", " )\n", " tl_2_y = tl_1_y\n", "\n", " tl_3_x = params.border_length + params.chess_length + params.marker_border_length\n", " tl_3_y = (\n", " params.border_length + params.chess_length * 2 + params.marker_border_length\n", " )\n", " return (\n", " ArUcoMarker2D(ids[0], tl_to_square(tl_0_x, tl_0_y, params.marker_leghth), params),\n", " ArUcoMarker2D(ids[1], tl_to_square(tl_1_x, tl_1_y, params.marker_leghth), params),\n", " ArUcoMarker2D(ids[2], tl_to_square(tl_2_x, tl_2_y, params.marker_leghth), params),\n", " ArUcoMarker2D(ids[3], tl_to_square(tl_3_x, tl_3_y, params.marker_leghth), params),\n", " )\n" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "params = DiamondBoardParameter(0.097, 0.127)\n", "markers = generate_diamond_corners((16, 17, 18, 19), params)\n", "\n", "fig = plt.figure()\n", "ax = fig.gca()\n", "ax.set_xlim((0, params.total_side_length))\n", "ax.set_ylim((0, params.total_side_length)) # type: ignore\n", "ax.set_aspect(\"equal\")\n", "# set origin to top-left (from bottom-left)\n", "ax.invert_yaxis()\n", "ax.xaxis.set_ticks_position('top')\n", "\n", "for marker in markers:\n", " plt.plot(*marker.np_corners.T, \"o-\", label=str(marker.id))\n", " for i, (x, y) in enumerate(marker.corners):\n", " ax.text(x, y, str(i))\n", "plt.legend()\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [], "source": [ "from typing import Sequence\n", "import plotly.graph_objects as go\n", "import awkward as ak\n", "import cv2\n", "from cv2.typing import MatLike\n", "\n", "\n", "def transform_point(matrix: MatLike, point: MatLike):\n", " assert matrix.shape == (4, 4)\n", " assert point.shape == (3,)\n", "\n", " # Lift point to 4D\n", " homogeneous_point = np.array([point[0], point[1], point[2], 1])\n", " # Apply transformation\n", " transformed = matrix @ homogeneous_point\n", " # Project back to 3D if w != 1\n", " if transformed[3] != 1:\n", " transformed = transformed / transformed[3]\n", " return transformed[:3]\n", "\n", "\n", "class DiamondPlane3D:\n", " _ids: NDArray\n", " \"\"\"\n", " (n,)\n", " \"\"\"\n", " _corners: NDArray\n", " \"\"\"\n", " (n, 4, 3)\n", " \"\"\"\n", " _transform_matrix: NDArray\n", " \"\"\"\n", " 4x4 transformation matrix\n", " \"\"\"\n", " _normal_vector: NDArray\n", " \"\"\"\n", " (2, 3)\n", " start (the center of the plane) and end (the normal vector), length 1\n", " \"\"\"\n", "\n", " def __init__(self, items: Sequence[ArUcoMarker2D]):\n", " self._ids = np.array([item.id for item in items])\n", " # (n, 4, 2)\n", " corners_2d = np.array([item.np_corners for item in items])\n", " # (n, 4, 3)\n", " self._corners = np.concatenate(\n", " [corners_2d, np.zeros((corners_2d.shape[0], 4, 1))], axis=-1\n", " )\n", " self._transform_matrix = np.eye(4)\n", "\n", " def center(items: Sequence[ArUcoMarker2D]):\n", " return np.mean([item.np_corners for item in items], axis=(0, 1))\n", "\n", " c = center(items)\n", " assert c.shape == (2,)\n", " self._normal_vector = np.array([(c[0], c[1], 0), (c[0], c[1], 0.1)])\n", "\n", " @property\n", " def ids(self):\n", " return self._ids\n", "\n", " @property\n", " def corners(self):\n", " return self._corners\n", "\n", " @property\n", " def transform_matrix(self):\n", " return self._transform_matrix\n", "\n", " @property\n", " def transformed_corners(self):\n", " def g():\n", " for corner in self.corners:\n", " yield np.array(\n", " [transform_point(self.transform_matrix, c) for c in corner]\n", " )\n", "\n", " return np.array(list(g()))\n", "\n", " @property\n", " def transformed_normal_vector(self):\n", " def g():\n", " for v in self._normal_vector:\n", " yield transform_point(self.transform_matrix, v)\n", "\n", " return np.array(list(g()))\n", "\n", " @property\n", " def transformed_geometry_center(self):\n", " return np.mean(self.transformed_corners, axis=(0, 1))\n", "\n", " def local_rotate(self, angle: float, axis: NDArray):\n", " \"\"\"\n", " rotate the plane by angle (in radian) around local center\n", "\n", " Args:\n", " angle: in radian\n", " axis: (3,)\n", "\n", " change basis to local basis, rotate, then change back\n", " \"\"\"\n", " raise NotImplementedError\n", "\n", " def rotate(self, angle: float, axis: NDArray):\n", " \"\"\"\n", " rotate the plane by angle (in radian) around the axis\n", " \"\"\"\n", " assert axis.shape == (3,)\n", " rot_mat = cv2.Rodrigues(axis * angle)[0]\n", " self._transform_matrix[:3, :3] = np.dot(rot_mat, self._transform_matrix[:3, :3])\n", "\n", " def translate(self, vec: NDArray):\n", " \"\"\"\n", " translate the plane by vec\n", " \"\"\"\n", " assert vec.shape == (3,)\n", " self._transform_matrix[:3, 3] += vec\n", "\n", " def set_transform_matrix(self, mat: NDArray):\n", " assert mat.shape == (4, 4)\n", " self._transform_matrix = mat" ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [], "source": [ "plane_a = DiamondPlane3D(markers)\n", "\n", "markers_b = generate_diamond_corners((20, 21, 22, 23), params)\n", "plane_b = DiamondPlane3D(markers_b)\n", "# plane_b.translate(np.array([0, 0, 0.1]))\n", "plane_b.rotate(np.pi/2, np.array([1, 0, 0]))\n", "plane_b.rotate(np.pi, np.array([0, 0, 1]))\n", "tmp_c = plane_b.transformed_geometry_center\n", "plane_b.translate(-tmp_c)\n", "plane_b.rotate(np.pi, np.array([0, 1, 0]))\n", "plane_b.translate(tmp_c)\n", "plane_b.translate(np.array([0, 0, params.total_side_length]))\n", "\n", "markers_c = generate_diamond_corners((24, 25, 26, 27), params)\n", "plane_c = DiamondPlane3D(markers_c)\n", "tmp = plane_c.transformed_geometry_center\n", "plane_c.translate(-tmp)\n", "plane_c.rotate(-np.pi/2, np.array([0, 0, 1]))\n", "plane_c.translate(tmp)\n", "plane_c.translate(np.array([0, params.total_side_length-params.border_length, 0]))\n", "plane_c.rotate(np.pi/2, np.array([0, 1, 0]))\n", "plane_c.translate(np.array([0, 0, params.total_side_length]))" ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "data": { "application/vnd.plotly.v1+json": { "config": { "plotlyServerURL": "https://plot.ly" }, "data": [ { "marker": { "size": 1 }, "mode": "markers+lines+text", "name": "16", "text": [ "16:0", "16:1", "16:2", "16:3" ], "textposition": "middle center", "type": "scatter3d", "x": [ 0.15199999511241913, 0.24899999797344208, 0.24899999797344208, 0.15199999511241913 ], "y": [ 0.02500000037252903, 0.02500000037252903, 0.12200000137090683, 0.12200000137090683 ], "z": [ 0, 0, 0, 0 ] }, { "marker": { "size": 1 }, "mode": "markers+lines+text", "name": "17", "text": [ "17:0", "17:1", "17:2", "17:3" ], "textposition": "middle center", "type": "scatter3d", "x": [ 0.02500000037252903, 0.12200000137090683, 0.12200000137090683, 0.02500000037252903 ], "y": [ 0.15199999511241913, 0.15199999511241913, 0.24899999797344208, 0.24899999797344208 ], "z": [ 0, 0, 0, 0 ] }, { "marker": { "size": 1 }, "mode": "markers+lines+text", "name": "18", "text": [ "18:0", "18:1", "18:2", "18:3" ], "textposition": "middle center", "type": "scatter3d", "x": [ 0.27900001406669617, 0.37599998712539673, 0.37599998712539673, 0.27900001406669617 ], "y": [ 0.15199999511241913, 0.15199999511241913, 0.24899999797344208, 0.24899999797344208 ], "z": [ 0, 0, 0, 0 ] }, { "marker": { "size": 1 }, "mode": "markers+lines+text", "name": "19", "text": [ "19:0", "19:1", "19:2", "19:3" ], "textposition": "middle center", "type": "scatter3d", "x": [ 0.15199999511241913, 0.24899999797344208, 0.24899999797344208, 0.15199999511241913 ], "y": [ 0.27900001406669617, 0.27900001406669617, 0.37599998712539673, 0.37599998712539673 ], "z": [ 0, 0, 0, 0 ] }, { "marker": { "size": 2 }, "mode": "markers+lines", "name": "normal_a", "type": "scatter3d", "x": [ 0.2004999965429306, 0.2004999965429306 ], "y": [ 0.2004999816417694, 0.2004999816417694 ], "z": [ 0, 0.1 ] }, { "marker": { "size": 1 }, "mode": "markers+lines+text", "name": "20", "text": [ "20:0", "20:1", "20:2", "20:3" ], "textposition": "middle center", "type": "scatter3d", "x": [ 0.15199999511241913, 0.24899999797344208, 0.2489999979734421, 0.15199999511241916 ], "y": [ 1.7083822226738725e-17, 2.8962896528842315e-17, 2.3023359491844643e-17, 1.1144285189741054e-17 ], "z": [ 0.385999999627471, 0.38599999962747106, 0.28899999862909326, 0.2889999986290932 ] }, { "marker": { "size": 1 }, "mode": "markers+lines+text", "name": "21", "text": [ "21:0", "21:1", "21:2", "21:3" ], "textposition": "middle center", "type": "scatter3d", "x": [ 0.025000000372529047, 0.12200000137090684, 0.12200000137090686, 0.02500000037252906 ], "y": [ -6.245698330751839e-18, 5.633375743243504e-18, -3.061614078082899e-19, -1.2185235481803633e-17 ], "z": [ 0.2590000048875809, 0.2590000048875809, 0.16200000202655798, 0.16200000202655795 ] }, { "marker": { "size": 1 }, "mode": "markers+lines+text", "name": "22", "text": [ "22:0", "22:1", "22:2", "22:3" ], "textposition": "middle center", "type": "scatter3d", "x": [ 0.27900001406669617, 0.37599998712539673, 0.3759999871253968, 0.2790000140666962 ], "y": [ 2.4860332044642726e-17, 3.6739402697014446e-17, 3.079986554596265e-17, 1.892079489359093e-17 ], "z": [ 0.25900000488758096, 0.25900000488758096, 0.162000002026558, 0.16200000202655798 ] }, { "marker": { "size": 1 }, "mode": "markers+lines+text", "name": "23", "text": [ "23:0", "23:1", "23:2", "23:3" ], "textposition": "middle center", "type": "scatter3d", "x": [ 0.15199999511241916, 0.2489999979734421, 0.24899999797344213, 0.15199999511241918 ], "y": [ 1.5308070390414434e-18, 1.340988134114503e-17, 7.470346014959171e-18, -4.408728287144416e-18 ], "z": [ 0.1319999859333039, 0.1319999859333039, 0.03500001287460333, 0.035000012874603324 ] }, { "marker": { "size": 2 }, "mode": "markers+lines", "name": "normal_b", "type": "scatter3d", "x": [ 0.20049999654293063, 0.20049999654293063 ], "y": [ 1.2277084862200734e-17, 0.10000000000000002 ], "z": [ 0.21050001835823065, 0.21050001835823065 ] }, { "marker": { "size": 1 }, "mode": "markers+lines+text", "name": "24", "text": [ "24:0", "24:1", "24:2", "24:3" ], "type": "scatter3d", "x": [ 1.5308085217450162e-18, 1.5308085217450166e-18, 7.470345558742689e-18, 7.470345558742687e-18 ], "y": [ 0.2490000048875809, 0.15200000202655795, 0.15200000202655795, 0.2490000048875809 ], "z": [ 0.385999999627471, 0.385999999627471, 0.2889999986290932, 0.2889999986290932 ] }, { "marker": { "size": 1 }, "mode": "markers+lines+text", "name": "25", "text": [ "25:0", "25:1", "25:2", "25:3" ], "type": "scatter3d", "x": [ 9.30731537424187e-18, 9.30731537424187e-18, 1.5246852525293664e-17, 1.5246852525293664e-17 ], "y": [ 0.375999999627471, 0.2789999986290932, 0.2789999986290932, 0.375999999627471 ], "z": [ 0.2590000048875809, 0.2590000048875809, 0.16200000202655795, 0.16200000202655795 ] }, { "marker": { "size": 1 }, "mode": "markers+lines+text", "name": "26", "text": [ "26:0", "26:1", "26:2", "26:3" ], "type": "scatter3d", "x": [ 9.307315374241872e-18, 9.307315374241872e-18, 1.5246852525293664e-17, 1.5246852525293664e-17 ], "y": [ 0.12199998593330386, 0.025000012874603295, 0.025000012874603295, 0.12199998593330386 ], "z": [ 0.2590000048875809, 0.2590000048875809, 0.16200000202655793, 0.16200000202655793 ] }, { "marker": { "size": 1 }, "mode": "markers+lines+text", "name": "27", "text": [ "27:0", "27:1", "27:2", "27:3" ], "type": "scatter3d", "x": [ 1.7083823709442298e-17, 1.7083823709442298e-17, 2.3023359035628157e-17, 2.3023359035628157e-17 ], "y": [ 0.2490000048875809, 0.15200000202655795, 0.15200000202655795, 0.2490000048875809 ], "z": [ 0.13199998593330386, 0.13199998593330384, 0.03500001287460329, 0.035000012874603297 ] }, { "marker": { "size": 2 }, "mode": "markers+lines", "name": "normal_c", "textposition": "middle center", "type": "scatter3d", "x": [ 1.2277083037334799e-17, 0.10000000000000002 ], "y": [ 0.20050000345706942, 0.20050000345706942 ], "z": [ 0.21050001835823062, 0.21050001835823062 ] } ], "layout": { "scene": { "aspectmode": "cube", "xaxis": { "range": [ -0.1, 0.41100000000000003 ] }, "yaxis": { "range": [ 0.41100000000000003, -0.1 ] }, "zaxis": { "range": [ -0.1, 0.41100000000000003 ] } }, "template": { "data": { "bar": [ { "error_x": { "color": "#2a3f5f" }, "error_y": { "color": "#2a3f5f" }, "marker": { "line": { "color": "#E5ECF6", "width": 0.5 }, "pattern": { "fillmode": "overlay", "size": 10, "solidity": 0.2 } }, "type": "bar" } ], "barpolar": [ { "marker": { "line": { "color": "#E5ECF6", "width": 0.5 }, "pattern": { "fillmode": "overlay", "size": 10, "solidity": 0.2 } }, "type": "barpolar" } ], "carpet": [ { "aaxis": { "endlinecolor": "#2a3f5f", "gridcolor": "white", "linecolor": "white", "minorgridcolor": "white", "startlinecolor": "#2a3f5f" }, "baxis": { "endlinecolor": "#2a3f5f", "gridcolor": "white", "linecolor": "white", "minorgridcolor": "white", "startlinecolor": "#2a3f5f" }, "type": "carpet" } ], "choropleth": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "type": "choropleth" } ], "contour": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ 0, "#0d0887" ], [ 0.1111111111111111, "#46039f" ], [ 0.2222222222222222, "#7201a8" ], [ 0.3333333333333333, "#9c179e" ], [ 0.4444444444444444, "#bd3786" ], [ 0.5555555555555556, "#d8576b" ], [ 0.6666666666666666, "#ed7953" ], [ 0.7777777777777778, "#fb9f3a" ], [ 0.8888888888888888, "#fdca26" ], [ 1, "#f0f921" ] ], "type": "contour" } ], "contourcarpet": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "type": "contourcarpet" } ], "heatmap": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ 0, "#0d0887" ], [ 0.1111111111111111, "#46039f" ], [ 0.2222222222222222, "#7201a8" ], [ 0.3333333333333333, "#9c179e" ], [ 0.4444444444444444, "#bd3786" ], [ 0.5555555555555556, "#d8576b" ], [ 0.6666666666666666, "#ed7953" ], [ 0.7777777777777778, "#fb9f3a" ], [ 0.8888888888888888, "#fdca26" ], [ 1, "#f0f921" ] ], "type": "heatmap" } ], "heatmapgl": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ 0, "#0d0887" ], [ 0.1111111111111111, "#46039f" ], [ 0.2222222222222222, "#7201a8" ], [ 0.3333333333333333, "#9c179e" ], [ 0.4444444444444444, "#bd3786" ], [ 0.5555555555555556, "#d8576b" ], [ 0.6666666666666666, "#ed7953" ], [ 0.7777777777777778, "#fb9f3a" ], [ 0.8888888888888888, "#fdca26" ], [ 1, "#f0f921" ] ], "type": "heatmapgl" } ], "histogram": [ { "marker": { "pattern": { "fillmode": "overlay", "size": 10, "solidity": 0.2 } }, "type": "histogram" } ], "histogram2d": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ 0, "#0d0887" ], [ 0.1111111111111111, "#46039f" ], [ 0.2222222222222222, "#7201a8" ], [ 0.3333333333333333, "#9c179e" ], [ 0.4444444444444444, "#bd3786" ], [ 0.5555555555555556, "#d8576b" ], [ 0.6666666666666666, "#ed7953" ], [ 0.7777777777777778, "#fb9f3a" ], [ 0.8888888888888888, "#fdca26" ], [ 1, "#f0f921" ] ], "type": "histogram2d" } ], "histogram2dcontour": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ 0, "#0d0887" ], [ 0.1111111111111111, "#46039f" ], [ 0.2222222222222222, "#7201a8" ], [ 0.3333333333333333, "#9c179e" ], [ 0.4444444444444444, "#bd3786" ], [ 0.5555555555555556, "#d8576b" ], [ 0.6666666666666666, "#ed7953" ], [ 0.7777777777777778, "#fb9f3a" ], [ 0.8888888888888888, "#fdca26" ], [ 1, "#f0f921" ] ], "type": "histogram2dcontour" } ], "mesh3d": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "type": "mesh3d" } ], "parcoords": [ { "line": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "parcoords" } ], "pie": [ { "automargin": true, "type": "pie" } ], "scatter": [ { "fillpattern": { "fillmode": "overlay", "size": 10, "solidity": 0.2 }, "type": "scatter" } ], "scatter3d": [ { "line": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "scatter3d" } ], "scattercarpet": [ { "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "scattercarpet" } ], "scattergeo": [ { "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "scattergeo" } ], "scattergl": [ { "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "scattergl" } ], "scattermapbox": [ { "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "scattermapbox" } ], "scatterpolar": [ { "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "scatterpolar" } ], "scatterpolargl": [ { "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "scatterpolargl" } ], "scatterternary": [ { "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "scatterternary" } ], "surface": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ 0, "#0d0887" ], [ 0.1111111111111111, "#46039f" ], [ 0.2222222222222222, "#7201a8" ], [ 0.3333333333333333, "#9c179e" ], [ 0.4444444444444444, "#bd3786" ], [ 0.5555555555555556, "#d8576b" ], [ 0.6666666666666666, "#ed7953" ], [ 0.7777777777777778, "#fb9f3a" ], [ 0.8888888888888888, "#fdca26" ], [ 1, "#f0f921" ] ], "type": "surface" } ], "table": [ { "cells": { "fill": { "color": "#EBF0F8" }, "line": { "color": "white" } }, "header": { "fill": { "color": "#C8D4E3" }, "line": { "color": "white" } }, "type": "table" } ] }, "layout": { "annotationdefaults": { "arrowcolor": "#2a3f5f", "arrowhead": 0, "arrowwidth": 1 }, "autotypenumbers": "strict", "coloraxis": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "colorscale": { "diverging": [ [ 0, "#8e0152" ], [ 0.1, "#c51b7d" ], [ 0.2, "#de77ae" ], [ 0.3, "#f1b6da" ], [ 0.4, "#fde0ef" ], [ 0.5, "#f7f7f7" ], [ 0.6, "#e6f5d0" ], [ 0.7, "#b8e186" ], [ 0.8, "#7fbc41" ], [ 0.9, "#4d9221" ], [ 1, "#276419" ] ], "sequential": [ [ 0, "#0d0887" ], [ 0.1111111111111111, "#46039f" ], [ 0.2222222222222222, "#7201a8" ], [ 0.3333333333333333, "#9c179e" ], [ 0.4444444444444444, "#bd3786" ], [ 0.5555555555555556, "#d8576b" ], [ 0.6666666666666666, "#ed7953" ], [ 0.7777777777777778, "#fb9f3a" ], [ 0.8888888888888888, "#fdca26" ], [ 1, "#f0f921" ] ], "sequentialminus": [ [ 0, "#0d0887" ], [ 0.1111111111111111, "#46039f" ], [ 0.2222222222222222, "#7201a8" ], [ 0.3333333333333333, "#9c179e" ], [ 0.4444444444444444, "#bd3786" ], [ 0.5555555555555556, "#d8576b" ], [ 0.6666666666666666, "#ed7953" ], [ 0.7777777777777778, "#fb9f3a" ], [ 0.8888888888888888, "#fdca26" ], [ 1, "#f0f921" ] ] }, "colorway": [ "#636efa", "#EF553B", "#00cc96", "#ab63fa", "#FFA15A", "#19d3f3", "#FF6692", "#B6E880", "#FF97FF", "#FECB52" ], "font": { "color": "#2a3f5f" }, "geo": { "bgcolor": "white", "lakecolor": "white", "landcolor": "#E5ECF6", "showlakes": true, "showland": true, "subunitcolor": "white" }, "hoverlabel": { "align": "left" }, "hovermode": "closest", "mapbox": { "style": "light" }, "paper_bgcolor": "white", "plot_bgcolor": "#E5ECF6", "polar": { "angularaxis": { "gridcolor": "white", "linecolor": "white", "ticks": "" }, "bgcolor": "#E5ECF6", "radialaxis": { "gridcolor": "white", "linecolor": "white", "ticks": "" } }, "scene": { "xaxis": { "backgroundcolor": "#E5ECF6", "gridcolor": "white", "gridwidth": 2, "linecolor": "white", "showbackground": true, "ticks": "", "zerolinecolor": "white" }, "yaxis": { "backgroundcolor": "#E5ECF6", "gridcolor": "white", "gridwidth": 2, "linecolor": "white", "showbackground": true, "ticks": "", "zerolinecolor": "white" }, "zaxis": { "backgroundcolor": "#E5ECF6", "gridcolor": "white", "gridwidth": 2, "linecolor": "white", "showbackground": true, "ticks": "", "zerolinecolor": "white" } }, "shapedefaults": { "line": { "color": "#2a3f5f" } }, "ternary": { "aaxis": { "gridcolor": "white", "linecolor": "white", "ticks": "" }, "baxis": { "gridcolor": "white", "linecolor": "white", "ticks": "" }, "bgcolor": "#E5ECF6", "caxis": { "gridcolor": "white", "linecolor": "white", "ticks": "" } }, "title": { "x": 0.05 }, "xaxis": { "automargin": true, "gridcolor": "white", "linecolor": "white", "ticks": "", "title": { "standoff": 15 }, "zerolinecolor": "white", "zerolinewidth": 2 }, "yaxis": { "automargin": true, "gridcolor": "white", "linecolor": "white", "ticks": "", "title": { "standoff": 15 }, "zerolinecolor": "white", "zerolinewidth": 2 } } } } } }, "metadata": {}, "output_type": "display_data" } ], "source": [ "fig = go.Figure()\n", "t_corners_a = plane_a.transformed_corners\n", "for i, corners in enumerate(t_corners_a):\n", " fig.add_trace(\n", " go.Scatter3d(\n", " x=corners[:, 0],\n", " y=corners[:, 1],\n", " z=corners[:, 2],\n", " mode=\"markers+lines+text\",\n", " text=list(map(lambda x: f\"{plane_a.ids[i]}:{x}\", range(4))),\n", " textposition=\"middle center\",\n", " name=str(plane_a.ids[i]),\n", " marker=dict(size=1),\n", " )\n", " )\n", "\n", "# normal vector\n", "fig.add_trace(\n", " go.Scatter3d(\n", " x=plane_a.transformed_normal_vector[:, 0],\n", " y=plane_a.transformed_normal_vector[:, 1],\n", " z=plane_a.transformed_normal_vector[:, 2],\n", " mode=\"markers+lines\",\n", " name=\"normal_a\",\n", " marker=dict(size=2),\n", " )\n", ")\n", "\n", "t_corners_b = plane_b.transformed_corners\n", "for i, corners in enumerate(t_corners_b):\n", " fig.add_trace(\n", " go.Scatter3d(\n", " x=corners[:, 0],\n", " y=corners[:, 1],\n", " z=corners[:, 2],\n", " mode=\"markers+lines+text\",\n", " text=list(map(lambda x: f\"{plane_b.ids[i]}:{x}\", range(4))),\n", " textposition=\"middle center\",\n", " name=str(plane_b.ids[i]),\n", " marker=dict(size=1),\n", " )\n", " )\n", "fig.add_trace(\n", " go.Scatter3d(\n", " x=plane_b.transformed_normal_vector[:, 0],\n", " y=plane_b.transformed_normal_vector[:, 1],\n", " z=plane_b.transformed_normal_vector[:, 2],\n", " mode=\"markers+lines\",\n", " name=\"normal_b\",\n", " marker=dict(size=2),\n", " )\n", ")\n", "\n", "t_corners_c = plane_c.transformed_corners\n", "for i, corners in enumerate(t_corners_c):\n", " fig.add_trace(\n", " go.Scatter3d(\n", " x=corners[:, 0],\n", " y=corners[:, 1],\n", " z=corners[:, 2],\n", " mode=\"markers+lines+text\",\n", " text=list(map(lambda x: f\"{plane_c.ids[i]}:{x}\", range(4))),\n", " name=str(plane_c.ids[i]),\n", " marker=dict(size=1),\n", " )\n", " )\n", "fig.add_trace(\n", " go.Scatter3d(\n", " x=plane_c.transformed_normal_vector[:, 0],\n", " y=plane_c.transformed_normal_vector[:, 1],\n", " z=plane_c.transformed_normal_vector[:, 2],\n", " mode=\"markers+lines\",\n", " textposition=\"middle center\",\n", " name=\"normal_c\",\n", " marker=dict(size=2),\n", " )\n", ")\n", "\n", "# fig.update_layout(\n", "# scene=dict(\n", "# aspectmode=\"cube\",\n", "# yaxis_autorange=\"reversed\",\n", "# )\n", "# )\n", "\n", "fig.update_layout(\n", " scene=dict(\n", " aspectmode='cube',\n", " xaxis=dict(range=[-0.1, params.total_side_length]),\n", " yaxis=dict(range=[params.total_side_length, -0.1]),\n", " zaxis=dict(range=[-0.1, params.total_side_length]),\n", " )\n", ")\n", "fig.show()" ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
[{name: 'a', ids: [16, 17, 18, 19], corners: [[...], ...]},\n",
       " {name: 'b', ids: [20, 21, 22, 23], corners: [[...], ...]},\n",
       " {name: 'c', ids: [24, 25, 26, 27], corners: [[...], ...]}]\n",
       "-----------------------------------------------------------\n",
       "type: 3 * {\n",
       "    name: string,\n",
       "    ids: var * int64,\n",
       "    corners: var * var * var * float64\n",
       "}
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import awkward as ak\n", "from awkward import Record as AwkwardRecord, Array as AwkwardArray\n", "\n", "coords = AwkwardArray(\n", " [\n", " {\n", " \"name\": \"a\",\n", " \"ids\": plane_a.ids,\n", " \"corners\": t_corners_a,\n", " },\n", " {\n", " \"name\": \"b\",\n", " \"ids\": plane_b.ids,\n", " \"corners\": t_corners_b,\n", " },\n", " {\n", " \"name\": \"c\",\n", " \"ids\": plane_c.ids,\n", " \"corners\": t_corners_c,\n", " },\n", " ]\n", ")\n", "display(coords)\n", "_ = ak.to_parquet(coords, \"output/object_points.parquet\")" ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [], "source": [ "from typing import cast\n", "total_ids = cast(NDArray, ak.to_numpy(coords[\"ids\"])).flatten()\n", "total_corners = cast(NDArray, ak.to_numpy(coords[\"corners\"])).reshape(-1, 4, 3)\n", "#display(total_ids, total_corners)" ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{16: array([[0.152, 0.025, 0. ],\n", " [0.249, 0.025, 0. ],\n", " [0.249, 0.122, 0. ],\n", " [0.152, 0.122, 0. ]]),\n", " 17: array([[0.025, 0.152, 0. ],\n", " [0.122, 0.152, 0. ],\n", " [0.122, 0.249, 0. ],\n", " [0.025, 0.249, 0. ]]),\n", " 18: array([[0.27900001, 0.152 , 0. ],\n", " [0.37599999, 0.152 , 0. ],\n", " [0.37599999, 0.249 , 0. ],\n", " [0.27900001, 0.249 , 0. ]]),\n", " 19: array([[0.152 , 0.27900001, 0. ],\n", " [0.249 , 0.27900001, 0. ],\n", " [0.249 , 0.37599999, 0. ],\n", " [0.152 , 0.37599999, 0. ]]),\n", " 20: array([[1.51999995e-01, 1.70838222e-17, 3.86000000e-01],\n", " [2.48999998e-01, 2.89628965e-17, 3.86000000e-01],\n", " [2.48999998e-01, 2.30233595e-17, 2.88999999e-01],\n", " [1.51999995e-01, 1.11442852e-17, 2.88999999e-01]]),\n", " 21: array([[ 2.50000004e-02, -6.24569833e-18, 2.59000005e-01],\n", " [ 1.22000001e-01, 5.63337574e-18, 2.59000005e-01],\n", " [ 1.22000001e-01, -3.06161408e-19, 1.62000002e-01],\n", " [ 2.50000004e-02, -1.21852355e-17, 1.62000002e-01]]),\n", " 22: array([[2.79000014e-01, 2.48603320e-17, 2.59000005e-01],\n", " [3.75999987e-01, 3.67394027e-17, 2.59000005e-01],\n", " [3.75999987e-01, 3.07998655e-17, 1.62000002e-01],\n", " [2.79000014e-01, 1.89207949e-17, 1.62000002e-01]]),\n", " 23: array([[ 1.51999995e-01, 1.53080704e-18, 1.31999986e-01],\n", " [ 2.48999998e-01, 1.34098813e-17, 1.31999986e-01],\n", " [ 2.48999998e-01, 7.47034601e-18, 3.50000129e-02],\n", " [ 1.51999995e-01, -4.40872829e-18, 3.50000129e-02]]),\n", " 24: array([[1.53080852e-18, 2.49000005e-01, 3.86000000e-01],\n", " [1.53080852e-18, 1.52000002e-01, 3.86000000e-01],\n", " [7.47034556e-18, 1.52000002e-01, 2.88999999e-01],\n", " [7.47034556e-18, 2.49000005e-01, 2.88999999e-01]]),\n", " 25: array([[9.30731537e-18, 3.76000000e-01, 2.59000005e-01],\n", " [9.30731537e-18, 2.78999999e-01, 2.59000005e-01],\n", " [1.52468525e-17, 2.78999999e-01, 1.62000002e-01],\n", " [1.52468525e-17, 3.76000000e-01, 1.62000002e-01]]),\n", " 26: array([[9.30731537e-18, 1.21999986e-01, 2.59000005e-01],\n", " [9.30731537e-18, 2.50000129e-02, 2.59000005e-01],\n", " [1.52468525e-17, 2.50000129e-02, 1.62000002e-01],\n", " [1.52468525e-17, 1.21999986e-01, 1.62000002e-01]]),\n", " 27: array([[1.70838237e-17, 2.49000005e-01, 1.31999986e-01],\n", " [1.70838237e-17, 1.52000002e-01, 1.31999986e-01],\n", " [2.30233590e-17, 1.52000002e-01, 3.50000129e-02],\n", " [2.30233590e-17, 2.49000005e-01, 3.50000129e-02]])}" ] }, "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dict(zip(total_ids, total_corners))" ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [], "source": [ "total_ids = np.concatenate([plane_a.ids, plane_b.ids, plane_c.ids])\n", "total_corners = np.concatenate([t_corners_a, t_corners_b, t_corners_c])\n", "id_corner_map: dict[int, NDArray] = dict(zip(total_ids, total_corners))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "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.8" } }, "nbformat": 4, "nbformat_minor": 2 }