# How can I check if one two-dimensional NumPy array contains a specific pattern of values inside it?

Posted on

### Question :

How can I check if one two-dimensional NumPy array contains a specific pattern of values inside it?

I have a large `NumPy.array` `field_array` and a smaller array `match_array`, both consisting of `int` values. Using the following example, how can I check if any match_array-shaped segment of `field_array` contains values that exactly correspond to the ones in `match_array`?

``````import numpy
raw_field = ( 24,  25,  26,  27,  28,  29,  30,  31,  23,
33,  34,  35,  36,  37,  38,  39,  40,  32,
-39, -38, -37, -36, -35, -34, -33, -32, -40,
-30, -29, -28, -27, -26, -25, -24, -23, -31,
-21, -20, -19, -18, -17, -16, -15, -14, -22,
-12, -11, -10,  -9,  -8,  -7,  -6,  -5, -13,
-3,  -2,  -1,   0,   1,   2,   3,   4,  -4,
6,   7,   8,   4,   5,   6,   7,  13,   5,
15,  16,  17,   8,   9,  10,  11,  22,  14)
field_array = numpy.array(raw_field, int).reshape(9,9)
match_array = numpy.arange(12).reshape(3,4)
``````

These examples ought to return `True` since the pattern described by `match_array` aligns over `[6:9,3:7]`.

Approach #1

This approach derives from `a solution` to `Implement Matlab's im2col 'sliding' in python` that was designed to `rearrange sliding blocks from a 2D array into columns`. Thus, to solve our case here, those sliding blocks from `field_array` could be stacked as columns and compared against column vector version of `match_array`.

Here’s a formal definition of the function for the rearrangement/stacking –

``````def im2col(A,BLKSZ):

# Parameters
M,N = A.shape
col_extent = N - BLKSZ + 1
row_extent = M - BLKSZ + 1

# Get Starting block indices
start_idx = np.arange(BLKSZ)[:,None]*N + np.arange(BLKSZ)

# Get offsetted indices across the height and width of input array
offset_idx = np.arange(row_extent)[:,None]*N + np.arange(col_extent)

# Get all actual indices & index into input array for final output
return np.take (A,start_idx.ravel()[:,None] + offset_idx.ravel())
``````

To solve our case, here’s the implementation based on `im2col`

``````# Get sliding blocks of shape same as match_array from field_array into columns
# Then, compare them with a column vector version of match array.
col_match = im2col(field_array,match_array.shape) == match_array.ravel()[:,None]

# Shape of output array that has field_array compared against a sliding match_array
out_shape = np.asarray(field_array.shape) - np.asarray(match_array.shape) + 1

# Now, see if all elements in a column are ONES and reshape to out_shape.
# Finally, find the position of TRUE indices
R,C = np.where(col_match.all(0).reshape(out_shape))
``````

The output for the given sample in the question would be –

``````In : R,C
Out: (array(), array())
``````

Approach #2

Given that opencv already has template matching function that does square of differences, you can employ that and look for zero differences, which would be your matching positions. So, if you have access to cv2 (opencv module), the implementation would look something like this –

``````import cv2
from cv2 import matchTemplate as cv2m

M = cv2m(field_array.astype('uint8'),match_array.astype('uint8'),cv2.TM_SQDIFF)
R,C = np.where(M==0)
``````

giving us –

``````In : R,C
Out: (array(), array())
``````

## Benchmarking

This section compares runtimes for all the approaches suggested to solve the question. The credit for the various methods listed in this section goes to their contributors.

Method definitions –

``````def seek_array(search_in, search_for, return_coords = False):
si_x, si_y = search_in.shape
sf_x, sf_y = search_for.shape
for y in xrange(si_y-sf_y+1):
for x in xrange(si_x-sf_x+1):
if numpy.array_equal(search_for, search_in[x:x+sf_x, y:y+sf_y]):
return (x,y) if return_coords else True
return None if return_coords else False

def skimage_based(field_array,match_array):
windows = view_as_windows(field_array, match_array.shape)
return (windows == match_array).all(axis=(2,3)).nonzero()

def im2col_based(field_array,match_array):
col_match = im2col(field_array,match_array.shape)==match_array.ravel()[:,None]
out_shape = np.asarray(field_array.shape) - np.asarray(match_array.shape) + 1
return np.where(col_match.all(0).reshape(out_shape))

def cv2_based(field_array,match_array):
M = cv2m(field_array.astype('uint8'),match_array.astype('uint8'),cv2.TM_SQDIFF)
return np.where(M==0)
``````

Runtime tests –

Case # 1 (Sample data from question):

``````In : field_array
Out:
array([[ 24,  25,  26,  27,  28,  29,  30,  31,  23],
[ 33,  34,  35,  36,  37,  38,  39,  40,  32],
[-39, -38, -37, -36, -35, -34, -33, -32, -40],
[-30, -29, -28, -27, -26, -25, -24, -23, -31],
[-21, -20, -19, -18, -17, -16, -15, -14, -22],
[-12, -11, -10,  -9,  -8,  -7,  -6,  -5, -13],
[ -3,  -2,  -1,   0,   1,   2,   3,   4,  -4],
[  6,   7,   8,   4,   5,   6,   7,  13,   5],
[ 15,  16,  17,   8,   9,  10,  11,  22,  14]])

In : match_array
Out:
array([[ 0,  1,  2,  3],
[ 4,  5,  6,  7],
[ 8,  9, 10, 11]])

In : %timeit seek_array(field_array, match_array, return_coords = False)
1000 loops, best of 3: 465 µs per loop

In : %timeit skimage_based(field_array,match_array)
10000 loops, best of 3: 97.9 µs per loop

In : %timeit im2col_based(field_array,match_array)
10000 loops, best of 3: 74.3 µs per loop

In : %timeit cv2_based(field_array,match_array)
10000 loops, best of 3: 30 µs per loop
``````

Case #2 (Bigger random data):

``````In : field_array = np.random.randint(0,4,(256,256))

In : match_array = field_array[100:116,100:116].copy()

In : %timeit seek_array(field_array, match_array, return_coords = False)
1 loops, best of 3: 400 ms per loop

In : %timeit skimage_based(field_array,match_array)
10 loops, best of 3: 54.3 ms per loop

In : %timeit im2col_based(field_array,match_array)
10 loops, best of 3: 125 ms per loop

In : %timeit cv2_based(field_array,match_array)
100 loops, best of 3: 4.08 ms per loop
``````

There’s no such search function built in to NumPy, but it is certainly possible to do in NumPy

As long as your arrays are not too massive*, you could use a rolling window approach:

``````from skimage.util import view_as_windows

windows = view_as_windows(field_array, match_array.shape)
``````

The function `view_as_windows` is written purely in NumPy so if you don’t have skimage you can always copy the code from here.

Then to see if the sub-array appears in the larger array, you can write:

``````>>> (windows == match_array).all(axis=(2,3)).any()
True
``````

To find the indices of where the top-left corner of the sub-array matches, you can write:

``````>>> (windows == match_array).all(axis=(2,3)).nonzero()
(array(), array())
``````

This approach should also work for arrays of higher dimensions.

*although the array `windows` takes up no additional memory (only the strides and shape are changed to create a new view of the data), writing `windows == match_array` creates a boolean array of size (7, 6, 3, 4) which is 504 bytes of memory. If you’re working with very large arrays, this approach might not be feasible.

One solution is to search the entire `search_in` array block-at-a-time (a ‘block’ being a `search_for`-shaped slice) until either a matching segment is found or the `search_for` array is exhausted. I can use it to get coordinates for the matching block, or just a `bool` result by sending `True` or `False` for the `return_coords` optional argument…

``````def seek_array(search_in, search_for, return_coords = False):
"""Searches for a contiguous instance of a 2d array `search_for` within a larger `search_in` 2d array.
If the optional argument return_coords is True, the xy coordinates of the zeroeth value of the first matching segment of search_in will be returned, or None if there is no matching segment.
If return_coords is False, a boolean will be returned.
* Both arrays must be sent as two-dimensional!"""
si_x, si_y = search_in.shape
sf_x, sf_y = search_for.shape

for y in xrange(si_y-sf_y+1):
for x in xrange(si_x-sf_x+1):
if numpy.array_equal(search_for, search_in[x:x+sf_x, y:y+sf_y]):
return (x,y) if return_coords else True  # don't forget that coordinates are transposed when viewing NumPy arrays!
return None if return_coords else False
``````

I wonder if `NumPy` doesn’t already have a function that can do the same thing, though…

To add to the answers already posted, I’d like to add one that takes into account errors due to floating point precision in case that matrices come from, let’s say, image processing for instance, where numbers are subject to floating point operations.

You can recurse the indexes of the larger matrix, searching for the smaller matrix. Then you can extract a submatrix of the larger matrix matching the size of the smaller matrix.

You have a match if the contents of both, the submatrix of ‘large’ and the ‘small’ matrix match.

The following example shows how to return the first indexes of the location in the large matrix found to match. It would be trivial to extend this function to return an array of locations found to match if that’s the intent.

``````import numpy as np

def find_submatrix(a, b):
""" Searches the first instance at which 'b' is a submatrix of 'a', iterates
rows first. Returns the indexes of a at which 'b' was found, or None if
'b' is not contained within 'a'"""
a_rows=a.shape
a_cols=a.shape

b_rows=b.shape
b_cols=b.shape

row_diff = a_rows - b_rows
col_diff = a_cols - b_cols

for idx_row in np.arange(row_diff):
for idx_col in np.arange(col_diff):
row_indexes = [idx + idx_row for idx in np.arange(b_rows)]
col_indexes = [idx + idx_col for idx in np.arange(b_cols)]

submatrix_indexes = np.ix_(row_indexes, col_indexes)
a_submatrix = a[submatrix_indexes]

are_equal = np.allclose(a_submatrix, b)  # allclose is used for floating point numbers, if they
# are close while comparing, they are considered equal.
# Useful if your matrices come from operations that produce
# floating point numbers.
# You might want to fine tune the parameters to allclose()
if (are_equal):
return[idx_col, idx_row]

return None
``````

Using the function above you can run the following example:

``````large_mtx = np.array([[1,  2, 3, 7, 4, 2, 6],
[4,  5, 6, 2, 1, 3, 11],
[10, 4, 2, 1, 3, 7, 6],
[4,  2, 1, 3, 7, 6, -3],
[5,  6, 2, 1, 3, 11, -1],
[0,  0, -1, 5, 4, -1, 2],
[10, 4, 2, 1, 3, 7, 6],
[10, 4, 2, 1, 3, 7, 6]
])

# Example 1: An intersection at column 2 and row 1 of large_mtx
small_mtx_1 = np.array([[4, 2], [2,1]])
intersect = find_submatrix(large_mtx, small_mtx_1)
print "Example 1, intersection (col,row): " + str(intersect)

# Example 2: No intersection
small_mtx_2 = np.array([[-14, 2], [2,1]])
intersect = find_submatrix(large_mtx, small_mtx_2)
print "Example 2, intersection (col,row): " + str(intersect)
``````

Which would print:

```Example 1, intersection: [1, 2]
Example 2, intersection: None
```

Here’s a solution using the `as_strided()` function from `stride_tricks` module

``````import numpy as np
from numpy.lib.stride_tricks import as_strided

# field_array (I modified it to have two matching arrays)
A = np.array([[ 24,  25,  26,  27,  28,  29,  30,  31,  23],
[ 33,   0,   1,   2,   3,  38,  39,  40,  32],
[-39,   4,   5,   6,   7, -34, -33, -32, -40],
[-30,   8,   9,  10,  11, -25, -24, -23, -31],
[-21, -20, -19, -18, -17, -16, -15, -14, -22],
[-12, -11, -10,  -9,  -8,  -7,  -6,  -5, -13],
[ -3,  -2,  -1,   0,   1,   2,   3,   4,  -4],
[  6,   7,   8,   4,   5,   6,   7,  13,   5],
[ 15,  16,  17,   8,   9,  10,  11,  22,  14]])

# match_array
B = np.arange(12).reshape(3,4)

# Window view of A
A_w = as_strided(A, shape=(A.shape - B.shape + 1,
A.shape - B.shape + 1,
B.shape, B.shape),
strides=2*A.strides).reshape(-1, B.shape, B.shape)

match = (A_w == B).all(axis=(1,2))
``````

We can also find the indices of the first element of each matching block in A

``````where = np.where(match)
ind_flat = where + (B.shape - 1)*(np.floor(where/(A.shape - B.shape + 1)).astype(int))
ind = [tuple(row) for row in np.array(np.unravel_index(ind_flat, A.shape)).T]
``````

Result

``````print(match.any())
True

print(ind)
[(1, 1), (6, 3)]
``````