基于连通邻域的布尔交集 - NumPy / Python

Boolean intersection based on connected neighborhood - NumPy / Python

有这两个数组:

import numpy as np

arr1 = np.array([[0,0,0,0,1,0],
                 [0,0,0,0,0,0],
                 [0,0,0,0,0,0],
                 [0,1,0,0,1,0],
                 [0,0,0,0,0,0]], dtype=bool)

arr2 = np.array([[0,1,1,0,0,0],
                 [0,1,1,0,0,0],
                 [0,0,0,0,1,1],
                 [1,1,0,0,1,1],
                 [1,1,0,0,0,0]], dtype=bool)

我需要一种逻辑运算,使 returns 中被 arr1[=22= 拦截的任何连接特征 arr2 为真].结果应该是这样的:

arr3 = np.array([[0,0,0,0,0,0],
                 [0,0,0,0,0,0],
                 [0,0,0,0,1,1],
                 [1,1,0,0,1,1],
                 [1,1,0,0,0,0]], dtype=bool)

我检查了 python 和 numpy logic functions 中的逻辑操作,但似乎没有任何效果。有任何想法吗?谢谢:)

方法 #1

我们可以使用基于image-processing的标注函数对基于connected-ness的图像进行标注,然后使用masking得到我们想要的输出。要进行标记,我们可以使用 skimage.measure.label. Alternatively, we can also use scipy.ndimage.label 来获取标记的图像。因此,一种解决方案是 -

from skimage.measure import label as sklabel

def func1(arr1,arr2):
    # Get labeled image
    L = sklabel(arr2)

    # Get all present labels (this would also include zero label)
    present_labels = L[arr1]

    # Get presence of these labels in the labeled image. Remove the zero regions
    # by AND-ing with arr2.
    return np.isin(L,present_labels) & arr2

给定样本的输出 -

In [141]: func1(arr1,arr2)
Out[141]: 
array([[False, False, False, False, False, False],
       [False, False, False, False, False, False],
       [False, False, False, False,  True,  True],
       [ True,  True, False, False,  True,  True],
       [ True,  True, False, False, False, False]])

方法 #2

对于大斑点,我们应该在使用 np.isin 成为 performance-efficient 之前寻找唯一的当前标签。为了有效地获得那些独特的,我们可以利用 pandas.factorize。此外,标签部分可以通过使用 SciPy 版本来提高性能。因此,对于这种情况,另一种更有效的解决方案是 -

from scipy.ndimage import label as splabel
import pandas as pd

def func2(arr1,arr2):
    L = splabel(arr2)[0]
    pL = pd.factorize(L[arr1])[1]
    return np.isin(L,pL[pL!=0])

基准测试

我们将使用给定的示例数据,沿行和列按 1000x 扩展,同时保持相同数量的 blob。为了扩大规模,我们将使用 np.repeat -

In [147]: arr1 = arr1.repeat(1000, axis=0).repeat(1000, axis=1)

In [148]: arr2 = arr2.repeat(1000, axis=0).repeat(1000, axis=1)

In [149]: %timeit func1(arr1,arr2)
     ...: %timeit func2(arr1,arr2)
1.75 s ± 7.01 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
226 ms ± 5.99 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

如果您不想使用除 Numpy 之外的任何其他库,我建议您对矩阵进行洪水填充,这可以通过 DFS 或 BFS 来完成。 主要思想是迭代 arr1 的每个元素,如果元素为真,则将位置添加到 stack/queue。 然后对于这些位置中的每一个,您将在 arr2 上开始泛洪填充,并且对于生成的每个新位置,您必须在 arr3 中写入一个 1。 代码将是这样的:

from collections import deque
#arr3 start empty
arr3 = np.zeros((5,6))
rows = 5
cols = 6
#here i will use a deque as a queue
q = deque()
for i in range(len(arr1)):
    for j in range(len(arr1[i])):
        if arr1[i][j]:
            #for every true element in arr1
            #i add it to the queue
            q.append((i,j))
#bfs transversal
while q:
    i,j = q.popleft()
    if not arr3[i][j] and arr2[i][j]:
        #check on arr3 to avoid infinite looping
        #check on arr2 to see if it's part of a component
        arr3[i][j] = True
        #here i assume diagonals are neighbors too
        #but you can change these fors to define neighbors
        for ii in range(i-1,i+2):
            for jj in range(j-1,j+2):
                if ii==i and jj==j:
                    #we dont want to check the same element again
                    continue
                if ii<0 or ii>=rows or jj<0 or jj>=cols:
                    #we dont want to access an invalid position either
                    continue
                q.append((ii,jj))
print(arr3)