Python:如何逐行填充数组?

时间:2011-07-08 14:48:29

标签: python numpy fill

我有一个我无法解决的numpy问题。 我有3D数组(x,y,z)填充0和1。 例如,z轴上的一个切片:

array([[1, 0, 1, 0, 1, 1, 0, 0],
       [0, 0, 1, 1, 0, 1, 1, 0],
       [1, 0, 1, 1, 0, 0, 0, 1],
       [0, 0, 0, 0, 0, 0, 0, 0],
       [1, 1, 1, 0, 1, 0, 0, 1],
       [1, 0, 0, 0, 0, 1, 0, 1],
       [0, 0, 0, 0, 1, 0, 0, 0],
       [0, 0, 1, 0, 1, 1, 0, 1]])

我想要这个结果:

array([[1, 1, 1, 1, 1, 1, 0, 0],
       [0, 0, 1, 1, 1, 1, 1, 0],
       [1, 1, 1, 1, 1, 1, 1, 1],
       [0, 0, 0, 0, 0, 0, 0, 0],
       [1, 1, 1, 1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1, 1, 1, 1],
       [0, 0, 0, 0, 1, 0, 0, 0],
       [0, 0, 1, 1, 1, 1, 1, 1]])

也就是说,我想要为每个切片z做的是逐行扫描从左到右,从左到右(x轴),第一次我有一个1我要填充其余的切片与一行。

有没有一种有效的计算方法?

非常感谢。

Nico!

4 个答案:

答案 0 :(得分:4)

逐个访问NumPy数组元素效率不高。你可以用简单的Python列表做得更好。它们还有一个index方法,可以搜索列表中值的第一个条目。

from numpy import *

a = array([[1, 0, 1, 0, 1, 1, 0, 0],
       [0, 0, 1, 1, 0, 1, 1, 0],
       [1, 0, 1, 1, 0, 0, 0, 1],
       [0, 1, 0, 0, 1, 0, 1, 0],
       [1, 1, 1, 0, 1, 0, 0, 1],
       [1, 0, 0, 0, 0, 1, 0, 1],
       [0, 0, 0, 0, 1, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0, 0],
       [0, 0, 1, 0, 1, 1, 0, 1]])

def idx_front(ln):
    try:
        return list(ln).index(1)
    except ValueError:
        return len(ln) # an index beyond line end

def idx_back(ln):
    try:
        return len(ln) - list(reversed(ln)).index(1) - 1
    except ValueError:
        return len(ln) # an index beyond line end

ranges = [ (idx_front(ln), idx_back(ln)) for ln in a ]
for ln, (lo,hi) in zip(a, ranges):
    ln[lo:hi] = 1  # attention: destructive update in-place

print "ranges =", ranges
print a

输出:

ranges = [(0, 5), (2, 6), (0, 7), (1, 6), (0, 7), (0, 7), (4, 4), (8, 8), (2, 7)]
[[1 1 1 1 1 1 0 0]
 [0 0 1 1 1 1 1 0]
 [1 1 1 1 1 1 1 1]
 [0 1 1 1 1 1 1 0]
 [1 1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1 1]
 [0 0 0 0 1 0 0 0]
 [0 0 0 0 0 0 0 0]
 [0 0 1 1 1 1 1 1]]

答案 1 :(得分:4)

实际上,这是一项基本的binary image morphology操作。

您可以使用scipy.ndimage.morphology.binary_fill_holes

一步完成整个3D阵列

你只需要一个稍微不同的结构元素。简而言之,对于2D情况,您需要一个看起来像这样的结构元素:

[[0, 0, 0],
 [1, 1, 1],
 [0, 0, 0]]

这是一个简单的例子:

import numpy as np
import scipy.ndimage as ndimage

a = np.array( [[1, 0, 1, 0, 1, 1, 0, 0],
               [0, 0, 1, 1, 0, 1, 1, 0],
               [1, 0, 1, 1, 0, 0, 0, 1],
               [0, 1, 0, 0, 1, 0, 1, 0],
               [1, 1, 1, 0, 1, 0, 0, 1],
               [1, 0, 0, 0, 0, 1, 0, 1],
               [0, 0, 0, 0, 0, 0, 0, 0],
               [0, 0, 0, 0, 1, 0, 0, 0],
               [0, 0, 1, 0, 1, 1, 0, 1]])
structure = np.zeros((3,3), dtype=np.int)
structure[1,:] = 1

filled = ndimage.morphology.binary_fill_holes(a, structure)
print filled.astype(np.int)

这会产生:

[[1 1 1 1 1 1 0 0]
 [0 0 1 1 1 1 1 0]
 [1 1 1 1 1 1 1 1]
 [0 1 1 1 1 1 1 0]
 [1 1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1 1]
 [0 0 0 0 0 0 0 0]
 [0 0 0 0 1 0 0 0]
 [0 0 1 1 1 1 1 1]]

这方面的真正优势(除了速度......它比使用列表要快得多,内存效率更高!)它对于3D,4D,5D等阵列也能正常工作。

我们只需要调整结构元素以匹配维度数。

import numpy as np
import scipy.ndimage as ndimage

# Generate some random 3D data to match what we want...
x = (np.random.random((10,10,20)) + 0.5).astype(np.int)

# Make the structure (I'm assuming that "z" is the _last_ dimension!)
structure = np.zeros((3,3,3))
structure[1,:,1] = 1

filled = ndimage.morphology.binary_fill_holes(x, structure)

print x[:,:,5]
print filled[:,:,5].astype(np.int)

这是来自随机输入 3D 数组的切片:

[[1 0 1 0 1 1 0 1 0 0]
 [1 0 1 1 0 1 0 1 0 0]
 [1 0 0 1 0 1 1 1 1 0]
 [0 0 0 1 1 0 1 0 0 0]
 [1 0 1 0 1 0 0 1 1 0]
 [1 0 1 1 0 1 0 0 0 1]
 [0 1 0 1 0 0 1 0 1 0]
 [0 1 1 0 1 0 0 0 0 1]
 [0 0 0 1 1 1 1 1 0 1]
 [1 0 1 1 1 1 0 0 0 1]]

这是填充版本:

[[1 1 1 1 1 1 1 1 0 0]
 [1 1 1 1 1 1 1 1 0 0]
 [1 1 1 1 1 1 1 1 1 0]
 [0 0 0 1 1 1 1 0 0 0]
 [1 1 1 1 1 1 1 1 1 0]
 [1 1 1 1 1 1 1 1 1 1]
 [0 1 1 1 1 1 1 1 1 0]
 [0 1 1 1 1 1 1 1 1 1]
 [0 0 0 1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1 1 1 1]]

这里的关键区别在于我们在一步中为整个3D阵列的每个切片做了这个。

答案 2 :(得分:2)

经过一段时间的思考,按照你的描述和所有零行的角落情况,这对于numpy来说仍然非常简单:

In []: A
Out[]: 
array([[1, 0, 1, 0, 1, 1, 0, 0],
       [0, 0, 1, 1, 0, 1, 1, 0],
       [1, 0, 1, 1, 0, 0, 0, 1],
       [0, 1, 0, 0, 1, 0, 1, 0],
       [1, 1, 1, 0, 1, 0, 0, 1],
       [1, 0, 0, 0, 0, 1, 0, 1],
       [0, 0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 1, 0, 0, 0],
       [0, 0, 1, 0, 1, 1, 0, 1]])

In []: v= 0< A.sum(1) # work only with rows at least one 1
In []: A_v= A[v, :]
In []: (r, s), a= A_v.nonzero(), arange(v.sum())
In []: se= c_[searchsorted(r, a), searchsorted(r, a, side= 'right')- 1]
In []: for k in a: A_v[k, s[se[k, 0]]: s[se[k, 1]]]= 1
   ..: 
In []: A[v, :]= A_v

In []: A
Out[]: 
array([[1, 1, 1, 1, 1, 1, 0, 0],
       [0, 0, 1, 1, 1, 1, 1, 0],
       [1, 1, 1, 1, 1, 1, 1, 1],
       [0, 1, 1, 1, 1, 1, 1, 0],
       [1, 1, 1, 1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1, 1, 1, 1],
       [0, 0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 1, 0, 0, 0],
       [0, 0, 1, 1, 1, 1, 1, 1]])

<强>更新
在进行了一些修补之后,这里有一个更加“pythonic”的实现,并且比上面的方法简单得多。那么,以下几行:

for k in xrange(A.shape[0]):
    m= A[k].nonzero()[0]
    try: A[k, m[0]: m[-1]]= 1
    except IndexError: continue

非常简单。事实上,他们的表现非常好。

答案 3 :(得分:0)

我想不出比你描述的更有效的方式:

每行

  1. 从左侧扫描行,直至找到1

  2. 如果找不到1,请继续下一行。

  3. 否则,请从右侧扫描,找到该行中的最后一个1

  4. 使用1 s在1.和3之间的当前行中填写所有内容。