Skip to content

Hello! I'm Victor, the author of this project.
I'm looking for funding to be able to keep working on it. If you can, please consider donating or sponsoring.
Thank you! ❤️

Surfaces SDF

python
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as colors
import math
import torch
import torchlensmaker as tlm

from torch.nn.functional import normalize

import matplotlib as mpl

Tensor = torch.Tensor

# idea: Q =) F(P+tV) is a 1D function, plot it
# also plot Q' = V . grad F
# plot iterated t values


# 12 plots
# for each surface class
# plot F(x,y) : check finite everywhere
# plot grad_x F (x,y)  : check finite everywhere
# plot grad_y F (x,y)  : check finite everywhere
# plot norm(grad): check non zero
# for few values of V, plot F_grad . V
# V: (0,1), (1,0), (a,a)
#    (0, -1), (-1, 0), (-a, a), (a, -a), (-a, -a)

# check that init_t for any (P,V) does not result in F_grad . V = 0

# Create the input grid tensor
def sample_grid(xlim, ylim, N):
    x = np.linspace(xlim[0], xlim[1], N)
    y = np.linspace(ylim[0], ylim[1], N)
    X, Y = np.meshgrid(x, y)
    return X, Y, torch.tensor(np.stack((X, Y), axis=-1).reshape(-1, 2))

def surface_sdf_analysis(surface, xlim=(-10, 10), ylim=(-10, 10)):
    "Make analysis plots of f and f_grad for an implicit surface"

    # 8 subplots
    f, axes = plt.subplots(2, 4, figsize=(18, 24))

    # F, grad_x F, grad_y F, ||grad F||
    (ax_f, ax_grad_arg, ax_grad_norm, _) = axes[0]

    # axes for (F_grad . V) for 8 values of V
    axes_V = axes.flat[4:9]

    # Create the input grid tensor
    X, Y, points = sample_grid(xlim, ylim, 250)

    # The 4 V values of interest
    sq2 = math.sqrt(2) / 2
    Vs = torch.tensor([
        [0, 1],
        [1, 0],
        [sq2, sq2],
        [-sq2, sq2],
    ])
    
    # Evaluate everything
    F = surface.f(points)
    F_grad = surface.f_grad(points)
    Q_prime = [
        torch.sum(F_grad * V.expand_as(F_grad), dim=1)
        for V in Vs]

    # Plot
    norm = colors.SymLogNorm(linthresh=0.05, linscale=0.05, vmin=-20.0, vmax=20.0, base=10)
    
    ax_f.pcolormesh(X, Y, F.reshape(X.shape), cmap='RdBu_r', norm=norm, shading='auto')
    ax_f.set_title("F(x,y)")

    ax_grad_arg.pcolormesh(X, Y, np.arctan2(F_grad[:, 1], F_grad[:, 0]).reshape(X.shape), cmap='twilight')
    ax_grad_arg.set_title("arg(F(x,y))")

    ax_grad_norm.pcolormesh(X, Y, torch.linalg.norm(F_grad, dim=1).reshape(X.shape), cmap='RdBu_r', norm=norm, shading='auto')
    ax_grad_norm.set_title("||∇ F||")

    for i in range(len(Vs)):
        # F_grad . V
        axes_V[i].pcolormesh(X, Y, Q_prime[i].reshape(X.shape), cmap='RdBu_r', norm=norm, shading='auto')
        axes_V[i].set_title(f"[{Vs[i][0]:.2f} ; {Vs[i][1]:.2f}] . ∇F(x,y)")
    
    for ax in axes.flat:
        ax.set_aspect("equal")
    f.tight_layout()
    f.suptitle(f"Implicit Surface {surface.__class__.__name__}")
    
    return f, axes

f, _ = surface_sdf_analysis(tlm.Sphere(6, 3.5))
#f.savefig("sphere.png")

#F, _ = surface_sdf_analysis(tlm.DiameterBandSurface(1.0, 2.0))
#F, _ = surface_sdf_analysis(tlm.DiameterBandSurfaceSq(1.0, 2.0))
/tmp/ipykernel_31303/2810113602.py:75: DeprecationWarning: __array_wrap__ must accept context and return_scalar arguments (positionally) in the future. (Deprecated NumPy 2.0)
  ax_grad_arg.pcolormesh(X, Y, np.arctan2(F_grad[:, 1], F_grad[:, 0]).reshape(X.shape), cmap='twilight')

png