diff --git a/examples/bezier2d/README.md b/examples/bezier2d/README.md new file mode 100644 index 0000000..fb51965 --- /dev/null +++ b/examples/bezier2d/README.md @@ -0,0 +1,40 @@ +## Bezier Curve Example + +This example has primitives to compute Bezier Curve Coefficients given $N$ control points. Refer to `bezier.slang` file for the slang kernels. Along with computing Bezier Curve Coefficients, and interpolating along the curves, there's also primitives to compute SDF of the Bezier Curve, using implicitization. + +Refer to `notebook.ipynb` for an example of initializing a Bezier Curve with 4 control points, and computing SDF. Example Images from the notebook shown below. + +![alt text](assets/image.png) + +![alt text](assets/image-1.png) + +There are 2 toy application examples built on top of the Bezier Curve Primitive + +### Nearest point to Bezier Curve from an initialization using SDF based tracing. + +A Bezier Curve is initialized with random control points. Then from a random 2D point as an initialization, the point closest to the Bezier curve is reached by minimizing the absolute value of the SDF. `sdf_descent.py` runs the code for this, and has the initialization setup inside. On running the code, an image with the name `sdf_descent_{N}pts.png` will be generated in the same folder. $N$ is the number of control points, specified inside the file. An example of the image is shown below - + +![SDF Descent Example](assets/sdf_descent_6pts.png) + +You can run the file as + +``` +python3 sdf_descent.py +``` + +Parameters such as number of control points, learning rate etc can be changed inside the file. + + +### Fitting Bezier Curves to Arbitrary shapes + +We show that we can optimize the locations of the control points of Bezier curves to fit to arbitrary parameteric shapes. Refer to `bezier_curvefit.py`. In the file, we have 3 Shapes - `HEART`, `ELLIPSE` and `ASTRID`. The image below shows fitting a heart shape, starting from a randomly initialized Bezier Curve. In the image below, the predicted curve is intentionally scaled so that the ground truth and the prediction are clearly visible. The fit is actually perfect, and makes it hard to see both the curves overlayed separately on the plot. + +![Initialization](assets/init.png) +![Heart Fit](assets/control_pts_descent.png) + +You can run the file as + +``` +python3 bezier_curvefit.py +``` +On running the file as is, the optimization loss curve, initial Bezier Curve, and the final fit will be saved in a sub-folder `heart_20` in the current directory. This can be modified from inside the code, along with the number of control points, learning rate etc. \ No newline at end of file diff --git a/examples/bezier2d/assets/Loss_Curve.png b/examples/bezier2d/assets/Loss_Curve.png new file mode 100644 index 0000000..dadaee0 Binary files /dev/null and b/examples/bezier2d/assets/Loss_Curve.png differ diff --git a/examples/bezier2d/assets/control_pts_descent.png b/examples/bezier2d/assets/control_pts_descent.png new file mode 100644 index 0000000..61158d6 Binary files /dev/null and b/examples/bezier2d/assets/control_pts_descent.png differ diff --git a/examples/bezier2d/assets/image-1.png b/examples/bezier2d/assets/image-1.png new file mode 100644 index 0000000..db8ec32 Binary files /dev/null and b/examples/bezier2d/assets/image-1.png differ diff --git a/examples/bezier2d/assets/image.png b/examples/bezier2d/assets/image.png new file mode 100644 index 0000000..5b9b3a4 Binary files /dev/null and b/examples/bezier2d/assets/image.png differ diff --git a/examples/bezier2d/assets/init.png b/examples/bezier2d/assets/init.png new file mode 100644 index 0000000..6866ea9 Binary files /dev/null and b/examples/bezier2d/assets/init.png differ diff --git a/examples/bezier2d/assets/sdf_descent_6pts.png b/examples/bezier2d/assets/sdf_descent_6pts.png new file mode 100644 index 0000000..ce1c0bf Binary files /dev/null and b/examples/bezier2d/assets/sdf_descent_6pts.png differ diff --git a/examples/bezier2d/bezier.slang b/examples/bezier2d/bezier.slang new file mode 100644 index 0000000..b12aa47 --- /dev/null +++ b/examples/bezier2d/bezier.slang @@ -0,0 +1,142 @@ +static const int N = NUM_CTRL_PTS; +static const int c = DIM; +static const int N1 = c * (N- 1); + +struct MatrixG : IDifferentiable +{ + float vals[C]; +} + +int nCi(int n, int i) +{ + if (i > n) return 0; + if (i == 0 || i == n) return 1; + if (i > n - i) i = n - i; + + int result = 1; + for (int k = 1; k <= i; ++k) + { + result *= n - k + 1; + result /= k; + } + + return result; +} + +[PreferRecompute] +int fact(int n) +{ + int result = 1; + for (int i = 1; i <= n; ++i) + { + result *= i; + } + return result; +} + +/* + * We bottleneck the component calculation through a single function tagged as [PreferRecompute] + * to avoid interediate memory allocations for contents of loops. + */ +[PreferRecompute] +[Differentiable] +float calc_component(uint i, uint j, uint k, DiffTensorView control_pts) +{ + return pow(-1, i + j) * control_pts[i, k] / (fact(i) * fact(j - i)); +} + +// Function to assemble matrix to compute determinant of to compute SDF. +[Differentiable] +void asm_mat(uint index, DiffTensorView output, matrix coeffs) +{ + /** Function to create the matrix whose determinant is to be evaluated to get the sdf + @param coeffs: Tensor (N,c) + **/ + + for (int i = 0; i < N - 1; i++) + for (int j = 0; j < N; j++) + for (int k = 0; k < c; k++) + { + output.storeOnce(uint3(index, (k * (N - 1) + i), j + i), coeffs[j][k]); + } +} + +[AutoPyBindCUDA] +[CUDAKernel] +[Differentiable] +void bezier2D(DiffTensorView t, DiffTensorView control_pts, DiffTensorView output) +{ + /** @param t (tensor Mx1) : indices between 0-1 to traverse across the Bezier curve + ** @param control_pts (Nx2): N - Degree of Bezier Curve 2D + */ + uint3 tIdx = cudaThreadIdx() + cudaBlockIdx() * cudaBlockDim(); + + // If the thread index is beyond the input size, exit early. + if (tIdx.x > t.size(0)) + return; + [ForceUnroll] + for (int i = 0; i <= N - 1; i++) + { + output[tIdx.x, 0] = output[tIdx.x, 0] + nCi(N - 1, i) * pow((1 - t[tIdx.x]), (N - 1 - i)) * pow(t[tIdx.x], i) * control_pts[i, 0]; + output[tIdx.x, 1] = output[tIdx.x, 1] + nCi(N - 1, i) * pow((1 - t[tIdx.x]), (N - 1 - i)) * pow(t[tIdx.x], i) * control_pts[i, 1]; + } +} + +[AutoPyBindCUDA] +[CUDAKernel] +[Differentiable] +void bezier2DSDF(DiffTensorView xy, DiffTensorView bcoeffs, DiffTensorView output) +{ + /** @param xy - M,c + ** @param bcoeffs - N,c + ** @return output - M, N1, N1 matrix for each point at which SDF is to be evaluated + ** Each thread computes the SDF value for a given xy coordinate from the determinant function above. Maybe change it up to be just differentiable, and not AutoPyBindCUDA + */ + uint3 tIdx = cudaThreadIdx() + cudaBlockIdx() * cudaBlockDim(); + matrix coeffs; // = compute_coeffs_device(control_pts); + + // copying coefficients to a separate variable for each thread. + for (int i = 0; i < N; i++) + for (int j = 0; j < c; j++) + coeffs[i][j] = bcoeffs[i, j]; + + int M = xy.size(0); // xy - shaped M,2 + if (tIdx.x > M) + { + return; + } + + float coord[c]; + [ForceUnroll] + for (int i = 0; i < c; i++) + coord[i] = xy[tIdx.x, i]; + + [ForceUnroll] + for (int i = 0; i < c; i++) + coeffs[0][i] -= coord[i]; + + asm_mat(tIdx.x, output, coeffs); +} + +[AutoPyBindCUDA] +[CudaKernel] +[Differentiable] +void compute_coeffs(DiffTensorView control_pts, DiffTensorView output) +{ + // Compute the coefficients a_i for t^i, for bezier polynomial \sum a_i . t^i + for (int k = 0; k < c; k++) + { + for (int j = 0; j < N; j++) + { + int nCj = fact(N - 1) / fact(N - 1 - j); // degree of the polynomial is N-1 + float sum = 0; + + for (int i = 0; i < N; i++) + { + if (i <= j) + sum += calc_component(i, j, k, control_pts); + } + output.storeOnce(uint2(j, k), nCj * sum); + } + } +} diff --git a/examples/bezier2d/bezier_curvefit.py b/examples/bezier2d/bezier_curvefit.py new file mode 100644 index 0000000..6162ac4 --- /dev/null +++ b/examples/bezier2d/bezier_curvefit.py @@ -0,0 +1,127 @@ +## Fit Bezier Curve to Heart Shaped Equation +import torch +import slangtorch +import os +import matplotlib.pyplot as plt + +N = 20 +c = 2 +m = slangtorch.loadModule('bezier.slang', defines={"NUM_CTRL_PTS": N, "DIM":c}, verbose=True) + + +def heart(t): + t = t*2*torch.pi + x = 16*(torch.sin(t))**3 + y = 13*torch.cos(t) - 5*torch.cos(2*t) -2*torch.cos(3*t) - torch.cos(4*t) + return torch.hstack([x.reshape(-1,1),y.reshape(-1,1)]) + +def ellipse(t, a, b): + t = t*2*torch.pi + x = a * (torch.cos(t)) + y = b * (torch.sin(t)) + return torch.hstack([x.reshape(-1,1),y.reshape(-1,1)]) + +def astrid(t, a): + t = t*2*torch.pi + x = a * (torch.cos(t))**3 + y = a * (torch.sin(t))**3 + return torch.hstack([x.reshape(-1,1),y.reshape(-1,1)]) + +def curve_from_coeffs(t, coeffs): + """ To check if coefficients are correct """ + output = torch.zeros(t.shape[0], coeffs.shape[1]).cuda() + for i in range(coeffs.shape[0]): + output = output + (t**i).view(-1,1) * coeffs[i].view(1,-1) + return output + +class Bezier2D(torch.autograd.Function): + @staticmethod + def forward(ctx, t, control_pts): + """ + t: M,1 (torch.tensor) on GPU, parameter for bezier curves + control_pts: N,2 (torch.tensor) + """ + outputs = torch.zeros(t.shape[0], control_pts.shape[1]).cuda() + kernel_with_args = m.bezier2D(t=t, control_pts=control_pts, output=outputs) + NUM_BLOCKS = 1 + t.shape[0] // 1024 + kernel_with_args.launchRaw( + blockSize=(NUM_BLOCKS, 1, 1), + gridSize=(1024, 1, 1)) + ctx.save_for_backward(t, control_pts, outputs) + return outputs + + @staticmethod + def backward(ctx, grad_outputs): + (t, control_pts, outputs) = ctx.saved_tensors + grad_ctrl_pts = torch.zeros_like(control_pts).cuda() + grad_t = torch.zeros_like(t).cuda() + # Note: When using DiffTensorView, grad_output gets 'consumed' during the reverse-mode. + # If grad_output may be reused, consider calling grad_output = grad_output.clone() + + kernel_with_args = m.bezier2D.bwd(t=(t, grad_t), + control_pts=(control_pts, grad_ctrl_pts), + output=(outputs, grad_outputs)) + NUM_BLOCKS = 1 + t.shape[0] // 1024 + kernel_with_args.launchRaw( + blockSize=(NUM_BLOCKS, 1, 1), + gridSize=(1024, 1, 1)) + + return grad_t, grad_ctrl_pts + + + + +### Initializing Control points, and Target Curve +num_pts = 100 +t = torch.linspace(0.0, 1, num_pts, dtype=torch.float).cuda() + +savedir = "./heart_{}".format(N) +os.makedirs(savedir, exist_ok=True) +# gt_pts = ellipse(t, 3.0, 4.0) +# gt_pts = astrid(t, 3.0) +gt_pts = heart(t) +control_pts = torch.rand((N, 2), dtype=torch.float).cuda() +control_pts.requires_grad_(True) + + +### Experiment 1 - Learning control points to match heart +# Define a custom parameter, for example, a single value parameter. +opt_param = torch.nn.Parameter(control_pts) +pts = Bezier2D.apply(t, opt_param) + +plt.figure() +plt.plot(pts[:,0].detach().cpu().numpy()/0.9, pts[:,1].detach().cpu().numpy()/0.9, color='red',label='predicted') +plt.title('Bezier Curve Initialization') +plt.savefig(os.path.join(savedir, 'init.png')) + +# Use an optimizer, for example, SGD, and register the custom parameter with it. +lr_init = 0.01 +optimizer = torch.optim.Adam([opt_param], lr=lr_init) + +loss_curve = [] +for epoch in range(10000): # Assuming 10000 epochs + pts = Bezier2D.apply(t, opt_param) + loss = ((torch.linalg.norm(pts - gt_pts, dim=1))).mean() + loss_value = loss.item() + optimizer.zero_grad() + for pg in optimizer.param_groups: + pg['lr'] = lr_init * 0.99 + loss.backward() + optimizer.step() + loss_curve.append(loss.item()) + print(f"Epoch {epoch+1}, Loss: {loss_value}") + +plt.figure() +pts = Bezier2D.apply(t, opt_param) +plt.plot(pts[:,0].detach().cpu().numpy()/0.95, pts[:,1].detach().cpu().numpy()/0.95, color='red',label='predicted') +plt.plot(gt_pts[:,0].detach().cpu().numpy(), gt_pts[:,1].detach().cpu().numpy(), color='green',label='gt') +plt.title('HEART') +plt.legend(['Predicted', 'Ground Truth']) +plt.savefig(os.path.join(savedir, 'control_pts_descent.png')) + +plt.figure() +plt.plot(loss_curve) +plt.title('Loss Curve') +plt.xlabel('Iterations') +plt.ylabel('Loss Value') +plt.savefig(os.path.join(savedir, 'Loss_Curve.png')) \ No newline at end of file diff --git a/examples/bezier2d/notebook.ipynb b/examples/bezier2d/notebook.ipynb new file mode 100644 index 0000000..8d4fad8 --- /dev/null +++ b/examples/bezier2d/notebook.ipynb @@ -0,0 +1,138 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "import torch\n", + "import slangpy\n", + "\n", + "N = 4\n", + "c = 2 \n", + "num_pts = 1000\n", + "\n", + "m = slangpy.loadModule('bezier.slang', defines={\"NUM_CTRL_PTS\": N, \"DIM\":c})\n", + "\n", + "t = torch.linspace(0.0, 1, num_pts, dtype=torch.float).cuda()\n", + "control_pts = torch.rand((N,2),dtype=torch.float).cuda()\n", + "output = torch.zeros((num_pts,2), dtype=torch.float).cuda()\n", + "\n", + "# Number of threads launched = blockSize * gridSize\n", + "m.bezier2D(t=t, control_pts=control_pts, output=output).launchRaw(blockSize=(32, 1, 1), gridSize=(64, 1, 1))\n", + "\n", + "coeffs = torch.zeros((N,2), dtype=torch.float).cuda()\n", + "m.compute_coeffs(control_pts=control_pts, output=coeffs).launchRaw(blockSize=(4, 1, 1), gridSize=(1, 1, 1))" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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j3wTvnjxIkaNGoVBgwZVaA0IFxcX2NraQqPRICQkpOrF36d///6YOXMmfvvttwp9KDs6OpZ5aroi/7u836BBg/DKK6/ohmouXryIKVOmlNjGx8cHWVlZle5zcX92796NEydOYPLkyQCKJqv+8MMP8PDwgLW1NQIDAx/6PFX5ECvPnTt3kJWVha+//hpff/11qccbNWqEgQMHlnuZr5WVFXr27Im//voL169fh5eX10OP17BhQ+zZsweZmZklzo5ER0frHteXvj8XFxcXWFlZISYmptRj0dHRkMvlj+xHZTRs2BBarRaXLl3SnQkCiiaF3717t1J9L8vDfh7W1tYYMmQIhgwZgvz8fAwePBhffPEFpkyZIsml5FR9OGeEakxBQQF27doFpVKpe3N77rnnoNFodMMO9yssLNStUpmVlYX//Oc/8PT0xIoVKyr0hq5QKPD0009jw4YNZa4bkZqaWum+dOzYEX379sVPP/1U5gdffn4+3n33Xd33Pj4+iI6OLnHM06dP4++//9bruA4ODggNDcXatWuxevVqKJVKDBo0qMQ2zz33HI4cOYKdO3eW2v/u3bu6QFieRo0awdPTE3PnzkVBQYHutHjXrl0RGxuL9evXo0OHDo+8nLZ4Lo8hVxp1dXXFpk2bSn316NEDFhYW2LRpU6lw9qBp06ZBCIERI0boQvH9wsPDsWLFCgBAv379oNFoSlw2DRQNFclkMjzxxBN690Hfn4tCoUCfPn2wZcsW3SXAQFEoWLVqFbp06aIbfjKkfv36AUCpq6/mzJkDAHjyyScNchxra2ukp6eXar9161aJ75VKJXx9fSGEQEFBgUGOTbUHz4xQtdmxY4fuf5ApKSlYtWoVLl26hMmTJ+vePLt164ZXXnkFM2fORGRkJPr06QNzc3NcunQJ69atw7fffotnnnkG06dPx/nz5/HRRx9hy5YtJY7j4+ODjh07llnDrFmzsG/fPgQHB+Oll16Cr68vbt++jYiICOzZswe3b9+udP9++eUX9OnTB4MHD0b//v3Rq1cvWFtb49KlS1i9ejUSExN1a428+OKLmDNnDkJDQzF27FikpKRg0aJF8PPz03vy4ZAhQzB8+HAsXLgQoaGhpeasvPfee9i6dSueeuopjB49GoGBgcjOzsbZs2exfv16XLlypcSwTlm6du2K1atXo1WrVrpx/sceewzW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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt \n", + "\n", + "def curve_from_coeffs(t, coeffs):\n", + " \"\"\" To check if coefficients are correct \"\"\"\n", + " output = torch.zeros(t.shape[0], coeffs.shape[1]).cuda()\n", + " for i in range(coeffs.shape[0]):\n", + " output = output + (t**i).view(-1,1) * coeffs[i].view(1,-1)\n", + " return output \n", + "\n", + "curve_coeffs = curve_from_coeffs(t, coeffs)\n", + "\n", + "plt.figure()\n", + "plt.plot(curve_coeffs[:,0].detach().cpu().numpy(), curve_coeffs[:,1].detach().cpu().numpy())\n", + "for i in range(control_pts.shape[0]):\n", + " plt.scatter(control_pts[i][0].cpu(), control_pts[i][1].cpu())\n", + "\n", + "plt.title(f'Bezier Curve with {N} Control Points')\n", + "plt.savefig(f'Bcurve_{N}_closed.png')" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAgAAAAGzCAYAAABZzq+8AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/H5lhTAAAACXBIWXMAAA9hAAAPYQGoP6dpAACCVUlEQVR4nO2deZxT1fnGn5tZ2WaGdWbQAUakgICCoDiASAWLVFELCliq4AJqQUWs/qQKikoRbRUXFLEKWqFuuFWtGwhuKAJF0SqiIiBlBhRh2GZJ7vn9MU6499zk3JzkZjLJPF8++TA3Z703N8nJed/3eQ0hhAAhhBBCGhS+RE+AEEIIIXUPFwCEEEJIA4QLAEIIIaQBwgUAIYQQ0gDhAoAQQghpgHABQAghhDRAuAAghBBCGiBcABBCCCENEC4ACCGEkAZIUi0ABg0ahEGDBgWPv//+exiGgUWLFmn1s2jRIhiGge+//9617ooVK2AYBlasWKE1RqzI55rK/OMf/0CXLl2QkZGBvLy8RE/HQbT3GYkdnfdfbd3nnnsu/hNrYOh8ZnpJhw4dMH78+DodsyGhtQCovQnWrFkTr/kkjAcffDApP+Crqqpw7733olevXsjJyUFeXh66deuGiRMn4quvvgrWq33tah/Z2dlo27Ythg4divvuuw/79u1z9H3LLbfY2lgf8+fP92T+X331FcaPH4+OHTvikUcewYIFC8LWlefj8/lQWFiIM888Ex999JEn86kvlJeXY+bMmTjuuOPQtGlTNGrUCN27d8f//d//4X//+1+ip5dQlixZgrlz58Z9nFmzZsEwDHTv3l2r3YoVKzBixAgUFBQgMzMTbdq0wfDhw/H888/HaaY1vPbaa7jlllviOoYb8nu0cePGOOaYY3DTTTehvLy8TueSrJ/pdUl6oicQC+3bt8ehQ4eQkZGh1e6CCy7AmDFjkJWVFXzuwQcfRKtWrRyrzYEDB+LQoUPIzMz0YsqeM3LkSPz73//G+eefjwkTJqC6uhpfffUVXnnlFfTr1w9dunSx1b/11ltRXFyM6upqlJaWYsWKFZgyZQruvvtuvPzyyzj22GMdYzz00ENo2rSp7bm+fft6Mv8VK1bANE3ce++9OProoyNqUzsf0zSxbds2PPLIIxg4cCBWr16Nnj17ejIvK9HeZ9Hy3XffYciQIdi6dSvOO+88TJw4EZmZmfjss8/w6KOP4oUXXsDXX39dJ3NJNKHef0uWLMHnn3+OKVOmxG3cH374AX/5y1/QpEkTrXY333wzbr31VnTq1AmXXXYZ2rdvj59++gmvvfYaRo4cicWLF+P3v/99XOb82muvYd68eQlfBACH36P79+/Hm2++iVmzZmH58uX44IMPYBhGxP1s3LgRPl90G9XhPtPJYZJ6AVD7S1aXtLQ0pKWlRVTX5/NFNUZd8Mknn+CVV17BrFmz8Oc//9lW9sADD2DPnj2ONsOGDUOfPn2Cx9OmTcPy5ctx5pln4qyzzsKXX36JRo0a2dqce+65aNWqVVzOYefOnQCgtfUvz+ecc85B9+7d8eyzz8ZlARDtfRaOAwcOhP1i8fv9GDFiBMrKyrBixQoMGDDAVj5r1izMmTPHk3lUVFQgMzMz6g/YuiBR778//elPOOmkkxAIBPDjjz9G1Oa5557DrbfeinPPPRdLliyxLRivu+46vPHGG6iuro7XlLXw+/0wTTNuP2ys79HLL78cI0eOxPPPP4+PPvoIJSUlEfdj/ZFGvCfmd/748ePRtGlTbN26FWeeeSaaNm2KI444AvPmzQMAbNiwAaeeeiqaNGmC9u3bY8mSJbb2tVvT7777Li677DK0bNkSOTk5uPDCC/Hzzz8rxw5nm/3qq68watQotG7dGo0aNULnzp1x4403OsastWd16NABX3zxBVauXBncuqq1v8s2SHkr3fqQbfZPPvkkevfujUaNGqFFixYYM2YMtm3b5jiPBQsWoGPHjmjUqBFOPPFEvPfeey5XvYZvv/0WANC/f39HWVpaGlq2bBlRP6eeeiqmT5+OLVu24Mknn4yoTSQ8+OCD6NatG7KystC2bVtMmjTJtijp0KEDbr75ZgBA69atYRhGVL9eCgoKAADp6fb1bGVlJW6++WYcffTRyMrKQlFREa6//npUVlYG64wfPz7s61k7F9V9du6556JFixbIzs5Gnz598PLLL9vq1N4vK1euxB//+Ee0adMGRx55ZNhzWbp0KT799FPceOONji9/AMjJycGsWbOCx+FspLIPSe19/NRTT+Gmm27CEUccgcaNG2PdunUwDAOPP/64o4833ngDhmHglVdeCT63fft2XHzxxcjPz0dWVha6deuGxx57LOz51DJixAgcf/zxtueGDx8OwzBs1+zjjz+GYRj497//bZt37ftv0KBBePXVV7Fly5bg69ShQwdbv6ZpYtasWTjyyCORnZ2NwYMH45tvvnGdYy3vvvsunnvuOW0zw/Tp09GiRQs89thjIXeLhg4dijPPPDN4vHPnTlxyySXIz89HdnY2jjvuOMfrUHvv/fWvfw1+TmRlZeGEE07AJ598Eqw3fvz44Geu9R6W+5g7d26wj//+978AgOXLl+Pkk09GkyZNkJeXh7PPPhtffvml1rm7ceqppwIANm/eDKBmEXzttdeiqKgIWVlZ6Ny5M/76179CTk4r39+176cPPvgAU6dORevWrdGkSRP87ne/w65du2ztwn2mV1dXY+bMmejUqROys7PRsmVLDBgwAG+99Zan55wMeLIDEAgEMGzYMAwcOBB33nknFi9ejMmTJ6NJkya48cYbMXbsWIwYMQLz58/HhRdeiJKSEhQXF9v6mDx5MvLy8nDLLbdg48aNeOihh7Bly5bgB0CkfPbZZzj55JORkZGBiRMnokOHDvj222/xr3/9y/bBaWXu3Lm48sor0bRp0+BCIT8/P2TdgQMH4h//+IftuS1btuCmm25CmzZtgs/NmjUL06dPx6hRo3DppZdi165duP/++zFw4ED85z//Cf7iffTRR3HZZZehX79+mDJlCr777jucddZZaNGiBYqKipTn2r59ewDA4sWL0b9/f8cXoA4XXHAB/vznP+PNN9/EhAkTbGW7d++2HaelpaF58+bK/m655RbMnDkTQ4YMwRVXXBF8TT/55BN88MEHyMjIwNy5c/HEE0/ghRdeCG4ZhjJByNTOxzRNbN++Hbfddhuys7MxatSoYB3TNHHWWWfh/fffx8SJE9G1a1ds2LAB99xzD77++mu8+OKLAIDLLrsMQ4YMsfX/+uuvY/HixbbXU+aLL75A//79ccQRR+CGG25AkyZN8Mwzz+Ccc87B0qVL8bvf/c5W/49//CNat26NGTNm4MCBA2H7rf0yvOCCC1yvQzTcdtttyMzMxJ/+9CdUVlbimGOOwVFHHYVnnnkG48aNs9V9+umn0bx5cwwdOhQAUFZWhpNOOgmGYWDy5Mlo3bo1/v3vf+OSSy5BeXm5ckv+5JNPxksvvYTy8nLk5ORACIEPPvgAPp8P7733Hs466ywAwHvvvQefzxdyUQsAN954I/bu3YsffvgB99xzDwA4zFN33HEHfD4f/vSnP2Hv3r248847MXbsWHz88ceu1ycQCODKK6/EpZdeih49erjWr2XTpk346quvcPHFF6NZs2au9Q8dOoRBgwbhm2++weTJk1FcXIxnn30W48ePx549e3D11Vfb6i9ZsgT79u3DZZddBsMwcOedd2LEiBH47rvvkJGRgcsuuwz/+9//8NZbbzk+n2pZuHAhKioqMHHiRGRlZaFFixZ4++23MWzYMBx11FG45ZZbcOjQIdx///3o378/1q1b51hcRUvtj5WWLVtCCIGzzjoL77zzDi655BL07NkTb7zxBq677jps3749+LqquPLKK9G8eXPcfPPN+P777zF37lxMnjwZTz/9NAD1Z/ott9yC2bNn49JLL8WJJ56I8vJyrFmzBuvWrcNpp53myfkmDUKDhQsXCgDik08+CT43btw4AUD85S9/CT73888/i0aNGgnDMMRTTz0VfP6rr74SAMTNN9/s6LN3796iqqoq+Pydd94pAIiXXnop+Nwpp5wiTjnllODx5s2bBQCxcOHC4HMDBw4UzZo1E1u2bLHN3TRNx5ibN28OPtetWzdb37W88847AoB45513Ql6TQ4cOid69e4u2bduKHTt2CCGE+P7770VaWpqYNWuWre6GDRtEenp68PmqqirRpk0b0bNnT1FZWRmst2DBAgEg5HzkczrllFMEAJGfny/OP/98MW/ePMe5W8/Z+trJ5Obmil69egWPb775ZgHA8Wjfvr1yXjt37hSZmZniN7/5jQgEAsHnH3jgAQFAPPbYY44xdu3apexTNZ+8vDzx+uuv2+r+4x//ED6fT7z33nu25+fPny8AiA8++CDkGJs2bRK5ubnitNNOE36/XwgR+j4bPHiw6NGjh6ioqAg+Z5qm6Nevn+jUqVPwudrrPmDAgGB/Knr16iVyc3Nd69XSvn17MW7cOMfz8nul9j4+6qijxMGDB211p02bJjIyMsTu3buDz1VWVoq8vDxx8cUXB5+75JJLRGFhofjxxx9t7ceMGSNyc3Md/Vr55JNPBADx2muvCSGE+OyzzwQAcd5554m+ffsG65111lm2ezDU+++MM84IeQ/W1u3atavt/XTvvfcKAGLDhg1h51fLAw88IHJzc8XOnTuFEDXXsVu3bq7tXnrpJQFA3HPPPa51hRBi7ty5AoB48skng89VVVWJkpIS0bRpU1FeXi6EOHzvtWzZ0vb6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b9+Oxx9/HP/73/8wd+5cx1bZv//9b3z11Vfw+/0oKyvD8uXL8dZbb6F9+/Z4+eWXPRFdad26NaZNm4aZM2fi9NNPx1lnnRV8TU844QT84Q9/iKn/5557Dk2bNoUQAv/73//w6KOP4ueff8b8+fODq/4LLrgAzzzzTPB16t+/PwKBAL766is888wzeOONN9CnTx9cddVV2LVrF66//no89dRTtnGOPfbYsNva8+bNw4ABA9CjRw9MmDABRx11FMrKyrBq1Sr88MMP+PTTT6M6t4yMDDz//PMYMmQIBg4ciFGjRqF///7IyMjAF198gSVLlqB58+bBkNZLL70Uzz33HE4//XSMGjUK3377LZ588kmbzTVSRo8ejRkzZiA7OxuXXHKJwz59xx134J133kHfvn0xYcIEHHPMMdi9ezfWrVuHt99+2xEuKtO4cWP07t0bH330UVADAKjZAThw4AAOHDgQ0QKgd+/eePrppzF16lSccMIJaNq0KYYPH659vlZatWqFc845x/F87S/+UGUyo0ePxoYNGzBr1iz85z//wfnnnx9UAnz99dexbNmyoA7KxIkT8fDDD2P8+PFYu3YtOnTogOeeew4ffPAB5s6dG5UjaO/evQEAV111FYYOHYq0tDTbLk4o7rrrLgwbNgwlJSW45JJLgmGAubm5cVMUHD58OH7961/jxhtvxPfff4/jjjsOb775Jl566SVMmTIlqns3FOE+04855hgMGjQIvXv3RosWLbBmzRo899xzmDx5sifjJhU6IQPhwgCbNGniqBsufKZ9+/a2EIzaPleuXCkmTpwomjdvLpo2bSrGjh1rC02p7dMtDFAIIT7//HPxu9/9TuTl5Yns7GzRuXNnMX36dMeY1pCW0tJSccYZZ4hmzZrZQvDkMKRwoWjWNrUsXbpUDBgwQDRp0kQ0adJEdOnSRUyaNEls3LjRVu/BBx8UxcXFIisrS/Tp00e8++67jnMNRVlZmbjjjjvEKaecIgoLC0V6erpo3ry5OPXUU8Vzzz1nq1t7zrWPzMxMUVBQIE477TRx7733BsOOrOiE6IXigQceEF26dBEZGRkiPz9fXHHFFeLnn3+OeoxQ175JkyaipKREPPPMM476VVVVYs6cOaJbt24iKytLNG/eXPTu3VvMnDlT7N27VwghgmGUoR614arh7rNvv/1WXHjhhaKgoEBk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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "## Computing SDF for line function \n", + "num_points = 100 # for example, 100 points along each axis\n", + "\n", + "# Generate evenly spaced points between 0 and 1\n", + "# Generate evenly spaced points between 0 and 1\n", + "xmin, xmax = torch.min(control_pts[:,0]), torch.max(control_pts[:,0])\n", + "ymin, ymax = torch.min(control_pts[:,1]), torch.max(control_pts[:,1])\n", + "px = torch.linspace(xmin, xmax, num_points)\n", + "py = torch.linspace(ymin, ymax, num_points)\n", + "\n", + "# Create the meshgrid\n", + "x, y = torch.meshgrid(px, py.flip(dims=[0]), indexing='ij') # 'i\n", + "xy = torch.stack([x,y], dim=-1 ).view(-1,2).cuda()\n", + "sdf_mats = torch.zeros(xy.shape[0], c*(N-1), c*(N-1)).cuda()\n", + "\n", + "# m.bezier2DSDFtest(xy=xy, control_pts=control_pts, output=sdf).launchRaw(blockSize=(1024, 1, 1), gridSize=(1024, 1, 1))\n", + "m.bezier2DSDF(xy=xy, bcoeffs=coeffs, output=sdf_mats).launchRaw(blockSize=(256, 1, 1), gridSize=(64, 1, 1))\n", + "sdf = torch.linalg.det(sdf_mats)\n", + "sdf = torch.sgn(sdf) * torch.sqrt(torch.abs(sdf))\n", + "\n", + "\n", + "sdf_plot = sdf.view(num_points, num_points).cpu().numpy()\n", + "\n", + "plt.imshow(sdf_plot.T, cmap='inferno')\n", + "plt.title(f'Implicitized SDF of Bezier Curve with {N} Control Points')\n", + "# plt.axis('off')\n", + "plt.colorbar()\n", + "\n", + "plt.savefig(f'Bcurve_{N}_SDF_closed.png')\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "gaussian_splatting", + "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.9.12" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/bezier2d/sdf_descent.py b/examples/bezier2d/sdf_descent.py new file mode 100644 index 0000000..1396f14 --- /dev/null +++ b/examples/bezier2d/sdf_descent.py @@ -0,0 +1,143 @@ +import torch +import slangtorch +import numpy as np +import matplotlib.pyplot as plt + +N = 5 +c = 2 +m = slangtorch.loadModule('bezier.slang', defines={"NUM_CTRL_PTS": N, "DIM":c}) + +class BezierSDF_mats(torch.autograd.Function): + @staticmethod + def forward(ctx, xy, control_coeffs): + """ + xy: M,2 torch tensor on GPU, points at which SDF is to be evaluated + control_pts: + """ + # coeffs = torch.zeros_like(control_pts, dtype=torch.float).cuda() + # m.compute_coeffs(control_pts=control_pts, output=coeffs).launchRaw(blockSize=(1, 1, 1), gridSize=(1, 1, 1)) + sdf_mats = torch.zeros(xy.shape[0], c*(N-1), c*(N-1)).cuda() + kernel_with_args = m.bezier2DSDF(xy=xy, bcoeffs=control_coeffs, output=sdf_mats) + NUM_BLOCKS = 1 + xy.shape[0] // 1024 + kernel_with_args.launchRaw( + blockSize=(NUM_BLOCKS, 1, 1), + gridSize=(1024, 1, 1)) + ctx.save_for_backward(xy, control_coeffs, sdf_mats) + return sdf_mats + + @staticmethod + def backward(ctx, grad_sdf_mats): + (xy, control_coeffs, sdf_mats) = ctx.saved_tensors + grad_ctrl_coeffs = torch.zeros_like(control_coeffs) + grad_xy = torch.zeros_like(xy) + # Note: When using DiffTensorView, grad_output gets 'consumed' during the reverse-mode. + # If grad_output may be reused, consider calling grad_output = grad_output.clone() + + kernel_with_args = m.bezier2DSDF.bwd(xy=(xy, grad_xy), + bcoeffs=(control_coeffs, grad_ctrl_coeffs), + output=(sdf_mats, grad_sdf_mats)) + NUM_BLOCKS = 1 + xy.shape[0] // 1024 + kernel_with_args.launchRaw( + blockSize=(NUM_BLOCKS, 1, 1), + gridSize=(1024, 1, 1)) + + return grad_xy, grad_ctrl_coeffs + + +def compute_sdf(control_pts, num_pts, xrange=[0.0,1.0], yrange=[0.0,1.0]): + px = torch.linspace(xrange[0], xrange[1], num_pts) + py = torch.linspace(yrange[0], yrange[1], num_pts) + + # Create the meshgrid + x, y = torch.meshgrid(px, py.flip(dims=[0]), indexing='ij') # 'i + xy = torch.stack([x,y], dim=-1 ).view(-1,2).cuda() + xy.requires_grad_(True) + coeffs = torch.zeros((control_pts.shape[0],2), dtype=torch.float).cuda() + m.compute_coeffs(control_pts=control_pts, output=coeffs).launchRaw(blockSize=(4, 1, 1), gridSize=(1, 1, 1)) + sdf_mats = BezierSDF_mats.apply(xy, coeffs) + sdf = torch.linalg.det(sdf_mats) + sdf = torch.sign(sdf) * torch.sqrt(torch.abs(sdf)) + + return sdf + +def compute_sdf_pts(control_pts, xy): + """ Compute sdf for a pre-specified point / array of points + Args: + xy: (M,2) + control_pts: (N,2) + Returns: + sdf (M,1) + """ + coeffs = torch.zeros_like(control_pts, dtype=torch.float).cuda() + m.compute_coeffs(control_pts=control_pts, output=coeffs).launchRaw(blockSize=(4, 1, 1), gridSize=(1, 1, 1)) + sdf_mats = BezierSDF_mats.apply(xy, coeffs) + sdf = torch.linalg.det(sdf_mats) + sdf = torch.sign(sdf) * torch.sqrt(torch.abs(sdf)) + return sdf + + +def curve_from_coeffs(t, coeffs): + """ To check if coefficients are correct """ + output = torch.zeros(t.shape[0], coeffs.shape[1]).cuda() + for i in range(coeffs.shape[0]): + output = output + (t**i).view(-1,1) * coeffs[i].view(1,-1) + return output + + +## Problem setup. Bezier Curve control points, and an initialization. +gt_control_pts = torch.tensor([[0.0, 0.0],[0.5, 0.5], [0.0, 1.0], [0.8, 0.5], [1.0, 1.0], [1.0, 0.0]]).cuda() +gt_control_pts.requires_grad_(True) +init_x, init_y = 0.3, 0.2 #initial location of the point +xy = torch.tensor([init_x, init_y]).view(-1,2).cuda() +xy.requires_grad_(True) + + +### Experiment 1 - Gradient Descenting along SDF +# Define a custom parameter, for example, a single value parameter. +loc_param = torch.nn.Parameter(xy) + +# Use an optimizer, for example, SGD, and register the custom parameter with it. +lr_init = 0.00001 +optimizer = torch.optim.Adam([loc_param], lr=lr_init) +loc_traj = [] +for epoch in range(1000): # Assuming 1000 epochs + sdf = compute_sdf_pts(gt_control_pts, loc_param) + loss = torch.abs(torch.mean(sdf)) + loss_value = loss.item() + + optimizer.zero_grad() + for pg in optimizer.param_groups: + pg['lr'] = lr_init * loss.item() + + loss.backward() + + optimizer.step() + loc_traj.append(loc_param[0].detach().cpu().numpy()) + print(f"Epoch {epoch+1}, Loss: {loss_value}, Parameter: {[loc_param[0][0].item(), loc_param[0][1].item()]}") + + + +### Plotting +num_pts = 1000 +t = torch.linspace(0.0, 1, num_pts, dtype=torch.float).cuda() +coeffs = torch.zeros((N,c), dtype=torch.float).cuda() +m.compute_coeffs(control_pts=gt_control_pts, output=coeffs).launchRaw(blockSize=(4, 1, 1), gridSize=(1, 1, 1)) + +curve_coeffs = curve_from_coeffs(t, coeffs) + +plt.figure() +plt.plot(curve_coeffs[:,0].detach().cpu().numpy(), curve_coeffs[:,1].detach().cpu().numpy()) +for i in range(gt_control_pts.shape[0]): + plt.scatter(gt_control_pts[i][0].detach().cpu(), gt_control_pts[i][1].detach().cpu()) + + +loc_traj = np.array(loc_traj) +plt.scatter(init_x, init_y) +plt.text(init_x, init_y, 'Initialization', rotation=45) + +plt.plot(loc_traj[:,0], loc_traj[:,1]) +plt.scatter(loc_traj[-1,0], loc_traj[-1,1]) +plt.text(loc_traj[-1][0], loc_traj[-1][1], 'After Optimization', rotation=45) + +plt.title(f'Gradient Descenting Along SDF') +plt.savefig(f'sdf_descent_{N}pts.png')