How to go from a contour to an image mask in with Matplotlib
Asked Answered
L

3

7

If I plot a 2D array and contour it, I can get the access to the segmentation map, via cs = plt.contour(...); cs.allsegs but it's parameterized as a line. I'd like a segmap boolean mask of what's interior to the line, so I can, say, quickly sum everything within that contour.

Many thanks!

Liggett answered 7/6, 2013 at 2:46 Comment(2)
Don't you have access to the original data producing the contour plot? You should then be able to produce the desired boolean mask by doing data > threshold, where threshold is the value at the contour line.Derick
This would work in certain situations, but you can have multiple contour lines for the same value if for example there are multiple peaks in the data. Using a threshold would select the data within all those contour lines.Hadhramaut
H
7

I dont think there is a really easy way, mainly because you want to mix raster and vector data. Matplotlib paths fortunately have a way to check if a point is within the path, doing this for all pixels will make a mask, but i think this method can get very slow for large datasets.

import matplotlib.patches as patches
from matplotlib.nxutils import points_inside_poly
import matplotlib.pyplot as plt
import numpy as np

# generate some data
X, Y = np.meshgrid(np.arange(-3.0, 3.0, 0.025), np.arange(-3.0, 3.0, 0.025))
Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
# difference of Gaussians
Z = 10.0 * (Z2 - Z1)

fig, axs = plt.subplots(1,2, figsize=(12,6), subplot_kw={'xticks': [], 'yticks': [], 'frameon': False})

# create a normal contour plot
axs[0].set_title('Standard contour plot')
im = axs[0].imshow(Z, cmap=plt.cm.Greys_r)
cs = axs[0].contour(Z, np.arange(-3, 4, .5), linewidths=2, colors='red', linestyles='solid')

# get the path from 1 of the contour lines
verts = cs.collections[7].get_paths()[0]

# highlight the selected contour with yellow
axs[0].add_patch(patches.PathPatch(verts, facecolor='none', ec='yellow', lw=2, zorder=50))

# make a mask from it with the dimensions of Z
mask = verts.contains_points(list(np.ndindex(Z.shape)))
mask = mask.reshape(Z.shape).T

axs[1].set_title('Mask of everything within one contour line')
axs[1].imshow(mask, cmap=plt.cm.Greys_r, interpolation='none')

# get the sum of everything within the contour
# the mask is inverted because everything within the contour should not be masked
print np.ma.MaskedArray(Z, mask=~mask).sum()

Note that contour lines which 'leave' the plot at different edges by default wont make a path which follows these edges. These lines would need some additional processing.

enter image description here

Hadhramaut answered 7/6, 2013 at 10:30 Comment(2)
This is a great answer. I ended up instead using scipy.ndimage.measurements.label, which essentially makes the contour masks I need. Using another package is of course what I was hoping not to do, but thank you anyway!Liggett
Do you think you could share your approach on the post please?Pond
H
6

Another way, perhaps more intuitive, is the binary_fill_holes function from scipy.ndimage.

import numpy as np
import scipy


image = np.zeros((512, 512))
image[contour1[:, 0], contour1[:, 1]] = 1
masked_image = scipy.ndimage.morphology.binary_fill_holes(image)
```
Hesperides answered 15/1, 2018 at 10:23 Comment(0)
M
0

Here is how to create a filled polygon from contours and create a binary mask using OpenCV

import cv2
import numpy as np
import matplotlib.pyplot as plt

mask = np.zeros((10,10,3), dtype=np.uint8)
# polygon's coordinates
coords = np.array([[3,3],[3,6],[6,6],[6,3]])

cv2.drawContours(mask, [coords], contourIdx=-1, color=(1,1,1), thickness=-1)
bin_mask = mask[:,:,0].astype(np.float32)
plt.imshow(bin_mask, cmap='gray')
  • contourIdx=-1 - draw all contours

  • color=(1,1,1) - a number from 0 to 255 for each channel; since we generate a binary mask it is set to 1

  • thickness=-1 - fills the polygon

enter image description here

Mattland answered 22/11, 2022 at 14:10 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.