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
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:
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');
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()
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()