如何裁剪numpy数组的零边?

时间:2016-09-13 08:47:53

标签: python numpy crop

我有这个丑陋的,非pythonic的野兽:

def crop(dat, clp=True):
    '''Crops zero-edges of an array and (optionally) clips it to [0,1].

    Example:
    >>> crop( np.array(
    ...       [[0,0,0,0,0,0],
    ...        [0,0,0,0,0,0],
    ...        [0,1,0,2,9,0],
    ...        [0,0,0,0,0,0],
    ...        [0,7,4,1,0,0],
    ...        [0,0,0,0,0,0]]
    ...     ))
    array([[1, 0, 1, 1],
           [0, 0, 0, 0],
           [1, 1, 1, 0]])
    '''
    if clp: np.clip( dat, 0, 1, out=dat )
    while np.all( dat[0,:]==0 ):
        dat = dat[1:,:]
    while np.all( dat[:,0]==0 ):
        dat = dat[:,1:]
    while np.all( dat[-1,:]==0 ):
        dat = dat[:-1,:]
    while np.all( dat[:,-1]==0 ):
        dat = dat[:,:-1]
    return dat
    # Below gets rid of zero-lines/columns in the middle
    #+so not usable.
    #dat = dat[~np.all(dat==0, axis=1)]      
    #dat = dat[:, ~np.all(dat == 0, axis=0)]

我如何驯服它,让它美丽?

4 个答案:

答案 0 :(得分:4)

尝试合并这样的内容:

# argwhere will give you the coordinates of every non-zero point
true_points = np.argwhere(dat)
# take the smallest points and use them as the top left of your crop
top_left = true_points.min(axis=0)
# take the largest points and use them as the bottom right of your crop
bottom_right = true_points.max(axis=0)
out = dat[top_left[0]:bottom_right[0]+1,  # plus 1 because slice isn't
          top_left[1]:bottom_right[1]+1]  # inclusive

对于一般n-d案例,这可以毫无困难地扩展。

答案 1 :(得分:1)

这应该适用于任何数量的维度。我相信它也非常有效,因为交换轴和切片只创建数组上的视图,而不是复制(排除了take()compress()可能会被诱惑使用的函数)或任何临时函数。然而,它并不比你自己的解决方案明显“好”。

def crop2(dat, clp=True):
    if clp: np.clip( dat, 0, 1, out=dat )
    for i in range(dat.ndim):
        dat = np.swapaxes(dat, 0, i)  # send i-th axis to front
        while np.all( dat[0]==0 ):
            dat = dat[1:]
        while np.all( dat[-1]==0 ):
            dat = dat[:-1]
        dat = np.swapaxes(dat, 0, i)  # send i-th axis to its original position
    return dat

答案 2 :(得分:1)

绝对不是最漂亮的方法,但想尝试别的东西。

def _fill_gap(a):
    """
    a = 1D array of `True`s and `False`s.
    Fill the gap between first and last `True` with `True`s.

    Doesn't do a copy of `a` but in this case it isn't really needed.
    """
    a[slice(*a.nonzero()[0].take([0,-1]))] = True
    return a

def crop3(d, clip=True):
    dat = np.array(d)
    if clip: np.clip(dat, 0, 1, out=dat)
    dat = np.compress(_fill_gap(dat.any(axis=0)), dat, axis=1)
    dat = np.compress(_fill_gap(dat.any(axis=1)), dat, axis=0)
    return dat

但它有效。

In [639]: crop3(np.array(
     ...:   [[0,0,0,0,0,0],
     ...:    [0,0,0,0,0,0],
     ...:    [0,1,0,2,9,0],
     ...:    [0,0,0,0,0,0],
     ...:    [0,7,4,1,0,0],
     ...:    [0,0,0,0,0,0]]))
Out[639]:
array([[1, 0, 1, 1],
       [0, 0, 0, 0],
       [1, 1, 1, 0]])

答案 3 :(得分:0)

实现此目标的另一种方法(对于密集数组而言更快)利用了argmax属性:


def get_last_nz(vec):
    """Get last nonzero element position of a vector
    :param vec: the vector
    :type vec: iterable
    """
    if not isinstance(vec, np.ndarray) or vec.dtype != 'bool':
        vec = np.array(vec) > 0
    return vec.size - 1 - np.argmax(vec[::-1])

def get_first_nz(vec):
    """Get the first nonzero element position of a vector

    :param vec: the vector
    :type vec: iterable
    """
    if not isinstance(vec, np.ndarray) or vec.dtype != 'bool':
        vec = np.array(vec) > 0
    return np.argmax(vec)

def crop(array):
    y_sum = array.sum(axis=1) > 0
    x_sum = array.sum(axis=0) > 0
    x_min = get_first_nz(x_sum)
    x_max = get_last_nz(x_sum)
    y_min = get_first_nz(y_sum)
    y_max = get_last_nz(y_sum)
    return array[y_min: y_max + 1, x_min: x_max + 1]