• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mkofler96 / DeepSDFStruct / 19530884105

20 Nov 2025 08:46AM UTC coverage: 82.144% (-0.09%) from 82.235%
19530884105

push

github

mkofler96
Address PR review feedback: remove hardcoded version, fix cap_border_dict description, add mmapy reference, update fallback version to 'latest', remove deformation mapping bullet

Co-authored-by: mkofler96 <18218171+mkofler96@users.noreply.github.com>

324 of 385 branches covered (84.16%)

Branch coverage included in aggregate %.

2749 of 3356 relevant lines covered (81.91%)

0.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

8.33
DeepSDFStruct/plotting.py
1
"""
2
Visualization and Plotting Utilities
3
=====================================
4

5
This module provides utilities for visualizing SDF representations, particularly
6
for creating 2D cross-sectional views of 3D signed distance functions.
7

8
Functions
9
---------
10
plot_slice
11
    Create a contour plot of an SDF on a 2D plane slice.
12
generate_plane_points
13
    Generate a regular grid of points on a plane in 3D space.
14

15
The primary use case is visualizing SDF values on axis-aligned planes
16
to understand the geometry and verify correctness during development
17
and debugging.
18
"""
19

20
import matplotlib.pyplot as plt
1✔
21
import numpy as np
1✔
22
import torch
1✔
23

24

25
def plot_slice(
1✔
26
    fun,
27
    origin=(0, 0, 0),
28
    normal=(0, 0, 1),
29
    res=(100, 100),
30
    ax=None,
31
    xlim=(-1, 1),
32
    ylim=(-1, 1),
33
    clim=(-1, 1),
34
    cmap="seismic",
35
    show_zero_level=True,
36
):
37
    """Plot a 2D slice through an SDF as a contour plot.
38
    
39
    This function evaluates an SDF on a planar grid and visualizes the
40
    signed distance values using a color map. The zero level set (the
41
    actual surface) can be highlighted with a contour line.
42
    
43
    Parameters
44
    ----------
45
    fun : callable
46
        The SDF function to visualize. Should accept a torch.Tensor
47
        of shape (N, 3) and return distances of shape (N, 1).
48
    origin : tuple of float, default (0, 0, 0)
49
        A point on the slice plane.
50
    normal : tuple of float, default (0, 0, 1)
51
        Normal vector of the slice plane. Currently supports only
52
        axis-aligned planes: (1,0,0), (0,1,0), or (0,0,1).
53
    res : tuple of int, default (100, 100)
54
        Resolution of the slice grid (num_points_u, num_points_v).
55
    ax : matplotlib.axes.Axes, optional
56
        Axes to plot on. If None, creates a new figure.
57
    xlim : tuple of float, default (-1, 1)
58
        Range along the first plane axis.
59
    ylim : tuple of float, default (-1, 1)
60
        Range along the second plane axis.
61
    clim : tuple of float, default (-1, 1)
62
        Color map limits for distance values.
63
    cmap : str, default 'seismic'
64
        Matplotlib colormap name.
65
    show_zero_level : bool, default True
66
        If True, draws a black contour line at distance=0 (the surface).
67
        
68
    Returns
69
    -------
70
    fig, ax : matplotlib.figure.Figure, matplotlib.axes.Axes
71
        Only returned if ax was None (i.e., a new figure was created).
72
        
73
    Examples
74
    --------
75
    >>> from DeepSDFStruct.sdf_primitives import SphereSDF
76
    >>> from DeepSDFStruct.plotting import plot_slice
77
    >>> import matplotlib.pyplot as plt
78
    >>> 
79
    >>> # Create a sphere
80
    >>> sphere = SphereSDF(center=[0, 0, 0], radius=0.5)
81
    >>> 
82
    >>> # Plot XY slice at z=0
83
    >>> fig, ax = plot_slice(
84
    ...     sphere,
85
    ...     origin=(0, 0, 0),
86
    ...     normal=(0, 0, 1),
87
    ...     res=(200, 200)
88
    ... )
89
    >>> plt.title("XY Slice of Sphere")
90
    >>> plt.show()
91
    
92
    Notes
93
    -----
94
    The 'seismic' colormap is well-suited for SDFs as it uses blue for
95
    negative (inside) and red for positive (outside), with white near zero.
96
    """
97
    plt_show = False
×
98
    if ax is None:
×
99
        fig, ax = plt.subplots()
×
100
        plt_show = True
×
101

102
    points, u, v = generate_plane_points(origin, normal, res, xlim, ylim)
×
103

104
    points = torch.from_numpy(points).to(torch.float32)
×
105
    sdf = fun(points).reshape((res[0], res[1]))
×
106
    X = u.reshape((res[0], res[1]))
×
107
    Y = v.reshape((res[0], res[1]))
×
108
    if isinstance(sdf, torch.Tensor):
×
109
        sdf = sdf.detach().cpu().numpy()
×
110

111
    # cbar = ax[0].scatter(X, Y, c=sdf, cmap="seismic")c
112
    cbar = ax.contourf(X, Y, sdf, cmap=cmap, levels=10)
×
113
    if show_zero_level:
×
114
        ax.contour(X, Y, sdf, levels=[0], colors="black", linewidths=0.5)
×
115
    cbar.set_clim(clim[0], clim[1])
×
116
    ax.set_xticks([])
×
117
    ax.set_yticks([])
×
118
    ax.set_aspect(1)
×
119
    if plt_show:
×
120
        plt.show()
×
121
        return fig, ax
×
122

123

124
def generate_plane_points(origin, normal, res, xlim, ylim):
1✔
125
    """Generate evenly spaced points on a plane in 3D space.
126
    
127
    Creates a regular grid of points on a plane defined by a point and normal
128
    vector. The grid is axis-aligned in the plane's local coordinate system.
129
    
130
    Parameters
131
    ----------
132
    origin : array-like of shape (3,)
133
        A point on the plane (3D vector).
134
    normal : array-like of shape (3,)
135
        Normal vector of the plane (3D vector). Currently supports only
136
        axis-aligned normals: [1,0,0], [0,1,0], or [0,0,1].
137
    res : tuple of int
138
        Grid resolution (num_points_u, num_points_v).
139
    xlim : tuple of float
140
        Range along the first plane axis (umin, umax).
141
    ylim : tuple of float
142
        Range along the second plane axis (vmin, vmax).
143
        
144
    Returns
145
    -------
146
    points : np.ndarray of shape (num_points_u * num_points_v, 3)
147
        3D coordinates of grid points.
148
    u : np.ndarray of shape (num_points_u * num_points_v,)
149
        First plane coordinate for each point.
150
    v : np.ndarray of shape (num_points_u * num_points_v,)
151
        Second plane coordinate for each point.
152
        
153
    Raises
154
    ------
155
    NotImplementedError
156
        If normal is not axis-aligned.
157
        
158
    Examples
159
    --------
160
    >>> from DeepSDFStruct.plotting import generate_plane_points
161
    >>> import numpy as np
162
    >>> 
163
    >>> # Generate points on XY plane at z=0.5
164
    >>> points, u, v = generate_plane_points(
165
    ...     origin=[0, 0, 0.5],
166
    ...     normal=[0, 0, 1],
167
    ...     res=(10, 10),
168
    ...     xlim=(-1, 1),
169
    ...     ylim=(-1, 1)
170
    ... )
171
    >>> print(points.shape)  # (100, 3)
172
    >>> print(np.allclose(points[:, 2], 0.5))  # True (all on z=0.5 plane)
173
    
174
    Notes
175
    -----
176
    The function determines two orthogonal axes (u and v) in the plane
177
    based on the normal vector. For axis-aligned planes:
178
    - Normal [0,0,1] (XY plane): u=[1,0,0], v=[0,1,0]
179
    - Normal [0,1,0] (XZ plane): u=[1,0,0], v=[0,0,1]
180
    - Normal [1,0,0] (YZ plane): u=[0,1,0], v=[0,0,1]
181
    """
182
    normal = np.array(normal)
×
183
    normal = normal / np.linalg.norm(normal)
×
184
    origin = np.array(origin)
×
185
    # Find two orthogonal vectors to the normal that lie on the plane (u and v axes)
186
    if np.allclose(normal, [0, 0, 1]):  # Special case when the normal is along z-axis
×
187
        u = np.array([1, 0, 0])
×
188
        v = np.array([0, 1, 0])
×
189
    elif np.allclose(normal, [0, 1, 0]):  # Special case when the normal is along z-axis
×
190
        u = np.array([1, 0, 0])
×
191
        v = np.array([0, 0, 1])
×
192
    elif np.allclose(normal, [1, 0, 0]):  # Special case when the normal is along z-axis
×
193
        u = np.array([0, 1, 0])
×
194
        v = np.array([0, 0, 1])
×
195
    else:
196
        raise NotImplementedError(
×
197
            "Normal vector other than [1,0,0], [0,1,0] and [0,0,1] not supported yet."
198
        )
199

200
    # Create grid points in 2D space (u-v plane)
201
    u_coords = np.linspace(xlim[0], xlim[1], res[0])
×
202
    v_coords = np.linspace(ylim[0], ylim[1], res[1])
×
203

204
    points = []
×
205
    u_exp = []
×
206
    v_exp = []
×
207
    for u_val in u_coords:
×
208
        for v_val in v_coords:
×
209
            point = origin + u_val * u + v_val * v
×
210
            u_exp.append(u_val)
×
211
            v_exp.append(v_val)
×
212
            points.append(point)
×
213

214
    return np.array(points), np.array(u_exp), np.array(v_exp)
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc