A user case: given a signed distance field phi, the contour phi = 0 marks the surface of a geometry, and regions inside the geometry have phi < 0. In some case, one wants to focus on values inside the geometry and only plot regions inside the geometry, i.e., regions masked by phi < 0.
Note: directly masking the array phi causes zig-zag boundary near the contour line phi = 0, i.e., bad visualization.
I was able to write the following code with the answer here: Fill OUTSIDE of polygon | Mask array where indicies are beyond a circular boundary? the function mask_outside_polygon below is from that post. My idea is to extract and use the coordinate of the contour line for creating a polygon mask.
The code works well when the contour line does not intersect the boundary of the figure. There is no zig-zag boundary so it's a good visualization.
But when the contour intersects with the figure boundary, the contour line is fragmented into pieces and the simple code no longer works. I wonder if there is some existing feature for masking the figure, or there is some simpler method I can use. Thanks!
import numpy as np
import matplotlib.pyplot as plt
def mask_outside_polygon(poly_verts, ax=None):
"""
Plots a mask on the specified axis ("ax", defaults to plt.gca()) such that
all areas outside of the polygon specified by "poly_verts" are masked.
"poly_verts" must be a list of tuples of the verticies in the polygon in
counter-clockwise order.
Returns the matplotlib.patches.PathPatch instance plotted on the figure.
"""
import matplotlib.patches as mpatches
import matplotlib.path as mpath
if ax is None:
ax = plt.gca()
# Get current plot limits
xlim = ax.get_xlim()
ylim = ax.get_ylim()
# Verticies of the plot boundaries in clockwise order
bound_verts = [(xlim[0], ylim[0]), (xlim[0], ylim[3]),
(xlim[3], ylim[3]), (xlim[3], ylim[0]),
(xlim[0], ylim[0])]
# A series of codes (1 and 2) to tell matplotlib whether to draw a line or
# move the "pen" (So that there's no connecting line)
bound_codes = [mpath.Path.MOVETO] + (len(bound_verts) - 1) * [mpath.Path.LINETO]
poly_codes = [mpath.Path.MOVETO] + (len(poly_verts) - 1) * [mpath.Path.LINETO]
# Plot the masking patch
path = mpath.Path(bound_verts + poly_verts, bound_codes + poly_codes)
patch = mpatches.PathPatch(path, facecolor='white', edgecolor='none')
patch = ax.add_patch(patch)
# Reset the plot limits to their original extents
ax.set_xlim(xlim)
ax.set_ylim(ylim)
return patch
def main():
x = np.linspace(-1.2, 1.2, 101)
y = np.linspace(-1.2, 1.2, 101)
xx, yy = np.meshgrid(x, y)
rr = np.sqrt(xx**2 + yy**2)
psi = xx*xx - yy*yy
plt.contourf(xx,yy,psi)
if 0: # change to 1 to see the working result
cs = plt.contour(xx,yy,rr,levels=[3]) # works
else:
cs = plt.contour(xx,yy,rr,levels=[1.3]) # does not work
path = cs.collections[0].get_paths()[0]
poly_verts = path.vertices
mask_outside_polygon(poly_verts.tolist()[::-1])
plt.show()
if __name__ == '__main__':
main()