【问题标题】:How to crop zero edges of a numpy array?如何裁剪 numpy 数组的零边缘?
【发布时间】:2017-01-20 19:20:49
【问题描述】:

我有一个丑陋的、非蟒蛇式的野兽:

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)]

如何驯服它,让它变得美丽?

【问题讨论】:

  • 你确定函数文档中的例子是正确的吗?输出对应于输入?我看到不仅删除了零,而且看到了完全不同的数字。 :)
  • 除此之外,我认为代码很好。鉴于您只是在 2D 中工作,几乎没有任何更好的解决方案。或者您的目标是在任意数量的维度上的通用解决方案?
  • 我不需要一个通用的解决方案,但如果我有一个,那就更好了。至于“完全不同的数字”:该函数还将数组剪辑到区间[0,1]。所以是的,两者对应(或者至少 doctest 没有抱怨)。
  • 哦,我的错。我忘记了剪裁,我只专注于零的剪裁。你是对的,你的输入/输出示例是正确的。但是我建议将这两个功能分开 - 剪辑和裁剪应该分开 - 你知道规则:一个功能(或类)一个责任。
  • 注意:np.clip( dat, 0, 1, out=dat ) 也会对实际参数进行更改。我建议@SCB 在开头使用def crop(d, clp=True): dat = np.array(d) 回答。

标签: python numpy crop


【解决方案1】:

尝试合并这样的东西:

# 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 案例,这可以毫无困难地扩展。

【讨论】:

  • 似乎是一个不错的解决方案。对于大型输入数组 (dat),true_points 可能会变得有点大。
  • 有点大是多大?对于 2d 数组,在绝对最坏的情况下,最大大小为 2 * 数组大小。如果您希望这更常见,那么您的原始解决方案可能会更有效(尽管可能将 np.all(dat[...] == 0) 切换为 not np.any(dat[...]))。
  • 无循环;算法高效!
  • @ÉbeIsaac:嗯,Python 中没有循环。但在 C 中可能有很多循环......以及在此之上的大量临时分配。而且我猜还有对内存不友好的缓存遍历...... :)
  • @V.K.: True :-) 但它们并没有像 OP 最初提出的那样不受约束,它们是 Numpy 例程并且在底层进行了优化。
【解决方案2】:

这应该适用于任意数量的维度。我相信它也非常有效,因为交换轴和切片仅在数组上创建视图,而不是副本(这排除了诸如 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

【讨论】:

    【解决方案3】:

    绝对不是最漂亮的方法,但想尝试其他方法。

    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]])
    

    【讨论】:

      【解决方案4】:

      实现这一点的另一种方法是使用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]
      
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-09-21
        • 2018-08-05
        • 2021-12-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-11-12
        • 2015-07-29
        相关资源
        最近更新 更多