在2D或3D阵列中快速查找相邻数字的方法

时间:2018-01-04 22:19:04

标签: python arrays performance scipy scikit-image

我有跟随2D数组

regions = array([[3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4],
                 [3, 3, 3, 3, 8, 8, 8, 8, 8, 4, 4, 4, 4],
                 [3, 3, 3, 3, 8, 8, 8, 8, 8, 4, 4, 4, 4],
                 [3, 3, 3, 3, 8, 8, 8, 8, 8, 4, 4, 4, 4],
                 [3, 6, 6, 6, 8, 8, 8, 8, 8, 7, 7, 7, 4],
                 [3, 6, 6, 6, 8, 8, 8, 8, 8, 7, 7, 7, 4],
                 [3, 6, 6, 6, 6, 8, 8, 8, 7, 7, 7, 7, 4],
                 [3, 6, 6, 6, 6, 2, 2, 2, 7, 7, 7, 7, 4],
                 [5, 6, 6, 6, 6, 2, 2, 2, 7, 7, 7, 7, 1],
                 [5, 6, 6, 6, 6, 2, 2, 2, 7, 7, 7, 7, 1],
                 [5, 6, 6, 6, 6, 2, 2, 2, 7, 7, 7, 7, 1],
                 [5, 5, 5, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1],
                 [5, 5, 5, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1]])

我想查找所有个别号码的相邻号码。例如,34,5,6,8的后续行为。目前,我正在使用for loop按照下面提到的代码进行此练习。

numbers = scipy.unique(regions)
for i in numbers:
    index = i-1
    slices = scipy.ndimage.find_objects(regions)
    sub_im = regions[slices[index]]
    im = sub_im == i
    neighbors = scipy.ndimage.binary_dilation(input=im, structure=disk(1))
    neighbors = neighbors*sub_im
    neighbors_list = scipy.unique(neighbors)[1:] - 1
    print (neighbors_list)

问题是我不想用于循环,因为我的区域数组大约是数百万。如果没有for循环,有没有快速解决这个问题的方法?

2 个答案:

答案 0 :(得分:1)

您可以使用=TEXTJOIN("",FALSE,B3:ALM3) numpynp.roll()使用np.where()完成此操作。这个想法是将每个条目追加到它的四个邻居,然后将这些五个列表中的唯一成员(条目及其四个邻居)拉出来。这是一个应该澄清的实现:

np.unique()

这会产生# make a 3d array with the matrix entry and its four neighbors neighbor_array = np.array([regions, np.roll(regions,+1,axis=0), np.roll(regions,-1,axis=0), np.roll(regions,+1,axis=1), np.roll(regions,-1,axis=1), ]) # if you want neighbors to include wraparounds, use above; if not, prune neighbor_array_pruned = neighbor_array[:,1:-1,1:-1] # reshape to a 2d array entries x neighbors neighbor_list = np.reshape(neighbor_array_pruned,[5,-1]).T # get uniques into a dictionary neighbor_dict = {} for num in np.unique(regions): neighbor_dict[num] = np.unique(neighbor_list[np.where(neighbor_list[:,0]==num)])

neighbor_dict

注意我修剪了边缘;如果你想包括环绕邻居或做一些更细微的事情,你可以详细说明修剪线。

答案 1 :(得分:1)

我建议使用类似这种方法的东西,它在矩阵元素的数量上是线性的(考虑到你不能支付少于扫描至少一次所有矩阵的元素)。基本上,我将相邻数字列表分组,然后在此基础上计算邻居条目。

import numpy as np
import collections

def adj_to_neighbor_dict(adj):
    assert hasattr(adj, "__iter__")

    neighbor_dict = collections.defaultdict(lambda: set())
    for i,j in adj:
        if i == j:
            continue
        neighbor_dict[i].add(j)
        neighbor_dict[j].add(i)
    return neighbor_dict

def get_neighbors_2d(npmatrix):
    assert len(npmatrix.shape) == 2
    I, J = range(npmatrix.shape[0]-1), range(npmatrix.shape[1]-1)
    adj_set = set(
        (npmatrix[i,j], npmatrix[i+1,j])
        for i in I
        for j in J
    ) | set(
        (npmatrix[i,j], npmatrix[i,j+1])
        for i in I
        for j in J
    )
    return adj_to_neighbor_dict(adj_set)

我在一个包含10个不同数字(np.random.randint(0,10,(1000,1000)))的1M元素的随机矩阵上进行了测试,花了1.61秒。

更新:

相同的方法可用于3D阵列。执行此操作的代码如下:

def get_neighbors_3d(npmatrix):
    assert len(npmatrix.shape) == 3
    I, J, K = range(npmatrix.shape[0]-1), range(npmatrix.shape[1]-1), range(npmatrix.shape[2]-1)
    adj_set = set(
        (npmatrix[i,j,k], npmatrix[i+1,j,k])
        for i in I
        for j in J
        for k in K
    ) | set(
        (npmatrix[i,j,k], npmatrix[i,j+1,k])
        for i in I
        for j in J
        for k in K
    ) | set(
        (npmatrix[i,j,k], npmatrix[i,j,k+1])
        for i in I
        for j in J
        for k in K
    )
    return adj_to_neighbor_dict(adj_set)

我在一个包含10个不同数字(np.random.randint(0,10,(100,100,100)))的1M元素的随机矩阵上测试了这个函数,花了2.60秒。

我还建议一个通用的解决方案,它不是基于np.array的形状:

def npmatrix_shape_iter(shape):
    num_dimensions = len(shape)
    last_dimension = num_dimensions-1
    coord = [0] * num_dimensions
    while True:
        yield tuple(coord)
        coord[last_dimension] += 1
        for i in xrange(last_dimension, 0, -1):
            if coord[i] < shape[i]:
                break
            coord[i] = 0
            coord[i-1] += 1
        # end condition: all the dimensions have been explored
        if coord[0] >= shape[0]:
            break

def adj_position_iter(tpl):
    new_tpl = list(tpl)
    for i in xrange(len(tpl)):
        new_tpl[i] += 1
        yield tuple(new_tpl)
        new_tpl[i] -= 1

def get_neighbors(npmatrix):
    neighbors = set(
        (npmatrix[tpl], npmatrix[adj_tpl])
        for tpl in npmatrix_shape_iter(tuple(np.array(npmatrix.shape)-1))
        for adj_tpl in adj_position_iter(tpl)
    )
    neighbor_dict = collections.defaultdict(lambda: [])
    for i,j in neighbors:
        if i == j:
            continue
        neighbor_dict[i].append(j)
        neighbor_dict[j].append(i)
    return neighbor_dict

由于这个功能很普遍,所以需要做更多的工作,实际上它比以前的工作慢。在第一次测试的相同2D矩阵上,它需要6.71秒,而在第二次测试的3D矩阵上需要7.96秒。

更新2:

我用更快(也希望也更容易)的版本更新了2D和3D矩阵的代码。如果没有关于矩阵内部数字位置的其他限制,则无法在不扫描所有矩阵单元的情况下发现所有颜色:使用for循环,我们当前正在执行此操作。您可以用来完成任务的每个功能都将在内部扫描整个矩阵(至少)。顺便说一下,我并不是说这是最快的解决方案,因为一个替代解决方案可以是使用cython或本地numpy代码(如果存在的话)。