0

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.

good

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!

bad

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()
Taozi
  • 363
  • 5
  • 12

0 Answers0