Python 查找三维numpy阵列中区域的中心坐标

Python 查找三维numpy阵列中区域的中心坐标,python,arrays,numpy,pandas,scipy,Python,Arrays,Numpy,Pandas,Scipy,我有一个大的numpy 3d阵列(10000,3,3)。我想在其中找到每个区域的中心坐标(具有相同编号的簇)。每个子阵列可以有1、2、3或4个区域 我的数组的一个子集是: largearray= array([[[1, 0, 0], [0, 0, 2], [3, 0, 2]], [[0, 0, 4], [0, 0, 4], [0, 0, 4]], [[5, 0, 0], [5, 0, 6], [0, 6, 6]], [[7,

我有一个大的numpy 3d阵列(10000,3,3)。我想在其中找到每个区域的中心坐标(具有相同编号的簇)。每个子阵列可以有1、2、3或4个区域

我的数组的一个子集是:

largearray= array([[[1, 0, 0],
    [0, 0, 2],
    [3, 0, 2]],

   [[0, 0, 4],
    [0, 0, 4],
    [0, 0, 4]],

   [[5, 0, 0],
    [5, 0, 6],
    [0, 6, 6]],

   [[7, 0, 8],
    [0, 0, 0],
    [9, 0,10]]])
我想要的输出是子阵列的位置以及代表中心的x和y坐标:

#output:
array([[ 0., 0., 0.],
[ 0., 1.5, 2.],
[ 0., 2., 0.],
[ 1., 1.,  2.],
[ 2., 0.5,  0.],
[ 2., 1.66666667, 1.66666667],
[ 3., 0., 0.],
[ 3., 0., 2.],
[ 3., 2., 0.],
[ 3., 2., 2.]])
我对其他输出开放,但像这样的东西会很棒


提前谢谢

你在找这个吗

n_clusters = 10
for i in range(1, n_clusters + 1):
    matches = np.transpose((largearray == i).nonzero())
    print "The center of cluster {} is at {}".format(i, np.mean(matches, axis=0))

使用包中的功能(免责声明:我是它的作者),可以构建一个完全矢量化的解决方案(即,无for循环):

对于大的输入,这应该更有效

请注意,如果标签在每个子阵列中不是唯一的(它们似乎在您的示例中,但没有明确说明),但您仍然希望仅取每个子阵列的平均值,您可以简单地编写以下内容:

(label, subarr), mean = npi.group_by((largearray.flatten(), idx[0])).mean(idx[1:], axis=1)

也就是说,通过子数组索引和标签的唯一元组进行分组。

这是一个完全矢量化的版本,仅使用numpy:

# the list of all the cluster ids
clusters = np.arange(1, n_clusters+1)

# convert to a boolean array, where mask[i] = largearray != clusters[i]
mask = np.rollaxis(clusters != largearray[...,np.newaxis], axis=-1)

# the coordinate of each item in the array
idx = np.indices(largearray.shape)

# broadcast (cluster_num, 1, ...) with (1, coord, ...)
mask, idx = np.broadcast_arrays(mask[:,np.newaxis], idx[np.newaxis,:])

# an array of the indices, with all the ones we don't care about masked out
idx_mask = np.ma.masked_array(idx, mask)

# flatten out the unneeded dimensions and average over them
means = idx_mask.reshape(idx_mask.shape[:2] + (-1,)).mean(axis=-1)
给予:

masked_array(data =
 [[0.0 0.0 0.0]
 [0.0 1.5 2.0]
 [0.0 2.0 0.0]
 [1.0 1.0 2.0]
 [2.0 0.5 0.0]
 [2.0 1.6666666666666667 1.6666666666666667]
 [3.0 0.0 0.0]
 [3.0 0.0 2.0]
 [3.0 2.0 0.0]
 [3.0 2.0 2.0]],
             mask =
 [[False False False]
 [False False False]
 [False False False]
 [False False False]
 [False False False]
 [False False False]
 [False False False]
 [False False False]
 [False False False]
 [False False False]],
       fill_value = 1e+20)

请注意,这还将指示缺少哪些群集,通过在此类棘手问题上设置掩码,难点在于确保操作次数最少。掩蔽或分组技术通常会增加无用的操作

简单的python方法大致如下:

def center(largearray):
    n=largearray.max()+1
    (x,y,z)=largearray.shape
    sm=np.zeros((n,3),np.float64)
    cnt=np.zeros(n,np.float64)
    for i in range(x):
        for j in range(y):
            for k in range(z):
                l=largearray[i,j,k]
                sm[l,0] += i; sm[l,1] += j; sm[l,2] += k
                cnt[l]+=1
    for l in range(n):
        if cnt[l]>0:
            for m in range(3): sm[l,m] /= cnt[l]  
        else:
            for m in range(3): sm[l,m] = -1
    return sm [1:]
以下是给定阵列上的性能(还不错):

In [16]: %timeit center(largearray)
1000 loops, best of 3: 248 µs per loop
幸运的是,这样的代码可以通过以下方式大幅加速:


您可能还想查看处理与此相关的问题的包。[免责声明:我是合著者]。它应该比
numpy index
(另一个答案中提到的包)更快,因为它使用
bincount
,而不是
argsort
reduceat

但是,这里的任务非常简单,您可以直接使用
bincount

s0, s1, s2 = a.shape

group_counts = np.bincount(a.ravel())

idx = np.broadcast_to(np.arange(s0).reshape([s0, 1, 1]), [s0,s1,s2])
group_sum_0 = np.bincount(a.ravel(), idx.ravel()) 

idx = np.broadcast_to(np.arange(s1).reshape([1, s1, 1]), [s0,s1,s2])
group_sum_1 = np.bincount(a.ravel(), idx.ravel()) 

idx = np.broadcast_to(np.arange(s2).reshape([1, 1, s2]), [s0,s1,s2])
group_sum_2 = np.bincount(a.ravel(), idx.ravel()) 

group_mean = np.vstack((group_sum_0, group_sum_1, group_sum_2)) / group_counts

group_mean.T[1:] # this is the output you show in the question


或者,如果您想“作弊”,您可以使用来自scipy的函数之一。

如果您事先知道区域的数量,则处理此问题的标准技术是;这些是支持k-means的Python库,例如scikit。一个快速的谷歌显示有一个模块,它的k-意思是熊猫:,但它只是Python2,它是一个4D数组。什么是区域?什么是索引?@PM2Ring谢谢!我来看看tool@B.M.它是3D的,我在问题中给出的尺寸是错误的,我会编辑它。区域是具有相同值的单元格群集,索引是2d子数组在3d ArrayTanks中的位置!这正是我需要的。如果没有for循环,这也是可能的吗?@WilmarvanOmmeren:那么为什么你的预期输出与我的完全不同呢?我不知道如何解决这个问题。很抱歉,但这正是我想要的。我更新了预期的输出。哇,这太快了。再次感谢你的帮助!不是for循环吗?那里有一个for循环,但这不是本例中遵循的代码路径。该码路仅在给定自定义缩减函数时使用;所有标准还原均通过ufunc.reduceeat处理;当然,它也做for循环,但是用C而不是python。这比我的回答要快@eelcoogendoorn,注意这个解决方案在集群数量上有二次时间复杂度。@Eelco:为什么?在我看来是线性的。这不是O(n_clusters*largearray.size)吗?是的;但是集群的数量似乎与数组大小成正比那么你的解决方案的复杂性是什么呢?在最坏的情况下,但是由于这里分组的对象已经基本上按顺序排列了,我希望它也是非常线性的,我不敢相信我没有想到使用
scipy.ndimage.measurements.center\u of\u mass
。这是最好的答案,我想说是的,ndimage实现看起来相当高效。实际上,在我的基准测试中,ufunc.reduceat似乎表现得非常出色;与ufunc.at不同,ufunc.at的速度确实非常慢。这当然是一个主观问题,但在我看来,在这些棘手的问题上,困难在于意图的清晰性和可维护性;并不是说表现从来都不重要;但是,如果最佳时间复杂度的完全矢量化解决方案还不够,那么您可能不应该首先使用numpy。
In [16]: %timeit center(largearray)
1000 loops, best of 3: 248 µs per loop
In [17]: center2=numba.jit(center)

In [18]: %timeit center2(largearray)
100000 loops, best of 3: 3.29 µs per loop
s0, s1, s2 = a.shape

group_counts = np.bincount(a.ravel())

idx = np.broadcast_to(np.arange(s0).reshape([s0, 1, 1]), [s0,s1,s2])
group_sum_0 = np.bincount(a.ravel(), idx.ravel()) 

idx = np.broadcast_to(np.arange(s1).reshape([1, s1, 1]), [s0,s1,s2])
group_sum_1 = np.bincount(a.ravel(), idx.ravel()) 

idx = np.broadcast_to(np.arange(s2).reshape([1, 1, s2]), [s0,s1,s2])
group_sum_2 = np.bincount(a.ravel(), idx.ravel()) 

group_mean = np.vstack((group_sum_0, group_sum_1, group_sum_2)) / group_counts

group_mean.T[1:] # this is the output you show in the question