{"id":312,"date":"2009-08-14T15:01:50","date_gmt":"2009-08-14T13:01:50","guid":{"rendered":"http:\/\/viscomp.alexandra.dk\/?p=312"},"modified":"2009-08-14T15:01:50","modified_gmt":"2009-08-14T13:01:50","slug":"deforming-geometry-using-radial-basis-functions","status":"publish","type":"post","link":"https:\/\/viscomp.alexandra.dk\/?p=312","title":{"rendered":"Noe&#8217;s tutorial on deforming 3D geometry using RBFs"},"content":{"rendered":"<p>In the following\u00a0I will present a method for deforming three dimensional geometry\u00a0using a technique relying on <em>radial basis functions<\/em> (RBFs).\u00a0These are\u00a0mathematical functions that take\u00a0a real number as input argument and return\u00a0a real number. RBFs can be used for creating a smooth\u00a0interpolation\u00a0between values\u00a0known\u00a0only at a\u00a0discrete set of positions. The term <em>radial<\/em> is used because the input argument given is typically computed as the distance between a fixed position in 3D space and another position\u00a0at which\u00a0we would like to evaluate\u00a0a certain quantity.<\/p>\n<p>The tutorial\u00a0will\u00a0give a short introduction to the linear algebra\u00a0involved. However the source code contains a working implementation of the technique which may be used as a black box.<\/p>\n<p>Source code with Visual Studio 2005 solution can be found <a href=\"http:\/\/www.daimi.au.dk\/~noe\/Tutorials\/RBFTutorial.zip\">here<\/a>. The code should also compile on other platforms.<\/p>\n<h3>Interpolation by radial basis functions<\/h3>\n<p>Assume that the value of a scalar valued function $$F : mathbb{R}^3 rightarrow mathbb{R}$$ is known in $$M$$ distinct discrete points $$mathbf{x}_i$$\u00a0 in three dimensional space. Then RBFs provide a means for creating a smooth interpolation function of $$F$$ in the whole domain of $$mathbb{R}^3$$. This function is written as a sum of $$M$$ evaluations of a radial basis function $$g(r_i) : mathbb{R} rightarrow mathbb{R}$$ where $$r_i$$ is the distance between the point $$mathbf{x} = (x, y, z)$$ to be evaluated and $$mathbf{x}_i$$:<\/p>\n<p>$$!F(mathbf{x}) = sum_{i=1}^M a_i g(||mathbf{x} &#8211; mathbf{x}_i||) + c_0 + c_1 x + c_2 y + c_3 z, mathbf{x} = (x,y,z) mathbf{(1)}$$<\/p>\n<p>Here $$a_i$$ are scalar coefficients and the last four terms constitute a first degree polynomial with coefficients $$c_0$$ to $$c_3$$. These terms\u00a0describe an affine transformation which cannot be realised by the radial basis functions alone. From the $$M$$ known function values $$F( x_i, y_i, z_i ) = F_i$$ we can assemble a system of $$M+4$$ linear equations:\u00a0\u00a0$$mathbf{G} mathbf{A} = mathbf{F}$$<br \/>\nwhere $$mathbf{F} = (F_1, F_2, ldots, F_M, 0, 0, 0, 0)$$, $$mathbf{A} = (a_1, a_2, ldots, a_M, c_0, c_1, c_2, c_3)$$ and $$mathbf{G}$$ is an $$(M+4) times (M+4)$$ matrix :<\/p>\n<p>$$! mathbf{G} = left[begin{array}{cccccccccc}g_{11} &amp; g_{12} &amp; bullet &amp; bullet &amp; bullet &amp; g_{1M} &amp; 1 &amp; x_1 &amp; y_1 &amp; z_1 \\ g_{21} &amp; g_{22} &amp; bullet &amp; bullet &amp; bullet &amp; g_{2M} &amp; 1 &amp; x_2 &amp; y_2 &amp; z_2 \\ bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet \\ bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet \\ bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet \\ g_{M1} &amp; g_{M2} &amp; bullet &amp; bullet &amp; bullet &amp; g_{MM} &amp; 1 &amp; x_M &amp; y_M &amp; z_M \\ 1 &amp; 1 &amp; bullet &amp; bullet &amp; bullet &amp; 1 &amp; 0 &amp; 0 &amp; 0 &amp; 0 \\ x_1 &amp; x_2 &amp; bullet &amp; bullet &amp; bullet &amp; x_M &amp; 0 &amp; 0 &amp; 0 &amp; 0 \\ y_1 &amp; y_2 &amp; bullet &amp; bullet &amp; bullet &amp; y_M &amp; 0 &amp; 0 &amp; 0 &amp; 0 \\ z_1 &amp; z_2 &amp; bullet &amp; bullet &amp; bullet &amp; z_M &amp; 0 &amp; 0 &amp; 0 &amp; 0end{array}right] $$<\/p>\n<p>Here $$g_{ij} = g(|| mathbf{x}_i &#8211; mathbf{x}_j ||)$$. A number of choices for $$g$$ will result in a unique solution of the system. In this tutorial we use the <em>shifted log function<\/em>:<\/p>\n<p>$$! g(t) = sqrt{log(t^2 + k^2)}, k^2geq 1$$<br \/>\nwith $$k = 1$$. Solving\u00a0the\u00a0equation system\u00a0for $$mathbf{A}$$ gives us the coefficients to use\u00a0 in equation $$textbf{(1)}$$ when interpolating between known values.<\/p>\n<p>&nbsp;<\/p>\n<h3>Interpolating displacements<\/h3>\n<p>How can RBF&#8217;s be used for deforming geometry? Well assume that the deformation is known for\u00a0$$M$$ 3D positions\u00a0$$mathbf{x}_i$$ and that this information is\u00a0represented\u00a0by a vector describing\u00a03D displacement $$mathbf{u}_i$$ of\u00a0the geometry\u00a0that was positioned at $$mathbf{x}_i$$ in the original, undeformed state. You can think of the $$mathbf{x}_i$$ positions as\u00a0control points that have been moved to positions $$mathbf{x}_i+mathbf{u}_i$$. The RBF interpolation method can now be used for interpolating these\u00a0displacements to other\u00a0positions.<\/p>\n<p>Using the\u00a0notation\u00a0$$mathbf{x}_i = (x_i, y_i, z_i)$$\u00a0and $$mathbf{u}_i = (u^x_i, u^y_i, u^z_i)$$\u00a0 three\u00a0linear systems are set up as above letting the displacements $$u$$ be the quantity we called $$a$$ in the previous section:<\/p>\n<p>$$!mathbf{G} mathbf{A}_x = (u^x_1, u^x_2, ldots, u^x_M, 0, 0, 0, 0)^T$$<br \/>\n$$!mathbf{G} mathbf{A}_y = (u^y_1, u^y_2, ldots, u^y_M, 0, 0, 0, 0)^T$$<br \/>\n$$!mathbf{G} mathbf{A}_z = (u^z_1, u^z_2, ldots, u^z_M, 0, 0, 0, 0)^T$$<\/p>\n<p>where $$mathbf{G}$$ is assembled as described above. Solving for $$mathbf{A}_x$$, $$mathbf{A}_y$$, and $$mathbf{A}_z$$ involves a single matrix inversion and three matrix-vector multiplications and gives us the coefficients for interpolating displacements in all three directions by the expression $$mathbf{(1)}$$<\/p>\n<p>&nbsp;<\/p>\n<h3>The source code<\/h3>\n<p>In the source code accompanying this tutorial you will find the class RBFInterpolator which has an interface like this:<\/p>\n<pre lang=\"CPP\">class RBFInterpolator\n{\npublic:\n\tRBFInterpolator();\n\t~RBFInterpolator();\n\n\t\/\/create an interpolation function f that obeys F_i = f(x_i, y_i, z_i)\n\tRBFInterpolator(vector x, vector y, vector z, vector F);\n\n\t\/\/specify new function values F_i while keeping the same\n\tvoid UpdateFunctionValues(vector F);\n\n\t\/\/evaluate the interpolation function f at the 3D position (x,y,z)\n\treal interpolate(float x, float y, float z);\n\nprivate:\n            ...\n};<\/pre>\n<p>This class implements the interpolation method described above using the <a href=\"http:\/\/www.robertnz.net\/nm_intro.htm\">newmat matrix library<\/a>. It is quite easy to use: just fill stl::vectors with the $$x_i$$, $$y_i$$ and $$z_i$$ components of the positions where the value $$F$$ is known and another stl::vector with the $$F_i$$ values. Then pass these vectors to the RBFInterpolator constructor, and it will be ready to interpolate. The $$F$$ value at any position is then evaluated by calling the &#8216;interpolate&#8217; function. If some of the $$F_i$$ values change at any time, the interpolator can be quickly updated using the &#8216;UpdateFunctionValues&#8217; method.<\/p>\n<p>We want to deform a triangle surface mesh. These are stored in a class TriangleMesh, and loaded from OBJ files.<br \/>\nIn the source code the allocation of stl::vectors describing the control points and the initialisation of RBFInterpolators looks like this:<\/p>\n<pre lang=\"CPP\">void loadMeshAndSetupControlPoints()\n{\n\t\/\/ open an OBJ file to deform\n\tstring sourceOBJ = \"test_dragon.obj\";\n\tundeformedMesh = new TriangleMesh(sourceOBJ);\n\tdeformedMesh = new TriangleMesh(sourceOBJ);\n\n\t\/\/ we want 11 control points which we place at different vertex positions\n\tconst int numControlPoints = 11;\n\n\tconst int verticesPerControlPoint = ((int)undeformedMesh->getParticles().size())\/numControlPoints;\n\n\tfor (int i = 0; i<numControlPoints; i++)\n\t{\n\t\tVector3 pos = undeformedMesh->getParticles()[i*verticesPerControlPoint].getPos();\n\t\tcontrolPointPosX.push_back(pos[0]);\n\t\tcontrolPointPosY.push_back(pos[1]);\n\t\tcontrolPointPosZ.push_back(pos[2]);\n\t}\n\n\t\/\/ allocate vectors for storing displacements\n\tfor (unsigned int i = 0; i<controlPointPosX.size();  i++)\n\t{\n\t\tcontrolPointDisplacementX.push_back(0.0f);\n\t\tcontrolPointDisplacementY.push_back(0.0f);\n\t\tcontrolPointDisplacementZ.push_back(0.0f);\n\t}\n\n\t\/\/ initialize interpolation functions\n\trbfX = RBFInterpolator(controlPointPosX, controlPointPosY, \n                                           controlPointPosZ, controlPointDisplacementX );\n\trbfY = RBFInterpolator(controlPointPosX, controlPointPosY, \n                                           controlPointPosZ, controlPointDisplacementY );\n\trbfZ = RBFInterpolator(controlPointPosX, controlPointPosY, \n                                           controlPointPosZ, controlPointDisplacementZ );\n}<\/pre>\n<p>Now all displacements are set to zero vectors - not terribly exciting! To make it a bit more fun we can vary the displacements with time:<\/p>\n<pre lang=\"CPP\">\t\/\/ move control points\n\tfor (unsigned int i = 0; i < controlPointPosX.size(); i++ )\n\t{\n\t\tcontrolPointDisplacementX[i] = displacementMagnitude*cosf(time+i*timeOffset);\n\t\tcontrolPointDisplacementY[i] = displacementMagnitude*sinf(2.0f*(time+i*timeOffset));\n\t\tcontrolPointDisplacementZ[i] = displacementMagnitude*sinf(4.0f*(time+i*timeOffset));\n\t}\n\n\t\/\/ update the control points based on the new control point positions\n\trbfX.UpdateFunctionValues(controlPointDisplacementX);\n\trbfY.UpdateFunctionValues(controlPointDisplacementY);\n\trbfZ.UpdateFunctionValues(controlPointDisplacementZ);\n\n\t\/\/ deform the object to render\n\tdeformObject(deformedMesh, undeformedMesh);<\/pre>\n<p>Here the function 'deformObject' looks like this:<\/p>\n<pre lang=\"CPP\">\/\/ Code for deforming the mesh 'initialObject' based on the current interpolation functions (global variables). \n\/\/ The deformed vertex positions will be stored in the mesh 'res'\n\/\/ The triangle connectivity is assumed to be already correct in 'res'  \nvoid deformObject(TriangleMesh* res, TriangleMesh* initialObject)\n{\n\tfor (unsigned int i = 0; i < res->getParticles().size(); i++)\n\t{\n\t\tVector3 oldpos = initialObject->getParticles()[i].getPos();\n\n\t\tVector3 newpos;\n\t\tnewpos[0] = oldpos[0] + rbfX.interpolate(oldpos[0], oldpos[1], oldpos[2]);\n\t\tnewpos[1] = oldpos[1] + rbfY.interpolate(oldpos[0], oldpos[1], oldpos[2]);\n\t\tnewpos[2] = oldpos[2] + rbfZ.interpolate(oldpos[0], oldpos[1], oldpos[2]);\n\n\t\tres->getParticles()[i].setPos(newpos);\n\t}\n}<\/pre>\n<p>That's it!!! Now I encourage you to download the <a href=\"http:\/\/www.daimi.au.dk\/~noe\/Tutorials\/RBFTutorial.zip\">source code<\/a> and play with it. Perhaps you can experiment with other radial basis functions? Or make the dragon crawl like a caterpillar? If you code something interesting based on this tutorial send a link to me and we will link to it from this page \ud83d\ude42<\/p>\n<p><a href=\"mailto:karsten.noe{at}alexandra.dk\">Karsten Noe<\/a><\/p>\n<p>I got a mail from Woo Won Kim from Yonsei University in South Korea who has made a head modeling program that can generate 3D human heads from two pictures of the person using code from this RBF tutorial. Check out a video of this <a href=\"http:\/\/www.youtube.com\/watch?v=RdsKzhtXaIw\">here<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In the following\u00a0I will present a method for deforming three dimensional geometry\u00a0using a technique relying on radial basis functions (RBFs).\u00a0These are\u00a0mathematical functions that take\u00a0a real number as input argument and return\u00a0a real number. RBFs can be used for creating a smooth\u00a0interpolation\u00a0between values\u00a0known\u00a0only at a\u00a0discrete set of positions. The term radial is used because the input argument given is typically computed as the distance between a fixed position in 3D space and another position\u00a0at which\u00a0we would like to evaluate\u00a0a certain quantity. The tutorial\u00a0will\u00a0give a short introduction to the linear algebra\u00a0involved. However the source code contains a working implementation of the technique which may be used as a black box. Source code with Visual Studio 2005 solution can be found here. The code should also compile on other platforms. Interpolation by radial basis functions Assume that the value of a scalar valued function $$F : mathbb{R}^3 rightarrow mathbb{R}$$ is known in $$M$$ distinct discrete points $$mathbf{x}_i$$\u00a0 in three dimensional space. Then RBFs provide a means for creating a smooth interpolation function of $$F$$ in the whole domain of $$mathbb{R}^3$$. This function is written as a sum of $$M$$ evaluations of a radial basis function $$g(r_i) : mathbb{R} rightarrow mathbb{R}$$ where $$r_i$$ is the distance between the point $$mathbf{x} = (x, y, z)$$ to be evaluated and $$mathbf{x}_i$$: $$!F(mathbf{x}) = sum_{i=1}^M a_i g(||mathbf{x} &#8211; mathbf{x}_i||) + c_0 + c_1 x + c_2 y + c_3 z, mathbf{x} = (x,y,z) mathbf{(1)}$$ Here $$a_i$$ are scalar coefficients and the last four terms constitute a first degree polynomial with coefficients $$c_0$$ to $$c_3$$. These terms\u00a0describe an affine transformation which cannot be realised by the radial basis functions alone. From the $$M$$ known function values $$F( x_i, y_i, z_i ) = F_i$$ we can assemble a system of $$M+4$$ linear equations:\u00a0\u00a0$$mathbf{G} mathbf{A} = mathbf{F}$$ where $$mathbf{F} = (F_1, F_2, ldots, F_M, 0, 0, 0, 0)$$, $$mathbf{A} = (a_1, a_2, ldots, a_M, c_0, c_1, c_2, c_3)$$ and $$mathbf{G}$$ is an $$(M+4) times (M+4)$$ matrix : $$! mathbf{G} = left[begin{array}{cccccccccc}g_{11} &amp; g_{12} &amp; bullet &amp; bullet &amp; bullet &amp; g_{1M} &amp; 1 &amp; x_1 &amp; y_1 &amp; z_1 \\ g_{21} &amp; g_{22} &amp; bullet &amp; bullet &amp; bullet &amp; g_{2M} &amp; 1 &amp; x_2 &amp; y_2 &amp; z_2 \\ bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet \\ bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet \\ bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet &amp; bullet \\ g_{M1} &amp; g_{M2} &amp; bullet &amp; bullet &amp; bullet &amp; g_{MM} &amp; 1 &amp; x_M &amp; y_M &amp; z_M \\ 1 &amp; 1 &amp; bullet &amp; bullet &amp; bullet &amp; 1 &amp; 0 &amp; 0 &amp; 0 &amp; 0 \\ x_1 &amp; x_2 &amp; bullet &amp; bullet &amp; bullet &amp; x_M &amp; 0 &amp; 0 &amp; 0 &amp; 0 \\ y_1 &amp; y_2 &amp; bullet &amp; bullet &amp; bullet &amp; y_M &amp; 0 &amp; 0 &amp; 0 &amp; 0 \\ z_1 &amp; z_2 &amp; bullet &amp; bullet &amp; bullet &amp; z_M &amp; 0 &amp; 0 &amp; 0 &amp; 0end{array}right] $$ Here $$g_{ij} = g(|| mathbf{x}_i &#8211; mathbf{x}_j ||)$$. A number of choices for $$g$$ will result in a unique solution of the system. In this tutorial we use the shifted log function: $$! g(t) = sqrt{log(t^2 + k^2)}, k^2geq 1$$ with $$k = 1$$. Solving\u00a0the\u00a0equation system\u00a0for $$mathbf{A}$$ gives us the coefficients to use\u00a0 in equation $$textbf{(1)}$$ when interpolating between known values. &nbsp; Interpolating displacements How can RBF&#8217;s be used for deforming geometry? Well assume that the deformation is known for\u00a0$$M$$ 3D positions\u00a0$$mathbf{x}_i$$ and that this information is\u00a0represented\u00a0by a vector describing\u00a03D displacement $$mathbf{u}_i$$ of\u00a0the geometry\u00a0that was positioned at $$mathbf{x}_i$$ in the original, undeformed state. You can think of the $$mathbf{x}_i$$ positions as\u00a0control points that have been moved to positions $$mathbf{x}_i+mathbf{u}_i$$. The RBF interpolation method can now be used for interpolating these\u00a0displacements to other\u00a0positions. Using the\u00a0notation\u00a0$$mathbf{x}_i = (x_i, y_i, z_i)$$\u00a0and $$mathbf{u}_i = (u^x_i, u^y_i, u^z_i)$$\u00a0 three\u00a0linear systems are set up as above letting the displacements $$u$$ be the quantity we called $$a$$ in the previous section: $$!mathbf{G} mathbf{A}_x = (u^x_1, u^x_2, ldots, u^x_M, 0, 0, 0, 0)^T$$ $$!mathbf{G} mathbf{A}_y = (u^y_1, u^y_2, ldots, u^y_M, 0, 0, 0, 0)^T$$ $$!mathbf{G} mathbf{A}_z = (u^z_1, u^z_2, ldots, u^z_M, 0, 0, 0, 0)^T$$ where $$mathbf{G}$$ is assembled as described above. Solving for $$mathbf{A}_x$$, $$mathbf{A}_y$$, and $$mathbf{A}_z$$ involves a single matrix inversion and three matrix-vector multiplications and gives us the coefficients for interpolating displacements in all three directions by the expression $$mathbf{(1)}$$ &nbsp; The source code In the source code accompanying this tutorial you will find the class RBFInterpolator which has an interface like this: class RBFInterpolator { public: RBFInterpolator(); ~RBFInterpolator(); \/\/create an interpolation function f that obeys F_i = f(x_i, y_i, z_i) RBFInterpolator(vector x, vector y, vector z, vector F); \/\/specify new function values F_i while keeping the same void UpdateFunctionValues(vector F); \/\/evaluate the interpolation function f at the 3D position (x,y,z) real interpolate(float x, float y, float z); private: &#8230; }; This class implements the interpolation method described above using the newmat matrix library. It is quite easy to use: just fill stl::vectors with the $$x_i$$, $$y_i$$ and $$z_i$$ components of the positions where the value $$F$$ is known and another stl::vector with the $$F_i$$ values. Then pass these vectors to the RBFInterpolator constructor, and it will be ready to interpolate. The $$F$$ value at any position is then evaluated by calling the &#8216;interpolate&#8217; function. If some of the $$F_i$$ values change at any time, the interpolator can be quickly updated using the &#8216;UpdateFunctionValues&#8217; method. We want to deform a triangle surface mesh. These are stored in a class TriangleMesh, and loaded from OBJ files. In the source code the allocation of stl::vectors describing the control points and the initialisation of RBFInterpolators looks like this: void loadMeshAndSetupControlPoints() { \/\/ open an OBJ file to deform string sourceOBJ = &#8220;test_dragon.obj&#8221;; undeformedMesh = new TriangleMesh(sourceOBJ); deformedMesh = new TriangleMesh(sourceOBJ); \/\/ we want 11 control points which we place at different vertex positions const int numControlPoints = 11; const int verticesPerControlPoint = ((int)undeformedMesh->getParticles().size())\/numControlPoints; for (int i = 0; igetParticles()[i*verticesPerControlPoint].getPos(); controlPointPosX.push_back(pos[0]); controlPointPosY.push_back(pos[1]); controlPointPosZ.push_back(pos[2]); } \/\/ allocate vectors for storing displacements for (unsigned int i = 0; igetParticles().size(); i++) { Vector3 oldpos = initialObject->getParticles()[i].getPos(); Vector3 newpos; newpos[0] = oldpos[0] + rbfX.interpolate(oldpos[0], oldpos[1], oldpos[2]); newpos[1] = oldpos[1] + rbfY.interpolate(oldpos[0], oldpos[1], oldpos[2]); newpos[2] = oldpos[2] + rbfZ.interpolate(oldpos[0], oldpos[1], oldpos[2]); res->getParticles()[i].setPos(newpos); } } That&#8217;s it!!! Now I encourage you to download the source code and play with it. Perhaps you can experiment with other radial basis functions? Or make the dragon crawl like a caterpillar? If you code something interesting based on this tutorial send a link to me and we will link to it from this page \ud83d\ude42 Karsten Noe I got a mail from Woo Won Kim from Yonsei University in South Korea who has made a head modeling program that can generate 3D human heads from two pictures of the person using code from this RBF tutorial. Check out a video of this here.<\/p>\n","protected":false},"author":5,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[2],"tags":[20,27,28,35,78,100,101],"_links":{"self":[{"href":"https:\/\/viscomp.alexandra.dk\/index.php?rest_route=\/wp\/v2\/posts\/312"}],"collection":[{"href":"https:\/\/viscomp.alexandra.dk\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/viscomp.alexandra.dk\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/viscomp.alexandra.dk\/index.php?rest_route=\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/viscomp.alexandra.dk\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=312"}],"version-history":[{"count":0,"href":"https:\/\/viscomp.alexandra.dk\/index.php?rest_route=\/wp\/v2\/posts\/312\/revisions"}],"wp:attachment":[{"href":"https:\/\/viscomp.alexandra.dk\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=312"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/viscomp.alexandra.dk\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=312"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/viscomp.alexandra.dk\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=312"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}