Can matplotlib contours match pixel edges?

enter image description here

contour_rect_slow draws slingle lines at the boundaries between pixels with values 0 and 1. contour_rect is a more compact version, connecting longer lines to a single line.

Code:

import numpy as np
k = []
for s in [2103, 1936, 2247, 2987]:
    np.random.seed(s)
    k.append(np.random.randint(0, 2, size=(2,6)))
arr = np.hstack([np.vstack(k)[:, :-1], np.vstack(k).T[::-1].T ])
image = np.zeros(shape=(arr.shape[0]+2, arr.shape[1]+2))
image[1:-1, 1:-1] = arr[::1]

#    image[1, 1] = 1

import matplotlib.pyplot as plt
plt.imshow(image, interpolation="none", cmap="Blues")

def contour_rect_slow(im):
    """Clear version"""

    pad = np.pad(im, [(1, 1), (1, 1)])  # zero padding

    im0 = np.abs(np.diff(pad, n=1, axis=0))[:, 1:]
    im1 = np.abs(np.diff(pad, n=1, axis=1))[1:, :]

    lines = []

    for ii, jj in np.ndindex(im0.shape):
        if im0[ii, jj] == 1:
            lines += [([ii-.5, ii-.5], [jj-.5, jj+.5])]
        if im1[ii, jj] == 1:
            lines += [([ii-.5, ii+.5], [jj-.5, jj-.5])]

    return lines



def contour_rect(im):
    """Fast version"""

    lines = []
    pad = np.pad(im, [(1, 1), (1, 1)])  # zero padding

    im0 = np.abs(np.diff(pad, n=1, axis=0))[:, 1:]
    im1 = np.abs(np.diff(pad, n=1, axis=1))[1:, :]

    im0 = np.diff(im0, n=1, axis=1)
    starts = np.argwhere(im0 == 1)
    ends = np.argwhere(im0 == -1)
    lines += [([s[0]-.5, s[0]-.5], [s[1]+.5, e[1]+.5]) for s, e
              in zip(starts, ends)]

    im1 = np.diff(im1, n=1, axis=0).T
    starts = np.argwhere(im1 == 1)
    ends = np.argwhere(im1 == -1)
    lines += [([s[1]+.5, e[1]+.5], [s[0]-.5, s[0]-.5]) for s, e
              in zip(starts, ends)]

    return lines

lines = contour_rect(image)
for line in lines:
    plt.plot(line[1], line[0], color='r', alpha=1)

Warning: This is significantly slower then mpl.contour for large images..


If the image has a resolution of 1 pixel per unit, how would you define the "edge" of a pixel? The notion of "edge" only makes sense in a frame of increased resolution compared to the pixel itself and contour cannot draw any edges if it is working with the same resoltion as the image itself.

On the other hand, it is of course possible to increase the resolution such that the notion "edge" carries a meaning. So let's say we increase the resolution by a factor of 100 we can easily draw the edges using a contour plot.

import matplotlib.pyplot as plt
import numpy as np

k = []
for s in [2103, 1936, 2247, 2987]:
    np.random.seed(s)
    k.append(np.random.randint(0, 2, size=(2,6)))
arr = np.hstack([np.vstack(k)[:, :-1], np.vstack(k).T[::-1].T ])
image = np.zeros(shape=(arr.shape[0]+2, arr.shape[1]+2))
image[1:-1, 1:-1] = arr


f = lambda x,y: image[int(y),int(x) ]
g = np.vectorize(f)

x = np.linspace(0,image.shape[1], image.shape[1]*100)
y = np.linspace(0,image.shape[0], image.shape[0]*100)
X, Y= np.meshgrid(x[:-1],y[:-1])
Z = g(X[:-1],Y[:-1])

plt.imshow(image[::-1], origin="lower", interpolation="none", cmap="Blues")

plt.contour(Z[::-1], [0.5], colors='r', linewidths=[3], 
            extent=[0-0.5, x[:-1].max()-0.5,0-0.5, y[:-1].max()-0.5])

plt.show()

enter image description here

For comparison, we can also draw the image itself in the same plot using imshow.