3

so I have a 2D Numpy array that looks something like this:

[[1,1,1,2,2],
 [1,1,1,2,2],
 [1,2,2,2,2]]

where each number in the array represents a region. I want to generate a boolean array that shows True on positions whos adjacent elements are NOT all equal i.e. on a region boundary. So the result should look something like this:

[[False, False, True, False],
...etc

I know a simple double-for loop could do the trick but I need something faster.

P. Camilleri
  • 12,664
  • 7
  • 41
  • 76
user3396592
  • 363
  • 1
  • 5
  • 15

1 Answers1

6

Generally speaking, you're looking for an edge detection filter.

There's more than one way to handle this, but the basic idea is that you convolve a simple filter such as [-1, 1] or [-1, 0, 1] with your data. scipy.ndimage and scikit-image are good places to start for this. Keep in mind that there's more than one way to handle the general case.

However, if you're wanting the simplest possible case, you can use basic numpy operations:

import numpy as np

x = np.array([[1,1,1,2,2],
              [1,1,1,2,2],
              [1,2,2,2,2]])

edges = np.abs(np.diff(x, axis=1)) > 0

This yields:

array([[False, False,  True, False],
       [False, False,  True, False],
       [ True, False, False, False]], dtype=bool)

If you'd like the output to have the same shape as the input, you have a few different options. You could pad the input array by the left or right hand edge values:

# Pad the right-hand side with the edge values
# Use [(0, 0), (1, 0)] to pad the left instead.
xpad = np.pad(x, [(0, 0), (0, 1)], mode='edge')

edges = np.abs(np.diff(xpad, axis=1)) > 0

Which would yield:

array([[False, False,  True, False, False],
       [False, False,  True, False, False],
       [ True, False, False, False, False]], dtype=bool)

In this specific case, you could use np.gradient instead of padding the array. gradient will use a different algorithm at the edges to ensure that the array size is maintained:

dy, dx = np.gradient(x)
edges = np.abs(dx) > 0

Which yields a slightly thicker border, as it uses a slightly different algorithm than simply subtracting adjacent elements:

array([[False, False,  True,  True, False],
       [False, False,  True,  True, False],
       [ True,  True, False, False, False]], dtype=bool)

Finally, depending on what you meant by "double border", you might try a more complex edge filter than a simple one-sided difference. For example:

In [4]: import scipy.ndimage

In [5]: abs(scipy.ndimage.laplace(x)) > 0
Out[5]:
array([[False, False,  True,  True, False],
       [False,  True,  True,  True, False],
       [ True,  True,  True, False, False]], dtype=bool)
Joe Kington
  • 275,208
  • 71
  • 604
  • 463
  • Is there a way to make the output the same size as the input so that I get a sort of "double border" effect? – user3396592 Sep 01 '15 at 16:08
  • 1
    @user3396592 - There are a few different ways. You can pad the input (e.g. `np.pad`), but in this specific case, you might find it easiest to use `np.gradient` instead of `np.diff`. I'll add an example. – Joe Kington Sep 01 '15 at 16:17