python arrays numpy pattern-matching

python - ¿Cómo puedo verificar si una matriz bidimensional NumPy contiene un patrón específico de valores dentro de ella?



arrays pattern-matching (5)

Aquí hay una solución usando la función as_strided() del módulo stride_tricks

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[0] - B.shape[0] + 1, A.shape[1] - B.shape[1] + 1, B.shape[0], B.shape[1]), strides=2*A.strides).reshape(-1, B.shape[0], B.shape[1]) match = (A_w == B).all(axis=(1,2))

También podemos encontrar los índices del primer elemento de cada bloque coincidente en A

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

Resultado

print(match.any()) True print(ind) [(1, 1), (6, 3)]

Tengo un gran NumPy.array field_array y una matriz más pequeña match_array , ambos compuestos por valores int . Usando el siguiente ejemplo, ¿cómo puedo verificar si algún segmento en forma de field_array de field_array contiene valores que se corresponden exactamente con los de 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)

Estos ejemplos deberían devolver True ya que el patrón descrito por match_array alinea sobre [6:9,3:7] .


No hay tal función de búsqueda integrada en NumPy, pero ciertamente es posible hacerlo en NumPy

Siempre que sus matrices no sean demasiado masivas *, podría usar un enfoque de ventana móvil:

from skimage.util import view_as_windows windows = view_as_windows(field_array, match_array.shape)

La función view_as_windows está escrita exclusivamente en NumPy, por lo que si no tiene skimage, siempre puede copiar el código desde here .

Luego, para ver si la matriz secundaria aparece en la matriz más grande, puede escribir:

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

Para encontrar los índices de dónde coincide la esquina superior izquierda de la submatriz, puede escribir:

>>> (windows == match_array).all(axis=(2,3)).nonzero() (array([6]), array([3]))

Este enfoque también debería funcionar para matrices de dimensiones superiores.

* aunque las windows matriz no ocupan memoria adicional (solo se cambian los pasos y la forma para crear una nueva vista de los datos), escribir windows == match_array crea una matriz booleana de tamaño (7, 6, 3, 4) que es 504 bytes de memoria. Si está trabajando con matrices muy grandes, este enfoque podría no ser factible.


Para agregar a las respuestas ya publicadas, me gustaría agregar una que tenga en cuenta los errores debidos a la precisión de punto flotante en caso de que las matrices provengan, digamos, del procesamiento de imágenes, por ejemplo, donde los números están sujetos a operaciones de punto flotante.

Puede repetir los índices de la matriz más grande, buscando la matriz más pequeña. Luego puede extraer una submatriz de la matriz más grande que coincida con el tamaño de la matriz más pequeña.

Tiene una coincidencia si el contenido de ambos, la submatriz de "grande" y la matriz "pequeña" coinciden.

El siguiente ejemplo muestra cómo devolver los primeros índices de la ubicación en la matriz grande que coinciden. Sería trivial extender esta función para devolver un conjunto de ubicaciones que coincidan si esa es la intención.

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[0] a_cols=a.shape[1] b_rows=b.shape[0] b_cols=b.shape[1] 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

Usando la función anterior puede ejecutar el siguiente ejemplo:

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)

Lo que imprimiría:

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


Una solución es buscar en todo el bloque search_in array-at-a-time (un ''bloque'' es un search_for forma) hasta que se encuentre un segmento coincidente o se search_for conjunto search_for . Puedo usarlo para obtener coordenadas para el bloque coincidente, o simplemente un resultado bool enviando True o False para el argumento opcional return_coords ...

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

Sin embargo, me pregunto si NumPy aún no tiene una función que pueda hacer lo mismo ...


Enfoque n. ° 1

Este enfoque deriva de a solution para Implement Matlab''s im2col ''sliding'' in python que fue diseñado para rearrange sliding blocks from a 2D array into columns . Por lo tanto, para resolver nuestro caso aquí, esos bloques deslizantes de field_array podrían apilarse como columnas y compararse con la versión vectorial de match_array de match_array .

Aquí hay una definición formal de la función para la reorganización / apilamiento:

def im2col(A,BLKSZ): # Parameters M,N = A.shape col_extent = N - BLKSZ[1] + 1 row_extent = M - BLKSZ[0] + 1 # Get Starting block indices start_idx = np.arange(BLKSZ[0])[:,None]*N + np.arange(BLKSZ[1]) # 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())

Para resolver nuestro caso, aquí está la implementación basada en 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))

El resultado para la muestra dada en la pregunta sería:

In [151]: R,C Out[151]: (array([6]), array([3]))

Enfoque n. ° 2

Dado que opencv ya tiene una función de coincidencia de plantilla que hace un cuadrado de diferencias, puede emplear eso y buscar diferencias cero, que serían sus posiciones de coincidencia. Entonces, si tiene acceso a cv2 (módulo opencv), la implementación se vería así:

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)

dándonos -

In [204]: R,C Out[204]: (array([6]), array([3]))

Benchmarking

Esta sección compara tiempos de ejecución para todos los enfoques sugeridos para resolver la pregunta. El crédito por los diversos métodos enumerados en esta sección va a sus contribuyentes.

Definiciones de métodos -

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)

Pruebas de tiempo de ejecución

Caso # 1 (Datos de muestra de la pregunta):

In [11]: field_array Out[11]: 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 [12]: match_array Out[12]: array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]]) In [13]: %timeit seek_array(field_array, match_array, return_coords = False) 1000 loops, best of 3: 465 µs per loop In [14]: %timeit skimage_based(field_array,match_array) 10000 loops, best of 3: 97.9 µs per loop In [15]: %timeit im2col_based(field_array,match_array) 10000 loops, best of 3: 74.3 µs per loop In [16]: %timeit cv2_based(field_array,match_array) 10000 loops, best of 3: 30 µs per loop

Caso # 2 (datos aleatorios más grandes):

In [17]: field_array = np.random.randint(0,4,(256,256)) In [18]: match_array = field_array[100:116,100:116].copy() In [19]: %timeit seek_array(field_array, match_array, return_coords = False) 1 loops, best of 3: 400 ms per loop In [20]: %timeit skimage_based(field_array,match_array) 10 loops, best of 3: 54.3 ms per loop In [21]: %timeit im2col_based(field_array,match_array) 10 loops, best of 3: 125 ms per loop In [22]: %timeit cv2_based(field_array,match_array) 100 loops, best of 3: 4.08 ms per loop