diff --git a/scripts/qmbb/CodeGenTest.ipynb b/scripts/qmbb/CodeGenTest.ipynb new file mode 100644 index 0000000000..64a8078d2a --- /dev/null +++ b/scripts/qmbb/CodeGenTest.ipynb @@ -0,0 +1,346 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import sympy as sp\n", + "import numpy as np\n", + "sp.init_printing()\n", + "\n", + "%load_ext cython" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "x0, x1, x2 = sp.symbols(\"x0, x1, x2\")\n", + "params = sp.MatrixSymbol('params', 3, 1)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "A = sp.Matrix([x0,\n", + " x1, \n", + " x2, \n", + " x2**2+x0+x1, \n", + " x1**2+x1+1, \n", + " x1**2+x0+1, \n", + " x2+1, \n", + " x0**2+x1+x2, \n", + " x2**2+x2+1, \n", + " x2**2+x0**2+x0+2, \n", + " x1+1, \n", + " x0**2+x0+x1])" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "param_map = dict(zip([x0, x1, x2], params))\n", + "B = A.xreplace(param_map)\n", + "R = sp.MatrixSymbol(\"coeff\", B.shape[0], B.shape[1])" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "from sympy.printing.ccode import C99CodePrinter\n", + "printer = C99CodePrinter()\n", + "\n", + "class CustomCodePrinter(C99CodePrinter):\n", + " def _print_Pow(self, expr):\n", + " if expr.exp.is_integer and expr.exp > 0 and expr.exp < 5:\n", + " return '*'.join([self._print(expr.base) for i in range(expr.exp)])\n", + " else:\n", + " return super()._print_Pow(expr)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "from sympy.utilities.codegen import codegen, default_datatypes" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle coeff = \\left[\\begin{matrix}params_{0, 0}\\\\params_{1, 0}\\\\params_{2, 0}\\\\params_{0, 0} + params_{1, 0} + params_{2, 0}^{2}\\\\params_{1, 0}^{2} + params_{1, 0} + 1\\\\params_{0, 0} + params_{1, 0}^{2} + 1\\\\params_{2, 0} + 1\\\\params_{0, 0}^{2} + params_{1, 0} + params_{2, 0}\\\\params_{2, 0}^{2} + params_{2, 0} + 1\\\\params_{0, 0}^{2} + params_{0, 0} + params_{2, 0}^{2} + 2\\\\params_{1, 0} + 1\\\\params_{0, 0}^{2} + params_{0, 0} + params_{1, 0}\\end{matrix}\\right]$" + ], + "text/plain": [ + " ⎡ params₀₀ ⎤\n", + " ⎢ ⎥\n", + " ⎢ params₁₀ ⎥\n", + " ⎢ ⎥\n", + " ⎢ params₂₀ ⎥\n", + " ⎢ ⎥\n", + " ⎢ 2 ⎥\n", + " ⎢ params₀₀ + params₁₀ + params₂₀ ⎥\n", + " ⎢ ⎥\n", + " ⎢ 2 ⎥\n", + " ⎢ params₁₀ + params₁₀ + 1 ⎥\n", + " ⎢ ⎥\n", + " ⎢ 2 ⎥\n", + " ⎢ params₀₀ + params₁₀ + 1 ⎥\n", + " ⎢ ⎥\n", + "coeff = ⎢ params₂₀ + 1 ⎥\n", + " ⎢ ⎥\n", + " ⎢ 2 ⎥\n", + " ⎢ params₀₀ + params₁₀ + params₂₀ ⎥\n", + " ⎢ ⎥\n", + " ⎢ 2 ⎥\n", + " ⎢ params₂₀ + params₂₀ + 1 ⎥\n", + " ⎢ ⎥\n", + " ⎢ 2 2 ⎥\n", + " ⎢params₀₀ + params₀₀ + params₂₀ + 2⎥\n", + " ⎢ ⎥\n", + " ⎢ params₁₀ + 1 ⎥\n", + " ⎢ ⎥\n", + " ⎢ 2 ⎥\n", + " ⎣ params₀₀ + params₀₀ + params₁₀ ⎦" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sp.Eq(R,B)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/******************************************************************************\n", + " * Code generated with sympy 1.4 *\n", + " * *\n", + " * See http://www.sympy.org/ for more information. *\n", + " * *\n", + " * This file is part of 'project' *\n", + " ******************************************************************************/\n", + "#include \"coefficients.h\"\n", + "#include \n", + "\n", + "void coefficients(float *params, float *coeff) {\n", + "\n", + " coeff[0] = params[0];\n", + " coeff[1] = params[1];\n", + " coeff[2] = params[2];\n", + " coeff[3] = params[0] + params[1] + params[2]*params[2];\n", + " coeff[4] = params[1]*params[1] + params[1] + 1;\n", + " coeff[5] = params[0] + params[1]*params[1] + 1;\n", + " coeff[6] = params[2] + 1;\n", + " coeff[7] = params[0]*params[0] + params[1] + params[2];\n", + " coeff[8] = params[2]*params[2] + params[2] + 1;\n", + " coeff[9] = params[0]*params[0] + params[0] + params[2]*params[2] + 2;\n", + " coeff[10] = params[1] + 1;\n", + " coeff[11] = params[0]*params[0] + params[0] + params[1];\n", + "\n", + "}\n", + "\n" + ] + } + ], + "source": [ + "from sympy.codegen.ast import real, float32\n", + "customprinter = CustomCodePrinter()\n", + "#customprinter.type_aliases[real] = float32 # cosf instead of cos\n", + "default_datatypes[\"float\"].cname = \"float\" # float instead of double\n", + "\n", + "[(cf, cs), (hf, hs)] = codegen(('coefficients', sp.Eq(R,B)), language='c', printer=customprinter)\n", + "print(cs)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "codegen(('coefficients', sp.Eq(R,B)), language='c', printer=customprinter, prefix='coefficients', to_files=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting cy_coefficients.pyxbld\n" + ] + } + ], + "source": [ + "%%writefile cy_coefficients.pyxbld\n", + "import numpy\n", + "\n", + "# module name specified by `%%cython_pyximport` magic\n", + "# | just `modname + \".pyx\"`\n", + "# | |\n", + "def make_ext(modname, pyxfilename):\n", + " from setuptools.extension import Extension\n", + " return Extension(modname,\n", + " sources=[pyxfilename, 'coefficients.c'],\n", + " include_dirs=['.', numpy.get_include()])" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/nfs/ka/home/freibold/.local/lib/python3.7/site-packages/Cython/Compiler/Main.py:369: FutureWarning: Cython directive 'language_level' not set, using 2 for now (Py2). This will change in a later release! File: /nfs/ka/home/freibold/OneDrive/MotionCurve/cy_coefficients.pyx\n", + " tree = Parsing.p_module(s, pxd, full_module_name)\n" + ] + } + ], + "source": [ + "%%cython_pyximport cy_coefficients\n", + "import numpy as np\n", + "cimport numpy as cnp # cimport gives us access to NumPy's C API\n", + "\n", + "# here we just replicate the function signature from the header\n", + "cdef extern from \"coefficients.h\":\n", + " void coefficients(float *params, float *result)\n", + "\n", + "# here is the \"wrapper\" signature that conforms to the odeint interface\n", + "def cy_coefficients(cnp.ndarray[cnp.float32_t, ndim=1] params, size):\n", + " # preallocate our output array\n", + " cdef cnp.ndarray[cnp.float32_t, ndim=1] result = np.empty(size, dtype='float32')\n", + " # now call the C function\n", + " coefficients( params.data, result.data)\n", + " # return the result\n", + " return result" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ 1. 2. 3. 12. 7. 6. 4. 6. 13. 13. 3. 4.]\n" + ] + } + ], + "source": [ + "params = np.array([1, 2, 3], dtype='float32')\n", + "result = cy_coefficients(params, 12)\n", + "print(result)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle \\left[\\begin{matrix}1\\\\2\\\\3\\\\12\\\\7\\\\6\\\\4\\\\6\\\\13\\\\13\\\\3\\\\4\\end{matrix}\\right]$" + ], + "text/plain": [ + "⎡1 ⎤\n", + "⎢ ⎥\n", + "⎢2 ⎥\n", + "⎢ ⎥\n", + "⎢3 ⎥\n", + "⎢ ⎥\n", + "⎢12⎥\n", + "⎢ ⎥\n", + "⎢7 ⎥\n", + "⎢ ⎥\n", + "⎢6 ⎥\n", + "⎢ ⎥\n", + "⎢4 ⎥\n", + "⎢ ⎥\n", + "⎢6 ⎥\n", + "⎢ ⎥\n", + "⎢13⎥\n", + "⎢ ⎥\n", + "⎢13⎥\n", + "⎢ ⎥\n", + "⎢3 ⎥\n", + "⎢ ⎥\n", + "⎣4 ⎦" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "A.subs({(x0, 1), (x1, 2), (x2, 3)})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "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.10.10" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/scripts/qmbb/MotionBounds.ipynb b/scripts/qmbb/MotionBounds.ipynb new file mode 100644 index 0000000000..ea45d6e335 --- /dev/null +++ b/scripts/qmbb/MotionBounds.ipynb @@ -0,0 +1,3861 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Derivation of motion bounds\n", + "\n", + "This document describes how the bounds of an animated object are computed in Embree.\n", + "\n", + "Animation is implemented by specifing transformation matrices and vertex positions for a number of time steps. We will assume that there are only time steps $t_0=0$ and $t_1=1$. For each of these time steps the animation is modelled as a product of three matrices $A=D R S$ where $D$ is a translation matrix, $S$ is an upper triangular matrix and $R$ is a rotation matrix defined using quaternions. The matrices $D$ and $S$ are interpolated linearly for $t\\in(0,1)$ and the quaternions are interpolated spherically (see https://en.wikipedia.org/wiki/Slerp). Also, the position of a vertex is specified per time step and interpolated lineraly in between. With this we can write the motion curve of a vertex as" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "$\\Gamma(t, S_0, S_1, Q_0, Q_1, D_0, D_1, p_0, p_1) = \\text{lerp}(D_0, D_1, t) \\ \\text{slerp}(Q_0, Q_1, t) \\ \\text{lerp}(S_0, S_1, t) \\ \\text{lerp}(p_0, p_1, t)$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To compute bounds for this curve over the whole time range $[0,1]$ we will evaluate compute the roots of the derivative $\\frac{d\\Gamma}{dt}$. We will then evaluate $\\Gamma$ for the corresponding times and the interval boundaries $t=0, t=1$. This is needed because the roots of the derivative might be outside of $[0,1]$ and the interval boundaries are the extrema in this case." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generating Code \n", + "\n", + "The following code will create an expression for $\\Gamma$ using the computer algebra system sympy" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib notebook\n", + "import sympy as sp\n", + "import numpy as np\n", + "import math\n", + "\n", + "t, theta = sp.symbols(\"t, theta\", real = True)\n", + "\n", + "px0, py0, pz0 = sp.symbols(\"px0, py0, pz0\", real=True) # vertex position at t=0\n", + "px1, py1, pz1 = sp.symbols(\"px1, py1, pz1\", real=True) # vertex position at t=1\n", + "\n", + "tx0, ty0, tz0 = sp.symbols(\"tx0, ty0, tz0\", real=True) # translation at t=0\n", + "tx1, ty1, tz1 = sp.symbols(\"tx1, ty1, tz1\", real=True) # translation at t=1\n", + "\n", + "qx0, qy0, qz0, qw0 = sp.symbols(\"qx0, qy0, qz0, qw0\", real=True) # quaternion at t=0 \n", + "qx1, qy1, qz1, qw1 = sp.symbols(\"qx1, qy1, qz1, qw1\", real=True) # quaternion at t=1\n", + "\n", + "# coefficients for upper triangular matrices\n", + "s000, s001, s002, s003, s011, s012, s013, s022, s023 = sp.symbols(\"s000, s001, s002, s003, s011, s012, s013, s022, s023\", real=True)\n", + "s100, s101, s102, s103, s111, s112, s113, s122, s123 = sp.symbols(\"s100, s101, s102, s103, s111, s112, s113, s122, s123\", real=True)\n", + "\n", + "# simple linear interpolation wrapper\n", + "def lerp(v0,v1,t):\n", + " return v0*(1-t)+v1*t\n", + "\n", + "# custom quaternion to matrix conversion\n", + "def to_rotation_matrix(q):\n", + " return sp.Matrix([[q.a*q.a + q.b*q.b - q.c*q.c - q.d*q.d, 2*(q.b*q.c - q.a*q.d), 2*(q.b*q.d + q.a*q.c), 0], \n", + " [2*(q.b*q.c + q.a*q.d), q.a*q.a - q.b*q.b + q.c*q.c - q.d*q.d, 2*(q.c*q.d - q.a*q.b), 0], \n", + " [2*(q.b*q.d - q.a*q.c), 2*(q.c*q.d + q.a*q.b), q.a*q.a - q.b*q.b - q.c*q.c + q.d*q.d, 0],\n", + " [0, 0, 0, 1]])\n", + "\n", + "q0 = sp.Quaternion(qw0, qx0, qy0, qz0)\n", + "q1 = sp.Quaternion(qw1, qx1, qy1, qz1)\n", + "\n", + "# assuming that q1 is qperp = normalize(q1-q0*cosTheta), where cosTheta=dot(q0, q1) and theta = acos(cosTheta).\n", + "# this simplifies the terms of the symbolic expressions later\n", + "qt = q0 * sp.cos(t*theta) + q1 * sp.sin(t*theta)\n", + "\n", + "S0 = sp.Matrix([[s000, s001, s002, s003],\n", + " [ 0, s011, s012, s013],\n", + " [ 0, 0, s022, s023],\n", + " [ 0, 0, 0, 1]])\n", + "S1 = sp.Matrix([[s100, s101, s102, s103],\n", + " [ 0, s111, s112, s113],\n", + " [ 0, 0, s122, s123],\n", + " [ 0, 0, 0, 1]])\n", + "D0 = sp.Matrix([[1, 0, 0, tx0],\n", + " [0, 1, 0, ty0],\n", + " [0, 0, 1, tz0],\n", + " [0, 0, 0, 1]])\n", + "D1 = sp.Matrix([[1, 0, 0, tx1],\n", + " [0, 1, 0, ty1],\n", + " [0, 0, 1, tz1],\n", + " [0, 0, 0, 1]])\n", + "p0 = sp.Matrix([px0, py0, pz0, 1])\n", + "p1 = sp.Matrix([px1, py1, pz1, 1])\n", + "\n", + "Gamma = lerp(D0, D1, t)*to_rotation_matrix(qt)*lerp(S0, S1, t)*lerp(p0, p1, t)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To bound this motion curve we will follow a similar approach as PBRT (see http://www.pbr-book.org/3ed-2018/Geometry_and_Transformations/Animating_Transformations.html) and write the derivative of $\\Gamma$ in a form that\n", + "can be quickly evaluated for varying times $t$ and vertex coordinates $p_0, p_1$. This form will be" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\\begin{align}\n", + "\\frac{d \\Gamma(t, ..., p_0, p_1)}{dt} &= c_0(..., p_0,p_1) + c_1(..., p_0,p_1) t\\\\\n", + "&+ (c_2(..., p_0,p_1) + c_3(..., p_0,p_1) t + c_4(..., p_0,p_1) t^2) \\cos(2t\\theta) \\\\\n", + "&+ (c_5(..., p_0,p_1) + c_6(..., p_0,p_1) t + c_7(..., p_0,p_1) t^2) \\sin(2t\\theta)\n", + "\\end{align}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "where $\\theta=\\cos^{-1}(Q_0 \\cdot Q_1)$ and the coefficients $c_i$ are of the form" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\\begin{align}\n", + "c_i(...,p_0, p_1) = k_0(...) + (k_1(...), k_2(...), k_3(...))^T p_0 + (k_4(...), k_5(...), k_6(...))^T p_1\n", + "\\end{align}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Due to the limited functionality of sympy we will do this decomposition for each dimension $x$, $y$, and $z$ seperately. Therefore there will be $168$ real-valued coefficients $k_j$. In the following we will derive expressions for all of these coefficients depending only on the input parameters of $S_i$, $Q_i$, and $D_i$. " + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "def getTerms(map, key):\n", + " if key in map.keys():\n", + " return map[key]\n", + " return 0\n", + "\n", + "C = sp.Matrix(np.empty(8)) # 8 coefficients\n", + "K = sp.Matrix(np.empty(7)) # 7 inputs\n", + "A = sp.Matrix(np.empty(8*7*3)) # 8 coefficients, 7 inputs (1, px0, py0, pz0, px1, py1, pz1), 3 dimensions (x, y, z)\n", + "dGamma = sp.diff(Gamma, t)\n", + "\n", + "# loop over dimensions (x, y, z)\n", + "for dim in range(3):\n", + " dm = sp.expand(dGamma[dim])\n", + " dm = dm.subs(sp.sin(t*theta)*sp.sin(t*theta),(1-sp.cos(2*t*theta))/2) # remove sin(t*theta)^2\n", + " dm = dm.subs(sp.cos(t*theta)*sp.cos(t*theta),(1+sp.cos(2*t*theta))/2) # remove cos(t*theta)^2\n", + " dm = dm.subs(sp.sin(t*theta)*sp.cos(t*theta),sp.sin(2*t*theta)/2) # remove sin(t*theta)*cos(t*theta)\n", + " dm = sp.expand(dm)\n", + " \n", + " # group all terms in the form a + b * cos(2*t*theta) + c * sin(2*t*theta)\n", + " dm_cos_sin = sp.collect(dm, (sp.cos(2*t*theta), sp.sin(2*t*theta)), evaluate=False)\n", + "\n", + " # get the terms\n", + " coeff_cos = getTerms(dm_cos_sin, sp.cos(2*t*theta))\n", + " coeff_sin = getTerms(dm_cos_sin, sp.sin(2*t*theta))\n", + " coeff_const = getTerms(dm_cos_sin, 1)\n", + "\n", + " # group the term in the form a + b * t \n", + " coeff_const_t = sp.collect(coeff_const, t, evaluate=False)\n", + " C[0] = getTerms(coeff_const_t, 1)\n", + " C[1] = getTerms(coeff_const_t, t)\n", + "\n", + " # group the term in the form a + b * t + c * t^2 \n", + " coeff_cos_t = sp.collect(coeff_cos, t, evaluate=False)\n", + " C[2] = getTerms(coeff_cos_t, 1)\n", + " C[3] = getTerms(coeff_cos_t, t)\n", + " C[4] = getTerms(coeff_cos_t, t*t)\n", + "\n", + " # group the term in the form a + b * t + c * t^2 \n", + " coeff_sin_t = sp.collect(coeff_sin, t, evaluate=False)\n", + " C[5] = getTerms(coeff_sin_t, 1)\n", + " C[6] = getTerms(coeff_sin_t, t)\n", + " C[7] = getTerms(coeff_sin_t, t*t)\n", + "\n", + " for c in range(8):\n", + " kc = sp.collect(C[c], (px0, py0, pz0, px1, py1, pz1), evaluate=False)\n", + " K[0] = getTerms(kc, 1)\n", + " K[1] = getTerms(kc, px0)\n", + " K[2] = getTerms(kc, py0)\n", + " K[3] = getTerms(kc, pz0)\n", + " K[4] = getTerms(kc, px1)\n", + " K[5] = getTerms(kc, py1)\n", + " K[6] = getTerms(kc, pz1)\n", + "\n", + " for k in range(7):\n", + " K[k] = sp.expand(K[k])\n", + " K[k] = K[k].subs(qw0*qw0, 1-qx0*qx0-qy0*qy0-qz0*qz0) # clean up substitutions\n", + " K[k] = K[k].subs(qw1*qw1, 1-qx1*qx1-qy1*qy1-qz1*qz1) # clean up substitutions\n", + " K[k] = sp.simplify(K[k])\n", + " A[8*7*dim + c*7 + k] = K[k]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The coefficents look all something like the following and are all independent of $t$ and $p_0,p_1$" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle - 2 qw_{0} qx_{0} s_{011} + qw_{0} qx_{0} s_{111} + 2 qw_{0} qx_{1} s_{011} \\theta + 2 qw_{0} qy_{0} s_{001} - qw_{0} qy_{0} s_{101} - 2 qw_{0} qy_{1} s_{001} \\theta + 2 qw_{1} qx_{0} s_{011} \\theta + 2 qw_{1} qx_{1} s_{011} - qw_{1} qx_{1} s_{111} - 2 qw_{1} qy_{0} s_{001} \\theta - 2 qw_{1} qy_{1} s_{001} + qw_{1} qy_{1} s_{101} - 2 qx_{0} qz_{0} s_{001} + qx_{0} qz_{0} s_{101} + 2 qx_{0} qz_{1} s_{001} \\theta + 2 qx_{1} qz_{0} s_{001} \\theta + 2 qx_{1} qz_{1} s_{001} - qx_{1} qz_{1} s_{101} - 2 qy_{0} qz_{0} s_{011} + qy_{0} qz_{0} s_{111} + 2 qy_{0} qz_{1} s_{011} \\theta + 2 qy_{1} qz_{0} s_{011} \\theta + 2 qy_{1} qz_{1} s_{011} - qy_{1} qz_{1} s_{111}$" + ], + "text/plain": [ + "-2*qw0*qx0*s011 + qw0*qx0*s111 + 2*qw0*qx1*s011*theta + 2*qw0*qy0*s001 - qw0*qy0*s101 - 2*qw0*qy1*s001*theta + 2*qw1*qx0*s011*theta + 2*qw1*qx1*s011 - qw1*qx1*s111 - 2*qw1*qy0*s001*theta - 2*qw1*qy1*s001 + qw1*qy1*s101 - 2*qx0*qz0*s001 + qx0*qz0*s101 + 2*qx0*qz1*s001*theta + 2*qx1*qz0*s001*theta + 2*qx1*qz1*s001 - qx1*qz1*s101 - 2*qy0*qz0*s011 + qy0*qz0*s111 + 2*qy0*qz1*s011*theta + 2*qy1*qz0*s011*theta + 2*qy1*qz1*s011 - qy1*qz1*s111" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "A[128]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the following we will generate a C function that takes the input parameters and computes all coefficients $k_j$. It will generate two files (in the directory of this notebook) ````motion_derivative_coefficents.h```` and ````motion_derivative_coefficients.c```` which contains only the definition and declaration of the function computing the coefficients $k_j$. The signature of the function will be\n", + "\n", + "````void motion_derivative_coefficients(float* p, float* coeff);````\n", + "\n", + "where ````p```` are all 33 parameters (see the list at the creation of````param_map```` below) and ````coeffs```` will be the 168 coefficients computed inside the function" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "from sympy.utilities.codegen import codegen, default_datatypes\n", + "from sympy.codegen.ast import real, float32\n", + "from sympy.printing.c import C99CodePrinter\n", + "printer = C99CodePrinter()\n", + "\n", + "# custom code printer that will not generate such nonesene as x^2 -> pow(x, 2)\n", + "class CustomCodePrinter(C99CodePrinter):\n", + " def _print_Pow(self, expr):\n", + " if expr.exp.is_integer and expr.exp > 0 and expr.exp < 5:\n", + " return '*'.join([self._print(expr.base) for i in range(expr.exp)])\n", + " else:\n", + " return super()._print_Pow(expr)\n", + "\n", + "customprinter = CustomCodePrinter()\n", + "customprinter.type_aliases[real] = float32 # cosf instead of cos\n", + "default_datatypes[\"float\"].cname = \"float\" # float instead of double\n", + "params = [\n", + " theta,\n", + " tx0, ty0, tz0,\n", + " tx1, ty1, tz1,\n", + " qw0, qx0, qy0, qz0,\n", + " qw1, qx1, qy1, qz1,\n", + " s000, s001, s002, s003, s011, s012, s013, s022, s023,\n", + " s100, s101, s102, s103, s111, s112, s113, s122, s123]\n", + "R = sp.MatrixSymbol(\"coeff\", A.shape[0], A.shape[1])\n", + "P = sp.MatrixSymbol('p', len(params), 1)\n", + "param_map = dict(zip(params, P))\n", + "B = A.xreplace(param_map)\n", + "codegen(('motion_derivative_coefficients', sp.Eq(R,B)), language='c', printer=customprinter, prefix='motion_derivative_coefficients', to_files=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Testing Code\n", + "\n", + "Next, we will use cython to compile the generated code and create a python wrapper around it. This way we can make sure that everything works as we want." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "%load_ext cython" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting cy_motion_derivative_coefficients.pyxbld\n" + ] + } + ], + "source": [ + "%%writefile cy_motion_derivative_coefficients.pyxbld\n", + "import numpy\n", + "\n", + "# module name specified by `%%cython_pyximport` magic\n", + "# | just `modname + \".pyx\"`\n", + "# | |\n", + "def make_ext(modname, pyxfilename):\n", + " from setuptools.extension import Extension\n", + " return Extension(modname,\n", + " sources=[pyxfilename, 'motion_derivative_coefficients.c'],\n", + " include_dirs=['.', numpy.get_include()])" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "C:\\Users\\freibold.GER\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python310\\site-packages\\Cython\\Compiler\\Main.py:369: FutureWarning: Cython directive 'language_level' not set, using 2 for now (Py2). This will change in a later release! File: C:\\Users\\freibold.GER\\OneDrive - Intel Corporation\\MotionCurve\\cy_motion_derivative_coefficients.pyx\n", + " tree = Parsing.p_module(s, pxd, full_module_name)\n" + ] + } + ], + "source": [ + "%%cython_pyximport cy_motion_derivative_coefficients\n", + "\n", + "import numpy as np # we have to import np here again\n", + "cimport numpy as cnp # cimport gives us access to NumPy's C API\n", + "\n", + "# here we just replicate the function signature from the header\n", + "cdef extern from \"motion_derivative_coefficients.h\":\n", + " void motion_derivative_coefficients(float *params, float *result)\n", + "\n", + "# here is the \"wrapper\"\n", + "def cy_motion_derivative_coefficients(cnp.ndarray[cnp.float32_t, ndim=1] params, size):\n", + " # preallocate our output array\n", + " cdef cnp.ndarray[cnp.float32_t, ndim=1] result = np.empty(size, dtype='float32')\n", + " # now call the C function\n", + " motion_derivative_coefficients( params.data, result.data)\n", + " # return the result\n", + " return result" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We define some test data to verify the bounds we compute. Below, we define a triangle with vertex positions $t_0$ at $t=0$ and positions $t_1$ at $t=1$ that will be transformed by the matrices." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "# enable/disable nonlinear interpolation\n", + "slerpMode = True\n", + "\n", + "Q0 = sp.Quaternion.from_axis_angle((0, 1, 0), 0)\n", + "Q1 = sp.Quaternion.from_axis_angle((0, 0, 1), math.pi) #pi\n", + "Q0 = Q0.normalize() \n", + "Q1 = Q1.normalize()\n", + "\n", + "cosTheta = Q0.a*Q1.a + Q0.b*Q1.b + Q0.c*Q1.c + Q0.d*Q1.d\n", + "Qperp = Q1\n", + "if (cosTheta < 0.995):\n", + " Qperp = Q1-Q0*cosTheta\n", + " Qperp = Qperp.normalize()\n", + "S0 = np.array([[ 1.0, 0.0, 0.0, 0.0],\n", + " [ 0, 1.0, 0.0, 0.0],\n", + " [ 0, 0, 1.0, 0.0],\n", + " [ 0, 0, 0, 1]])\n", + "S1 = np.array([[ 2.0, -0.5, 1.0, 0.1],\n", + " [ 0, 2.0, -0.5, -0.5],\n", + " [ 0, 0, 2.0, 0.3],\n", + " [ 0, 0, 0, 1]])\n", + "D0 = np.array([[1, 0, 0, 0.1],\n", + " [0, 1, 0, 0.2],\n", + " [0, 0, 1, 0.3],\n", + " [0, 0, 0, 1 ]])\n", + "D1 = np.array([[1, 0, 0, -0.1],\n", + " [0, 1, 0, -0.2],\n", + " [0, 0, 1, -0.3],\n", + " [0, 0, 0, 1 ]])\n", + "\n", + "# for linear motion\n", + "A0 = np.array(D0*to_rotation_matrix(Q0)*S0).astype(float)\n", + "A1 = np.array(D1*to_rotation_matrix(Q1)*S1).astype(float)\n", + "\n", + "t0 = np.array([[-0.1, 0.0, -0.1, 1.0], [ 0.1, 0.0, -0.1, 1.0], [ 0.0, -0.1, 0.1, 1.0]])\n", + "t1 = np.array([[-0.1, 1.0, 1.1, 1.0], [-0.1, 1.0, 0.9, 1.0], [ 0.1, 1.1, 1.0, 1.0]])\n", + "\n", + "\n", + "A0 = np.array([[ 2.0, 0.1, -0.2, 0.5],\n", + " [ 0, 2.0, 0.2, 0.0],\n", + " [ 0, 0, 2.0, -0.5],\n", + " [ 0, 0, 0, 1]])\n", + "A1 = np.array([[ 0, 0.0, 1.0, 0.0],\n", + " [ 0, 1.0, 0.0, 0.0],\n", + " [-1.0, 0, 0.0, 0.0],\n", + " [ 0, 0, 0, 1]]) \n", + "\n", + "t0 = np.array([[-0.1, 0.0, -0.1, 1.0], [ 0.1, 0.0, -0.1, 1.0], [ 0.0, -0.1, 0.1, 1.0]])\n", + "t1 = np.array([[-0.1, 2.0, 2.2, 1.0], [-0.2, 2.0, 1.8, 1.0], [ 0.2, 2.2, 2.0, 1.0]])\n", + "\n", + "A0 = np.array([[ 1.0, 0.0, 0.0, 0.0],\n", + " [ 0, 1.0, 0.0, 0.0],\n", + " [ 0, 0, 1.0, 0.0],\n", + " [ 0, 0, 0, 1]])\n", + "A1 = np.array([[ 0, 1.0, 0.0, 0.0],\n", + " [-1.0, 0.0, 0.0, 0.0],\n", + " [ 0.0, 0, 1.0, 0.0],\n", + " [ 0, 0, 0, 1]])\n", + "\n", + "t0 = np.array([[-0.1, 0.0, -0.1, 1.0], [ 0.1, 0.0, -0.1, 1.0], [ 0.0, 0.0, 0.1, 1.0]])\n", + "t1 = np.array([[-0.1, 2.0, -0.1, 1.0], [ 0.1, 2.0, -0.1, 1.0], [ 0.0, 2.0, 0.1, 1.0]])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We use our generated code to compute the coefficients for this test input:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "params = np.array([\n", + " math.acos(cosTheta), \n", + " D0[0][3], D0[1][3], D0[2][3],\n", + " D1[0][3], D1[1][3], D1[2][3],\n", + " Q0.a, Q0.b, Q0.c, Q0.d,\n", + " Qperp.a, Qperp.b, Qperp.c, Qperp.d,\n", + " S0[0][0], S0[0][1], S0[0][2], S0[0][3],\n", + " S0[1][1], S0[1][2], S0[1][3],\n", + " S0[2][2], S0[2][3],\n", + " S1[0][0], S1[0][1], S1[0][2], S1[0][3],\n", + " S1[1][1], S1[1][2], S1[1][3],\n", + " S1[2][2], S1[2][3]],\n", + " dtype='float32')\n", + "coeffs = cy_motion_derivative_coefficients(params, 3*8*7)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we define a function to evaluate a dimension of the motion derivative using the coefficents and vertex positions $p_0$ and $p_1$. " + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-3.1493549551856974\n", + "-0.5761509417289041\n", + "-0.40000001192092893\n", + "root (dim = 0): 0.6148657269556785\n", + "root (dim = 1): 0.20693675142228748\n" + ] + } + ], + "source": [ + "def evalK(A, dim, c, p0, p1):\n", + " res = 0\n", + " p = [1, p0[0], p0[1], p0[2], p1[0], p1[1], p1[2]]\n", + " for k in range(7):\n", + " res += A[8*7*dim + c*7 + k] * p[k]\n", + " return res\n", + "\n", + "def motion_derivative(t, A, theta, dim, p0, p1):\n", + " c0 = evalK(A, dim, 0, p0, p1)\n", + " c1 = evalK(A, dim, 1, p0, p1)\n", + " c2 = evalK(A, dim, 2, p0, p1)\n", + " c3 = evalK(A, dim, 3, p0, p1)\n", + " c4 = evalK(A, dim, 4, p0, p1)\n", + " c5 = evalK(A, dim, 5, p0, p1)\n", + " c6 = evalK(A, dim, 6, p0, p1)\n", + " c7 = evalK(A, dim, 7, p0, p1)\n", + " return c0 + c1 * t + (c2 + c3 * t + c4 * t**2) * np.cos(2*t*theta) + (c5 + c6 * t + c7 * t**2) * np.sin(2*t*theta)\n", + "\n", + "def motion_derivative_bbox(t, A, theta, dim, p0, p1, b0, b1):\n", + " return motion_derivative(t, A, theta, dim, p0, p1) - (b1 - b0)\n", + "\n", + "for i in range(3):\n", + " print(motion_derivative(0.25, coeffs, math.acos(cosTheta), i, t0[0], t1[0]))\n", + " \n", + "from scipy import optimize\n", + "num_splits = 10\n", + "for dim in range(3):\n", + " for i in range(num_splits): \n", + " tt0 = (i+0.0)/10.0\n", + " tt1 = (i+1.0)/10.0\n", + " tts = (i+0.5)/10.0\n", + " ttt = optimize.fsolve(motion_derivative, tts, \n", + " args=(coeffs, math.acos(cosTheta), dim, t0[0], t1[0]), full_output=True)\n", + " if ttt[2] == 1:\n", + " for t in ttt[0]:\n", + " if (t < tt1 and t >= tt0):\n", + " print(\"root (dim = {}): {}\".format(dim, t))" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# a simple bbox class for more readable code (see bbox.py)\n", + "from bbox import *\n", + "\n", + "def lerpBBox(bbox0, bbox1, t):\n", + " bboxt = BBox(lerp(bbox0.bbmin, bbox1.bbmin, t), lerp(bbox0.bbmax, bbox1.bbmax, t))\n", + " return bboxt\n", + "\n", + "# spherical interpolation of two (sympy) quaternions q0 and q1 for time t\n", + "def slerp(q0, q1, t):\n", + " cosTheta = q0.a * q1.a + q0.b * q1.b + q0.c * q1.c + q0.d * q1.d\n", + " if cosTheta > 0.9995:\n", + " q = (1-t) * q0 + t * q1\n", + " return q.normalize()\n", + " theta = math.acos(cosTheta)\n", + " return 1.0/math.sin(theta) * (q0 * math.sin((1-t)*theta) + q1 * math.sin(t*theta))\n", + "\n", + "# this evaluates the position of a transformed point with startpoint p0 and endpoint p1\n", + "# using the given DRS transformation matrices for a time point t\n", + "def motionSlerp(S0, S1, Q0, Q1, D0, D1, p0, p1, t):\n", + " D = lerp(D0, D1, t)\n", + " S = lerp(S0, S1, t)\n", + " Q = slerp(Q0, Q1, t)\n", + " R = to_rotation_matrix(Q)\n", + " p = lerp(p0, p1, t)\n", + " r = D*R*S*sp.Matrix([p[0], p[1], p[2], p[3]])\n", + " return [float(r[0]), float(r[1]), float(r[2]), float(r[3])]\n", + "\n", + "def motionLerp(A0, A1, p0, p1, t):\n", + " A = lerp(A0, A1, t)\n", + " p = lerp(p0, p1, t)\n", + " r = A.dot(p)\n", + " return [float(r[0]), float(r[1]), float(r[2]), float(r[3])]\n", + "\n", + "def motion(p0, p1, t):\n", + " if slerpMode:\n", + " return motionSlerp(S0, S1, Q0, Q1, D0, D1, p0, p1, t)\n", + " else:\n", + " return motionLerp(A0, A1, p0, p1, t)\n", + "\n", + "times = np.linspace(0.0, 1.0, 40)\n", + "motion_vertices = np.array([[motion(t0[i], t1[i], t) for t in times] for i in range(3)]) \n", + "\n", + "# triangle vertices and faces\n", + "tv = np.array([motion(t0[0], t1[0], 0), motion(t0[1], t1[1], 0), motion(t0[2], t1[2], 0),\n", + " motion(t0[0], t1[0], 1), motion(t0[1], t1[1], 1), motion(t0[2], t1[2], 1)])\n", + "tf = [[tv[0][:3], tv[2][:3], tv[1][:3]], [tv[3][:3], tv[5][:3], tv[4][:3]]]\n", + "\n", + "#plotting\n", + "from mpl_toolkits.mplot3d import Axes3D\n", + "from mpl_toolkits.mplot3d.art3d import Poly3DCollection, Line3DCollection\n", + "import matplotlib.pyplot as plt\n", + "\n", + "fig = plt.figure(figsize=(9, 7))\n", + "ax = fig.add_subplot(1, 1, 1, projection='3d')\n", + "for i in range(3):\n", + " ax.plot(motion_vertices[i][:,0], motion_vertices[i][:,1], motion_vertices[i][:,2])\n", + "ax.scatter3D(tv[:,0], tv[:,1], tv[:,2])\n", + "ax.add_collection3d(Poly3DCollection(tf, linewidth=1, edgecolors='black', facecolor=(0.0, 0.0, 1.0, 0.5)))\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "from scipy import optimize\n", + "def getRoots(dim, p0, p1, bbox0, bbox1):\n", + " # fsolve returns only one root. so we will call it for many starting points x0=[0, 0.1, ..., 0.9, 1.0]\n", + " # and hope that we find all roots\n", + " result = np.array([])\n", + " num_splits = 10\n", + " for i in range(num_splits): \n", + " t0 = (i+0.0)/10.0\n", + " t1 = (i+1.0)/10.0\n", + " ts = (i+0.5)/10.0\n", + " tt = optimize.fsolve(motion_derivative_bbox, ts, \n", + " args=(coeffs, math.acos(cosTheta), dim, p0, p1, bbox0.bbmin[dim], bbox1.bbmin[dim]))\n", + " for t in tt:\n", + " if (t < t1 and t >= t0):\n", + " result = np.append(result, t)\n", + " tt = optimize.fsolve(motion_derivative_bbox, ts, \n", + " args=(coeffs, math.acos(cosTheta), dim, p0, p1, bbox0.bbmax[dim], bbox1.bbmax[dim]))\n", + " for t in tt:\n", + " if (t < t1 and t >= t0):\n", + " result = np.append(result, t)\n", + " return result\n", + "\n", + "def extendSlerp(p0, p1, bbox0, bbox1):\n", + " for dim in range(3): \n", + " roots = getRoots(dim, p0, p1, bbox0, bbox1)\n", + " for t in roots:\n", + " pt = motion(p0, p1, t)\n", + " bboxt = lerpBBox(bbox0, bbox1, t)\n", + " bbox0.bbmax[dim] = bbox0.bbmax[dim] + max(0.0, pt[dim] - bboxt.bbmax[dim])\n", + " bbox1.bbmax[dim] = bbox1.bbmax[dim] + max(0.0, pt[dim] - bboxt.bbmax[dim])\n", + " bbox0.bbmin[dim] = bbox0.bbmin[dim] + min(0.0, pt[dim] - bboxt.bbmin[dim])\n", + " bbox1.bbmin[dim] = bbox1.bbmin[dim] + min(0.0, pt[dim] - bboxt.bbmin[dim])\n", + "\n", + "def extendLerp(p0, p1, bbox0, bbox1):\n", + " denom = 2.0 * (A0-A1).dot(p0-p1)\n", + " nom_min = 2.0 * A0.dot(p0) - A0.dot(p1) - A1.dot(p0) + (bbox1.bbmin - bbox0.bbmin)\n", + " nom_max = 2.0 * A0.dot(p0) - A0.dot(p1) - A1.dot(p0) + (bbox1.bbmax - bbox0.bbmax)\n", + " print('bbox0', bbox0)\n", + " print('bbox1', bbox1)\n", + " print('p0', p0)\n", + " print('p1', p1)\n", + " print('nom_lower', nom_min[0], nom_min[1], nom_min[2])\n", + " print('nom_upper', nom_max[0], nom_max[1], nom_max[2])\n", + " for dim in range(3):\n", + " if (abs(denom[dim]) > 0):\n", + " t = nom_min[dim] / denom[dim]\n", + " if (t <= 1 and t >= 0):\n", + " pt = motionLerp(A0, A1, p0, p1, t)\n", + " bboxt = lerpBBox(bbox0, bbox1, t)\n", + " print('extend lower', t, ', dim', dim, ', delta', min(0.0, pt[dim] - bboxt.bbmin[dim]))\n", + " bbox0.bbmin[dim] = bbox0.bbmin[dim] + min(0.0, pt[dim] - bboxt.bbmin[dim])\n", + " bbox1.bbmin[dim] = bbox1.bbmin[dim] + min(0.0, pt[dim] - bboxt.bbmin[dim])\n", + " t = nom_max[dim] / denom[dim]\n", + " if (t <= 1 and t >= 0):\n", + " pt = motionLerp(A0, A1, p0, p1, t)\n", + " bboxt = lerpBBox(bbox0, bbox1, t)\n", + " print('extend upper', t, ', dim', dim, ', delta', max(0.0, pt[dim] - bboxt.bbmax[dim]))\n", + " bbox0.bbmax[dim] = bbox0.bbmax[dim] + max(0.0, pt[dim] - bboxt.bbmax[dim])\n", + " bbox1.bbmax[dim] = bbox1.bbmax[dim] + max(0.0, pt[dim] - bboxt.bbmax[dim])\n", + " \n", + "def extend(p0, p1, bbox0, bbox1):\n", + " if slerpMode:\n", + " extendSlerp(p0, p1, bbox0, bbox1)\n", + " else:\n", + " extendLerp(p0, p1, bbox0, bbox1)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "C:\\Users\\freibold.GER\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python310\\site-packages\\scipy\\optimize\\_minpack_py.py:178: RuntimeWarning: The iteration is not making good progress, as measured by the \n", + " improvement from the last ten iterations.\n", + " warnings.warn(msg, RuntimeWarning)\n", + "C:\\Users\\freibold.GER\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python310\\site-packages\\scipy\\optimize\\_minpack_py.py:178: RuntimeWarning: The iteration is not making good progress, as measured by the \n", + " improvement from the last five Jacobian evaluations.\n", + " warnings.warn(msg, RuntimeWarning)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "bbox0 BBox\n", + " [-1.90447086 0.16280211 0.2 1. ]\n", + " [0.2 1.95023892 0.4 1. ]\n", + "bbox1 BBox\n", + " [-1.40447086 -3.78719789 -0.2 1. ]\n", + " [ 1.1 -1.89976108 0.2 1. ]\n" + ] + } + ], + "source": [ + "def xfmBounds(bounds, time):\n", + " corners = bounds.getCorners()\n", + " bbox = BBox()\n", + " for c in corners:\n", + " bbox.extend(motion(c, c, time))\n", + " return bbox\n", + "\n", + "obbox0 = BBox()\n", + "obbox1 = BBox()\n", + "for i in range(3):\n", + " obbox0.extend(t0[i])\n", + " obbox1.extend(t1[i])\n", + " \n", + "bbox0 = xfmBounds(obbox0, 0)\n", + "bbox1 = xfmBounds(obbox1, 1)\n", + "\n", + "cobbox0 = obbox0.getCorners()\n", + "cobbox1 = obbox1.getCorners()\n", + "for c in range(8):\n", + " c0 = cobbox0[c]\n", + " c1 = cobbox1[c]\n", + " extend(c0, c1, bbox0, bbox1)\n", + " \n", + "print('bbox0', bbox0)\n", + "print('bbox1', bbox1)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "labels = ['x coordinate', 'y coordinate', 'z coordinate']\n", + "fig = plt.figure(figsize=(9, 4))\n", + "for i in range(0,3):\n", + " ax = fig.add_subplot(1, 3, i+1)\n", + " ax.set_xlabel(\"time t\")\n", + " ax.set_title(labels[i])\n", + " ax.set_yticks([])\n", + " for j in range(3):\n", + " ax.plot(times, motion_vertices[j][:,i])\n", + " ax.plot(times, [lerp(bbox0.bbmin[i], bbox1.bbmin[i], x) for x in times], label=\"bbmin(t)\")\n", + " ax.plot(times, [lerp(bbox0.bbmax[i], bbox1.bbmax[i], x) for x in times], label=\"bbmax(t)\")\n", + " if i == 1:\n", + " ax.legend(loc='center', bbox_to_anchor=(0.5, -0.5))\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "#plotting\n", + "from mpl_toolkits.mplot3d import Axes3D\n", + "from mpl_toolkits.mplot3d.art3d import Poly3DCollection, Line3DCollection\n", + "import matplotlib.pyplot as plt\n", + "\n", + "fig = plt.figure(figsize=(9, 7))\n", + "ax = fig.add_subplot(1, 1, 1, projection='3d')\n", + "\n", + "# draw triangle and vertex motions\n", + "for i in range(3):\n", + " ax.plot(motion_vertices[i][:,0], motion_vertices[i][:,1], motion_vertices[i][:,2])\n", + "ax.scatter3D(tv[:,0], tv[:,1], tv[:,2])\n", + "ax.add_collection3d(Poly3DCollection(tf, linewidth=1, edgecolors='black', facecolor=(0.1, 0.1, 0.8, 0.5)))\n", + "\n", + "# draw extended linear bounding box\n", + "cbbox0 = bbox0.getCorners()\n", + "cbbox1 = bbox1.getCorners()\n", + "fbbox0 = getFacesForBBoxCorners(cbbox0)\n", + "fbbox1 = getFacesForBBoxCorners(cbbox1)\n", + "ax.scatter3D(cbbox0[:,0], cbbox0[:,1], cbbox0[:,2], color='black')\n", + "ax.scatter3D(cbbox1[:,0], cbbox1[:,1], cbbox1[:,2], color='black')\n", + "ax.add_collection3d(Poly3DCollection(fbbox0, linewidth=1, edgecolors='black', facecolor=(0.1, 0.1, 0.8, 0.1)))\n", + "ax.add_collection3d(Poly3DCollection(fbbox1, linewidth=1, edgecolors='black', facecolor=(0.1, 0.1, 0.8, 0.1)))\n", + "for i in range(8):\n", + " lbound = np.array([lerp(cbbox0[i], cbbox1[i], t) for t in times]) \n", + " ax.plot(lbound[:,0], lbound[:,1], lbound[:,2], color='gray')\n", + "\n", + "# draw start/original bounding boxes\n", + "bbox0w = BBox()\n", + "bbox1w = BBox()\n", + "for i in range(3):\n", + " bbox0w.extend(motion(t0[i], t1[i], 0))\n", + " bbox1w.extend(motion(t0[i], t1[i], 1))\n", + "fbbox0 = getFacesForBBoxCorners(bbox0w.getCorners())\n", + "fbbox1 = getFacesForBBoxCorners(bbox1w.getCorners())\n", + "ax.add_collection3d(Poly3DCollection(fbbox0, linewidth=1, edgecolors='black', facecolor=(0.8, 0.1, 0.1, 0.1)))\n", + "ax.add_collection3d(Poly3DCollection(fbbox1, linewidth=1, edgecolors='black', facecolor=(0.8, 0.1, 0.1, 0.1)))\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "everying OK!\n" + ] + } + ], + "source": [ + "from itertools import product\n", + "\n", + "OK = True\n", + "for i, T in product(range(3), np.arange(0, 1, 0.01)):\n", + " P = motion(t0[i], t1[i], T)\n", + " bboxt = lerpBBox(bbox0, bbox1, T)\n", + " if not bboxt.contains(P,1e-4):\n", + " OK = False\n", + "if OK:\n", + " print('everying OK!')\n", + "else:\n", + " print('error!')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "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.10.10" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/scripts/qmbb/MotionCurve.ipynb b/scripts/qmbb/MotionCurve.ipynb new file mode 100644 index 0000000000..351d7f3628 --- /dev/null +++ b/scripts/qmbb/MotionCurve.ipynb @@ -0,0 +1,1285 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "C:\\Users\\freibold.GER\\AppData\\Local\\Temp\\ipykernel_15772\\1967069686.py:10: DeprecationWarning: Importing display from IPython.core.display is deprecated since IPython 7.14, please import from IPython display\n", + " from IPython.core.display import display, HTML\n" + ] + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%matplotlib notebook\n", + "import sympy as sp\n", + "import numpy as np\n", + "import math\n", + "\n", + "from mpl_toolkits.mplot3d import Axes3D\n", + "from mpl_toolkits.mplot3d.art3d import Poly3DCollection, Line3DCollection\n", + "import matplotlib.pyplot as plt\n", + "\n", + "from IPython.core.display import display, HTML\n", + "display(HTML(\"\"))" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "# definitions\n", + "\n", + "class BBox():\n", + " def __init__(self, bbmin=None, bbmax=None):\n", + " if bbmin is None:\n", + " self.bbmin = np.array([math.inf, math.inf, math.inf, math.inf])\n", + " else:\n", + " self.bbmin = bbmin\n", + " if bbmax is None:\n", + " self.bbmax = np.array([-math.inf, -math.inf, -math.inf, -math.inf])\n", + " else:\n", + " self.bbmax = bbmax\n", + " \n", + " def extend(self, other):\n", + " if isinstance(other, BBox):\n", + " self.bbmin = np.minimum(self.bbmin, other.bbmin)\n", + " self.bbmax = np.maximum(self.bbmax, other.bbmax)\n", + " else:\n", + " self.bbmin = np.minimum(self.bbmin, other)\n", + " self.bbmax = np.maximum(self.bbmax, other)\n", + " \n", + " def extendAll(self, *others):\n", + " for other in others:\n", + " self.extend(other)\n", + " \n", + " def getCorners(self):\n", + " corners = np.array([[self.bbmin[0], self.bbmin[1], self.bbmin[2], 1],\n", + " [self.bbmin[0], self.bbmin[1], self.bbmax[2], 1],\n", + " [self.bbmin[0], self.bbmax[1], self.bbmin[2], 1],\n", + " [self.bbmin[0], self.bbmax[1], self.bbmax[2], 1],\n", + " [self.bbmax[0], self.bbmin[1], self.bbmin[2], 1],\n", + " [self.bbmax[0], self.bbmin[1], self.bbmax[2], 1],\n", + " [self.bbmax[0], self.bbmax[1], self.bbmin[2], 1],\n", + " [self.bbmax[0], self.bbmax[1], self.bbmax[2], 1]])\n", + " return corners\n", + " \n", + " def __str__(self):\n", + " return \"BBox\\n %s\\n %s\" % (self.bbmin, self.bbmax)\n", + " \n", + "def getFacesForBBoxCorners(c):\n", + " faces = [[c[0][:3], c[1][:3], c[3][:3], c[2][:3]],\n", + " [c[4][:3], c[5][:3], c[7][:3], c[6][:3]],\n", + " [c[0][:3], c[2][:3], c[6][:3], c[4][:3]],\n", + " [c[1][:3], c[3][:3], c[7][:3], c[5][:3]],\n", + " [c[0][:3], c[1][:3], c[5][:3], c[4][:3]],\n", + " [c[2][:3], c[3][:3], c[7][:3], c[6][:3]]]\n", + " return faces\n", + "\n", + "def lerp(v0,v1,t):\n", + " return v0*(1-t)+v1*t\n", + "\n", + "def motion(A0, A1, p0, p1, t):\n", + " return lerp(A0, A1, t).dot(lerp(p0, p1, t))\n", + "\n", + "def getBounds(S0, S1, p0, p1):\n", + " bbox = BBox(np.minimum(motion(S0, S1, p0, p1, 0), motion(S0, S1, p0, p1, 1)),\n", + " np.maximum(motion(S0, S1, p0, p1, 0), motion(S0, S1, p0, p1, 1)))\n", + " \n", + " denom = 2.0 * (S0-S1).dot(p0-p1)\n", + " nom = 2.0 * S0.dot(p0) - S0.dot(p1) - S1.dot(p0)\n", + " \n", + " #denom = [(S0[0][0]*p0[0] - S0[0][0]*p1[0] + S0[0][1]*p0[1] - S0[0][1]*p1[1] + S0[0][2]*p0[2] - S0[0][2]*p1[2] - S1[0][0]*p0[0] + S1[0][0]*p1[0] - S1[0][1]*p0[1] + S1[0][1]*p1[1] - S1[0][2]*p0[2] + S1[0][2]*p1[2]),\n", + " # (S0[1][0]*p0[0] - S0[1][0]*p1[0] + S0[1][1]*p0[1] - S0[1][1]*p1[1] + S0[1][2]*p0[2] - S0[1][2]*p1[2] - S1[1][0]*p0[0] + S1[1][0]*p1[0] - S1[1][1]*p0[1] + S1[1][1]*p1[1] - S1[1][2]*p0[2] + S1[1][2]*p1[2]),\n", + " # (S0[2][0]*p0[0] - S0[2][0]*p1[0] + S0[2][1]*p0[1] - S0[2][1]*p1[1] + S0[2][2]*p0[2] - S0[2][2]*p1[2] - S1[2][0]*p0[0] + S1[2][0]*p1[0] - S1[2][1]*p0[1] + S1[2][1]*p1[1] - S1[2][2]*p0[2] + S1[2][2]*p1[2])]\n", + " #nom = [(S0[0][0]*p0[0] - 0.5*S0[0][0]*p1[0] + S0[0][1]*p0[1] - 0.5*S0[0][1]*p1[1] + S0[0][2]*p0[2] - 0.5*S0[0][2]*p1[2] + 0.5*S0[0][3] - 0.5*S1[0][0]*p0[0] - 0.5*S1[0][1]*p0[1] - 0.5*S1[0][2]*p0[2] - 0.5*S1[0][3]),\n", + " # (S0[1][0]*p0[0] - 0.5*S0[1][0]*p1[0] + S0[1][1]*p0[1] - 0.5*S0[1][1]*p1[1] + S0[1][2]*p0[2] - 0.5*S0[1][2]*p1[2] + 0.5*S0[1][3] - 0.5*S1[1][0]*p0[0] - 0.5*S1[1][1]*p0[1] - 0.5*S1[1][2]*p0[2] - 0.5*S1[1][3]),\n", + " # (S0[2][0]*p0[0] - 0.5*S0[2][0]*p1[0] + S0[2][1]*p0[1] - 0.5*S0[2][1]*p1[1] + S0[2][2]*p0[2] - 0.5*S0[2][2]*p1[2] + 0.5*S0[2][3] - 0.5*S1[2][0]*p0[0] - 0.5*S1[2][1]*p0[1] - 0.5*S1[2][2]*p0[2] - 0.5*S1[2][3])]\n", + " \n", + " #for i in range(3):\n", + " # if (abs(denom[i]) > 0):\n", + " # t = nom[i] / denom[i]\n", + " # if (t <= 1 and t >= 0):\n", + " # bbox.extend(motion(S0, S1, p0, p1, t))\n", + " return bbox" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# computations\n", + "\n", + "A0 = np.array([[ 2.0, 0.1, -0.2, 0.5],\n", + " [ 0.0, 2.0, 0.2, 0.0],\n", + " [ 0.0, 0.0, 2.0, -0.5],\n", + " [ 0.0, 0.0, 0.0, 1.0]])\n", + "A1 = np.array([[ 0.0, 0.0, 1.0, -0.1],\n", + " [ 0.0, 1.0, 0.0, 0.2],\n", + " [-1.0, 0.0, 0.0, 0.1],\n", + " [ 0.0, 0.0, 0.0, 1.0]]) \n", + "t0 = np.array([[-0.1, 0.0, -0.1, 1.0], [ 0.1, 0.0, -0.1, 1.0], [ 0.0, -0.1, 0.1, 1.0]])\n", + "t1 = np.array([[-0.1, 1.0, 1.1, 1.0], [-0.1, 1.0, 0.9, 1.0], [ 0.1, 1.1, 1.0, 1.0]])\n", + "\n", + "# faster bounding box (if the number of vertices is large):\n", + "# bounding the motion bounds of each bounding box corner pair\n", + "bbox_t0 = BBox()\n", + "bbox_t0.extendAll(t0[0], t0[1], t0[2])\n", + "bbox_t1 = BBox()\n", + "bbox_t1.extendAll(t1[0], t1[1], t1[2])\n", + "\n", + "c0 = bbox_t0.getCorners()\n", + "c1 = bbox_t1.getCorners()\n", + "\n", + "bbox_corners = BBox()\n", + "for i in range(8):\n", + " bbox_corners.extend(getBounds(A0, A1, c0[i], c1[i]))\n", + "\n", + "# compute correct bounding box:\n", + "# bounding the motion bounds of each vertex\n", + "bbox_vertices = BBox()\n", + "for i in range(3):\n", + " bbox_vertices.extend(getBounds(A0, A1, t0[i], t1[i]))\n", + " \n", + "times = np.linspace(0, 1, 40)\n", + "motion_corners = np.array([[motion(A0, A1, c0[i], c1[i], t) for t in times] for i in range(8)])\n", + "motion_vertices = np.array([[motion(A0, A1, t0[i], t1[i], t) for t in times] for i in range(3)]) \n", + "\n", + "# some more data for plotting\n", + "c0 = np.array([A0.dot(c0[i]) for i in range(8)])\n", + "c1 = np.array([A1.dot(c1[i]) for i in range(8)])\n", + "\n", + "f0 = getFacesForBBoxCorners(c0)\n", + "f1 = getFacesForBBoxCorners(c1)\n", + "\n", + "# triangle vertices and faces\n", + "tv = np.array([A0.dot(t0[0]), A0.dot(t0[1]), A0.dot(t0[2]), A1.dot(t1[0]), A1.dot(t1[1]), A1.dot(t1[2])])\n", + "tf = [[tv[0][:3], tv[2][:3], tv[1][:3]], [tv[3][:3], tv[5][:3], tv[4][:3]]]" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[ 0.32 -0.02 -0.7 1. ]\n", + " [ 0.72 -0.02 -0.7 1. ]\n", + " [ 0.47 -0.18 -0.3 1. ]\n", + " [ 1. 1.2 0.2 1. ]\n", + " [ 0.8 1.2 0.2 1. ]\n", + " [ 0.9 1.3 0. 1. ]]\n", + "[0.32 0.72 0.47 1. 0.8 0.9 ]\n", + "[-0.02 -0.02 -0.18 1.2 1.2 1.3 ]\n", + "[-0.7 -0.7 -0.3 0.2 0.2 0. ]\n", + "\n", + "\n", + "\n", + "\n" + ] + }, + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "#plotting\n", + "\n", + "print(tv)\n", + "print(tv[:,0])\n", + "print(tv[:,1])\n", + "print(tv[:,2])\n", + "\n", + "print(type(tv))\n", + "print(type(tv[:,0]))\n", + "print(type(tv[:,1]))\n", + "print(type(tv[:,2]))\n", + "\n", + "labels = ['x coordinate', 'y coordinate', 'z coordinate']\n", + "fig = plt.figure(figsize=(15, 3.5))\n", + "ax = fig.add_subplot(1, 4, 1, projection='3d')\n", + "for i in range(3):\n", + " ax.plot(motion_vertices[i][:,0], motion_vertices[i][:,1], motion_vertices[i][:,2])\n", + "ax.scatter3D(tv[:,0], tv[:,1], tv[:,2])\n", + "ax.add_collection3d(Poly3DCollection(tf, linewidth=1, edgecolors='black', alpha=0.5))\n", + "ax.scatter3D(c0[:,0], c0[:,1], c0[:,2], color='black')\n", + "ax.scatter3D(c1[:,0], c1[:,1], c1[:,2], color='black')\n", + "ax.add_collection3d(Poly3DCollection(f0, linewidth=1, edgecolors='black', alpha=0.1))\n", + "ax.add_collection3d(Poly3DCollection(f1, linewidth=1, edgecolors='black', alpha=0.1))\n", + "for i in range(8):\n", + " ax.plot(motion_corners[i][:,0], motion_corners[i][:,1], motion_corners[i][:,2], color='gray')\n", + "verts = bbox_corners.getCorners()\n", + "faces = getFacesForBBoxCorners(verts)\n", + "ax.scatter3D(verts[:,0], verts[:,1], verts[:,2], color='black')\n", + "ax.add_collection3d(Poly3DCollection(faces, linewidth=1, edgecolors='black', alpha=0.1))\n", + "\n", + "for i in range(0,3):\n", + " ax = fig.add_subplot(1, 4, i+2)\n", + " ax.set_title(labels[i])\n", + " ax.set_xlabel(\"time t\")\n", + " for j in range(3):\n", + " ax.plot(times, motion_vertices[j][:,i])\n", + " ax.plot(times, [bbox_corners.bbmax[i] for x in times], label=\"max bbox\")\n", + " ax.plot(times, [bbox_vertices.bbmax[i] for x in times], label=\"max vert\")\n", + " ax.plot(times, [bbox_corners.bbmin[i] for x in times], label=\"min bbox\")\n", + " ax.plot(times, [bbox_vertices.bbmin[i] for x in times], label=\"min vert\")\n", + " if i == 0:\n", + " ax.legend(loc='center', bbox_to_anchor=(1, -0.15))\n", + "plt.tight_layout()\n", + "plt.savefig('plot.png')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "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.10.10" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/scripts/qmbb/MotionCurveCoefficientsSimple.ipynb b/scripts/qmbb/MotionCurveCoefficientsSimple.ipynb new file mode 100644 index 0000000000..75ed239ed6 --- /dev/null +++ b/scripts/qmbb/MotionCurveCoefficientsSimple.ipynb @@ -0,0 +1,183 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "import sympy as sp\n", + "\n", + "t, theta = sp.symbols(\"t, theta\", real = True)\n", + "\n", + "px, py, pz = sp.symbols(\"px, py, pz\", real=True) \n", + "\n", + "tx0, ty0, tz0 = sp.symbols(\"tx0, ty0, tz0\", real=True) \n", + "tx1, ty1, tz1 = sp.symbols(\"tx1, ty1, tz1\", real=True) \n", + "\n", + "qx0, qy0, qz0, qw0 = sp.symbols(\"qx0, qy0, qz0, qw0\", real=True) \n", + "qx1, qy1, qz1, qw1 = sp.symbols(\"qx1, qy1, qz1, qw1\", real=True) \n", + "\n", + "s000, s001, s002, s010, s011, s012, s020, s021, s022 = sp.symbols(\"s000, s001, s002, s010, s011, s012, s020, s021, s022\", real=True)\n", + "s100, s101, s102, s110, s111, s112, s120, s121, s122 = sp.symbols(\"s100, s101, s102, s110, s111, s112, s120, s121, s122\", real=True)\n", + "\n", + "def lerp(v0,v1,t):\n", + " return v0*(1-t)+v1*t\n", + "\n", + "# assuming that q1 is qperp = normalize(q1-q2*cosTheta)\n", + "def slerp(q0, q1, t):\n", + " return q0 * sp.cos(t*theta) + q1 * sp.sin(t*theta)\n", + "\n", + "def to_rotation_matrix(q):\n", + " return sp.Matrix([[q.a*q.a + q.b*q.b - q.c*q.c - q.d*q.d, 2*(q.b*q.c - q.a*q.d), 2*(q.b*q.d + q.a*q.c), 0], \n", + " [2*(q.b*q.c + q.a*q.d), q.a*q.a - q.b*q.b + q.c*q.c - q.d*q.d, 2*(q.c*q.d - q.a*q.b), 0], \n", + " [2*(q.b*q.d - q.a*q.c), 2*(q.c*q.d + q.a*q.b), q.a*q.a - q.b*q.b - q.c*q.c + q.d*q.d, 0],\n", + " [0, 0, 0, 1]])\n", + "\n", + "q0 = sp.Quaternion(qw0, qx0, qy0, qz0)\n", + "q1 = sp.Quaternion(qw1, qx1, qy1, qz1)\n", + "qt = slerp(q0, q1, t)\n", + "S0 = sp.Matrix([[s000, s001, s002, 0],\n", + " [s010, s011, s012, 0],\n", + " [s020, s021, s022, 0],\n", + " [ 0, 0, 0, 1]])\n", + "S1 = sp.Matrix([[s100, s101, s102, 0],\n", + " [s110, s111, s112, 0],\n", + " [s120, s121, s122, 0],\n", + " [ 0, 0, 0, 1]])\n", + "St = lerp(S0, S1, t)\n", + "D0 = sp.Matrix([[1, 0, 0, tx0],\n", + " [0, 1, 0, ty0],\n", + " [0, 0, 1, tz0],\n", + " [0, 0, 0, 1]])\n", + "D1 = sp.Matrix([[1, 0, 0, tx1],\n", + " [0, 1, 0, ty1],\n", + " [0, 0, 1, tz1],\n", + " [0, 0, 0, 1]])\n", + "Dt = lerp(D0, D1, t)\n", + "p = sp.Matrix([px, py, pz, 1])\n", + "\n", + "\n", + "M = Dt*to_rotation_matrix(qt)*St*p\n", + "m0 = M[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "dm0 = sp.diff(m0, t)\n", + "dm0 = sp.expand(dm0)\n", + "\n", + "dm0 = dm0.subs(sp.sin(t*theta)*sp.sin(t*theta),(1-sp.cos(2*t*theta))/2) # remove sin(t*theta)^2\n", + "dm0 = dm0.subs(sp.cos(t*theta)*sp.cos(t*theta),(1+sp.cos(2*t*theta))/2) # remove cos(t*theta)^2\n", + "dm0 = dm0.subs(sp.sin(t*theta)*sp.cos(t*theta),sp.sin(2*t*theta)/2) # remove sin(t*theta)*cos(t*theta)\n", + "dm0 = sp.expand(dm0)\n", + "\n", + "dm0_cos_sin = sp.collect(dm0, (sp.sin(2*t*theta), sp.cos(2*t*theta)), evaluate=False)\n", + "\n", + "coeff_const = dm0_cos_sin[1]\n", + "coeff_sin = dm0_cos_sin[sp.sin(2*t*theta)]\n", + "coeff_cos = dm0_cos_sin[sp.cos(2*t*theta)]\n", + "\n", + "coeff_sin_t = sp.collect(coeff_sin, t, evaluate=False)\n", + "coeff_cos_t = sp.collect(coeff_cos, t, evaluate=False)\n", + "c0 = coeff_const\n", + "c1 = coeff_cos_t[1]\n", + "c2 = coeff_cos_t[t]\n", + "c3 = coeff_sin_t[1]\n", + "c4 = coeff_sin_t[t]" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle - qw_{0} qy_{0} s_{020} + qw_{0} qy_{0} s_{120} + qw_{0} qz_{0} s_{010} - qw_{0} qz_{0} s_{110} - qw_{1} qy_{1} s_{020} + qw_{1} qy_{1} s_{120} + qw_{1} qz_{1} s_{010} - qw_{1} qz_{1} s_{110} - qx_{0} qy_{0} s_{010} + qx_{0} qy_{0} s_{110} - qx_{0} qz_{0} s_{020} + qx_{0} qz_{0} s_{120} - qx_{1} qy_{1} s_{010} + qx_{1} qy_{1} s_{110} - qx_{1} qz_{1} s_{020} + qx_{1} qz_{1} s_{120} + qy_{0}^{2} s_{000} - qy_{0}^{2} s_{100} + qy_{1}^{2} s_{000} - qy_{1}^{2} s_{100} + qz_{0}^{2} s_{000} - qz_{0}^{2} s_{100} + qz_{1}^{2} s_{000} - qz_{1}^{2} s_{100} - s_{000} + s_{100}$" + ], + "text/plain": [ + "-qw0*qy0*s020 + qw0*qy0*s120 + qw0*qz0*s010 - qw0*qz0*s110 - qw1*qy1*s020 + qw1*qy1*s120 + qw1*qz1*s010 - qw1*qz1*s110 - qx0*qy0*s010 + qx0*qy0*s110 - qx0*qz0*s020 + qx0*qz0*s120 - qx1*qy1*s010 + qx1*qy1*s110 - qx1*qz1*s020 + qx1*qz1*s120 + qy0**2*s000 - qy0**2*s100 + qy1**2*s000 - qy1**2*s100 + qz0**2*s000 - qz0**2*s100 + qz1**2*s000 - qz1**2*s100 - s000 + s100" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "kc0 = sp.collect(c0, (px, py, pz), evaluate=False)\n", + "kc0 = [kc0[1], kc0[px], kc0[py], kc0[pz]]\n", + "for i in range(4):\n", + " kc0[i] = sp.expand(kc0[i])\n", + " kc0[i] = kc0[i].subs(qw0*qw0, 1-qx0*qx0-qy0*qy0-qz0*qz0)\n", + " kc0[i] = kc0[i].subs(qw1*qw1, 1-qx1*qx1-qy1*qy1-qz1*qz1)\n", + " kc0[i] = sp.simplify(kc0[i])\n", + "kc0[1]" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle 2 qw_{0} qw_{1} s_{002} \\theta - qw_{0} qy_{0} s_{022} + qw_{0} qy_{0} s_{122} + 2 qw_{0} qy_{1} s_{022} \\theta + qw_{0} qz_{0} s_{012} - qw_{0} qz_{0} s_{112} - 2 qw_{0} qz_{1} s_{012} \\theta + 2 qw_{1} qy_{0} s_{022} \\theta + qw_{1} qy_{1} s_{022} - qw_{1} qy_{1} s_{122} - 2 qw_{1} qz_{0} s_{012} \\theta - qw_{1} qz_{1} s_{012} + qw_{1} qz_{1} s_{112} + 2 qx_{0} qx_{1} s_{002} \\theta - qx_{0} qy_{0} s_{012} + qx_{0} qy_{0} s_{112} + 2 qx_{0} qy_{1} s_{012} \\theta - qx_{0} qz_{0} s_{022} + qx_{0} qz_{0} s_{122} + 2 qx_{0} qz_{1} s_{022} \\theta + 2 qx_{1} qy_{0} s_{012} \\theta + qx_{1} qy_{1} s_{012} - qx_{1} qy_{1} s_{112} + 2 qx_{1} qz_{0} s_{022} \\theta + qx_{1} qz_{1} s_{022} - qx_{1} qz_{1} s_{122} + qy_{0}^{2} s_{002} - qy_{0}^{2} s_{102} - 2 qy_{0} qy_{1} s_{002} \\theta - qy_{1}^{2} s_{002} + qy_{1}^{2} s_{102} + qz_{0}^{2} s_{002} - qz_{0}^{2} s_{102} - 2 qz_{0} qz_{1} s_{002} \\theta - qz_{1}^{2} s_{002} + qz_{1}^{2} s_{102}$" + ], + "text/plain": [ + "2*qw0*qw1*s002*theta - qw0*qy0*s022 + qw0*qy0*s122 + 2*qw0*qy1*s022*theta + qw0*qz0*s012 - qw0*qz0*s112 - 2*qw0*qz1*s012*theta + 2*qw1*qy0*s022*theta + qw1*qy1*s022 - qw1*qy1*s122 - 2*qw1*qz0*s012*theta - qw1*qz1*s012 + qw1*qz1*s112 + 2*qx0*qx1*s002*theta - qx0*qy0*s012 + qx0*qy0*s112 + 2*qx0*qy1*s012*theta - qx0*qz0*s022 + qx0*qz0*s122 + 2*qx0*qz1*s022*theta + 2*qx1*qy0*s012*theta + qx1*qy1*s012 - qx1*qy1*s112 + 2*qx1*qz0*s022*theta + qx1*qz1*s022 - qx1*qz1*s122 + qy0**2*s002 - qy0**2*s102 - 2*qy0*qy1*s002*theta - qy1**2*s002 + qy1**2*s102 + qz0**2*s002 - qz0**2*s102 - 2*qz0*qz1*s002*theta - qz1**2*s002 + qz1**2*s102" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "kc1 = sp.collect(c1, (px, py, pz), evaluate=False)\n", + "kc1 = [0, kc1[px], kc1[py], kc1[pz]]\n", + "for i in range(4):\n", + " kc1[i] = sp.expand(kc1[i])\n", + " kc1[i] = kc1[i].subs(qw0*qw0, 1-qx0*qx0-qy0*qy0-qz0*qz0)\n", + " kc1[i] = kc1[i].subs(qw1*qw1, 1-qx1*qx1-qy1*qy1-qz1*qz1)\n", + " kc1[i] = sp.simplify(kc1[i])\n", + "kc1[3]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "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.10.10" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/scripts/qmbb/bbox.py b/scripts/qmbb/bbox.py new file mode 100644 index 0000000000..1052d09bf1 --- /dev/null +++ b/scripts/qmbb/bbox.py @@ -0,0 +1,54 @@ +import numpy as np +import math + +class BBox(): + def __init__(self, bbmin=None, bbmax=None): + if bbmin is None: + self.bbmin = np.array([math.inf, math.inf, math.inf, math.inf]) + else: + self.bbmin = bbmin + if bbmax is None: + self.bbmax = np.array([-math.inf, -math.inf, -math.inf, -math.inf]) + else: + self.bbmax = bbmax + + def extend(self, other): + if isinstance(other, BBox): + self.bbmin = np.minimum(self.bbmin, other.bbmin) + self.bbmax = np.maximum(self.bbmax, other.bbmax) + else: + self.bbmin = np.minimum(self.bbmin, other) + self.bbmax = np.maximum(self.bbmax, other) + + def extendAll(self, *others): + for other in others: + self.extend(other) + + def contains(self, p, eps=0.0): + for i in range(3): + if p[i] < self.bbmin[i]-eps or p[i] > self.bbmax[i]+eps: + return False + return True + + def getCorners(self): + corners = np.array([[self.bbmin[0], self.bbmin[1], self.bbmin[2], 1], + [self.bbmin[0], self.bbmin[1], self.bbmax[2], 1], + [self.bbmin[0], self.bbmax[1], self.bbmin[2], 1], + [self.bbmin[0], self.bbmax[1], self.bbmax[2], 1], + [self.bbmax[0], self.bbmin[1], self.bbmin[2], 1], + [self.bbmax[0], self.bbmin[1], self.bbmax[2], 1], + [self.bbmax[0], self.bbmax[1], self.bbmin[2], 1], + [self.bbmax[0], self.bbmax[1], self.bbmax[2], 1]]) + return corners + + def __str__(self): + return "BBox\n %s\n %s" % (self.bbmin, self.bbmax) + +def getFacesForBBoxCorners(c): + faces = [[c[0][:3], c[1][:3], c[3][:3], c[2][:3]], + [c[4][:3], c[5][:3], c[7][:3], c[6][:3]], + [c[0][:3], c[2][:3], c[6][:3], c[4][:3]], + [c[1][:3], c[3][:3], c[7][:3], c[5][:3]], + [c[0][:3], c[1][:3], c[5][:3], c[4][:3]], + [c[2][:3], c[3][:3], c[7][:3], c[6][:3]]] + return faces