Fill OUTSIDE of polygon | Mask array where indicies are beyond a circular boundary?

前端 未结 2 416
栀梦
栀梦 2020-12-18 22:52

I use plot(x,y,\'r\') to plot a red circle. x and y are arrays such that when paired as (x,y) and plotted, all points form a circle-line.

fill(x,y

相关标签:
2条回答
  • 2020-12-18 23:36

    Note: This answer uses MATLAB syntax, since the question was originally tagged as such. However, even if you're using matplotlib in Python the concept should be the same even if the syntax is slightly different.

    One option you have is to make a polygon that appears to have a hole in it, but really just has two of its edges wrapping around an empty space and touching. You can do this by creating a set of x and y coordinates that track around the edge of the circle, then track from the circle edge to the edge of a bounding square, then track around the edge of that square and back to the circle edge along the same line. Here's an example with a unit circle and a 4 by 4 square centered at the origin:

    theta = linspace(0,2*pi,100);      %# A vector of 100 angles from 0 to 2*pi
    xCircle = cos(theta);              %# x coordinates for circle
    yCircle = sin(theta);              %# y coordinates for circle
    xSquare = [2 2 -2 -2 2 2];         %# x coordinates for square
    ySquare = [0 -2 -2 2 2 0];         %# y coordinates for square
    hp = fill([xCircle xSquare],...    %# Plot the filled polygon
              [yCircle ySquare],'r');
    axis equal                         %# Make axes tick marks equal in size
    

    And here is the figure you should see:

    alt text

    Notice the line on the right joining the edges of the circle and square. This is where two edges of the red polygon meet and touch each other. If you don't want the edge lines to be visible, you can change their color to be the same as the fill color for the polygon like so:

    set(hp,'EdgeColor','r');
    
    0 讨论(0)
  • 2020-12-18 23:48

    All I am really needing to do is mask out numbers in a 2d array that are located beyond this boundary of the circle created with x and y, such that when the 2D array is viewed as a color plot, or contour, inside the circle will be the image, and outside will be white-ed out.

    You have two options:

    First, you could use a masked array for the images. This is more complicated but a bit more failsafe. To mask an array outside of a circle, generate a distance map from the center point, and mask where distance is greater than the radius.

    The easier option is to clip the areas ouside of the patch with im.set_clip_path() after you've plotted the image.

    See this example from the matplotlib gallery. Unfortunately, with some axes (non-cartesian axes) this can be a bit glitchy, in my experience. In every other case it should work perfectly, though.

    Edit: Incidentally, this is how to do what you originally asked: plot a polygon with a hole inside. If you just want to mask an image, though, you're better off with either of the two options above.

    Edit2: Just to give a quick example of both ways...

    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib.patches as patches
    
    def main():
        # Generate some random data
        nx, ny = 100, 100
        data = np.random.random((ny,nx))
    
        # Define a circle in the center of the data with a radius of 20 pixels
        radius = 20
        center_x = nx // 2
        center_y = ny // 2
    
        plot_masked(data, center_x, center_y, radius)
        plot_clipped(data, center_x, center_y, radius)
        plt.show()
    
    def plot_masked(data, center_x, center_y, radius):
        """Plots the image masked outside of a circle using masked arrays"""
        # Calculate the distance from the center of the circle
        ny, nx = data.shape
        ix, iy = np.meshgrid(np.arange(nx), np.arange(ny))
        distance = np.sqrt((ix - center_x)**2 + (iy - center_y)**2)
    
        # Mask portions of the data array outside of the circle
        data = np.ma.masked_where(distance > radius, data)
    
        # Plot
        plt.figure()
        plt.imshow(data)
        plt.title('Masked Array')
    
    def plot_clipped(data, center_x, center_y, radius):
        """Plots the image clipped outside of a circle by using a clip path"""
        fig = plt.figure()
        ax = fig.add_subplot(111)
    
        # Make a circle
        circ = patches.Circle((center_x, center_y), radius, facecolor='none')
        ax.add_patch(circ) # Plot the outline
    
        # Plot the clipped image
        im = ax.imshow(data, clip_path=circ, clip_on=True)
    
        plt.title('Clipped Array')
    
    main()
    

    enter image description here enter image description here

    Edit 2: Plotting a mask polygon over the original plot: Here's a bit more detail on how to plot a polygon that masks everything outside of it over the current plot. Apparently, there isn't a better way to clip contour plots (That I could find, anyway...).

    import numpy as np
    import matplotlib.pyplot as plt
    
    def main():
        # Contour some regular (fake) data
        grid = np.arange(100).reshape((10,10))
        plt.contourf(grid)
    
        # Verticies of the clipping polygon in counter-clockwise order
        #  (A triange, in this case)
        poly_verts = [(2, 2), (5, 2.5), (6, 8), (2, 2)]
    
        mask_outside_polygon(poly_verts)
    
        plt.show()
    
    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[1]), 
                       (xlim[1], ylim[1]), (xlim[1], 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
    
    if __name__ == '__main__':
        main()
    

    Clipped contour plot

    0 讨论(0)
提交回复
热议问题