零填充切片在numpy中的数组末尾

时间:2016-12-14 23:07:44

标签: python numpy

在numpy中,如果我正在切换数组的末尾,有没有办法将填充条目归零,这样我得到的东西就像所需切片的大小一样?

例如,

>>> x = np.ones((3,3,))
>>> x
array([[ 1.,  1.,  1.],
       [ 1.,  1.,  1.],
       [ 1.,  1.,  1.]])
>>> x[1:4, 1:4] # would behave as x[1:3, 1:3] by default
array([[ 1.,  1.,  0.],
       [ 1.,  1.,  0.],
       [ 0.,  0.,  0.]])
>>> x[-1:2, -1:2]
 array([[ 0.,  0.,  0.],
       [ 0.,  1.,  1.],
       [ 0.,  1.,  1.]])

从视觉上看,我希望越界区域为零填充:

enter image description here

我正在处理图像,并希望将零填充表示为我的应用程序移动图像。

我目前的计划是在切片之前使用np.pad使整个数组更大,但索引似乎有点棘手。有没有更容易的方法?

5 个答案:

答案 0 :(得分:4)

此类可以处理您的第一个测试(x[1:4, 1:4]),并且可以修改以处理您的其他测试(即,如果您愿意,可以在开头添加零)。

class CustomArray():

    def __init__(self, numpy_array):
        self._array = numpy_array

    def __getitem__(self, val):

        # Get the shape you wish to return
        required_shape = []
        for i in range(2):
            start = val[i].start
            if not start:
                start = 0
            required_shape.append(val[i].stop - start)

        get = self._array[val]

        # Check first dimension
        while get.shape[0] < required_shape[0]:
            get = np.concatenate((get, np.zeros((1, get.shape[1]))))

        # Check second dimension
        get = get.T
        while get.shape[0] < required_shape[1]:
            get = np.concatenate((get, np.zeros((1, get.shape[1]))))
        get = get.T

        return get

以下是它的用法示例:

a = CustomArray(np.ones((3, 3)))

print(a[:2, :2])
[[ 1.  1.]
 [ 1.  1.]]

print(a[:4, 1:6])
[[ 1.  1.  0.  0.  0.]
 [ 1.  1.  0.  0.  0.]
 [ 1.  1.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.]]

# The actual numpy array is stored in the _array attribute
actual_numpy_array = a._array

答案 1 :(得分:4)

据我所知,对于这样的问题,没有任何笨拙的解决方案(也不是我知道的任何包装)。你可以自己做,但即使你只想要基本的切片,它也会是一个非常非常复杂的。我建议你手动np.pad你的数组,然后在实际切片之前简单地偏移你的开始/停止/步骤。

但是,如果您需要支持的只是整数和切片而没有步骤,我会有一些&#34;工作代码&#34;为此:

import numpy as np

class FunArray(np.ndarray):
    def __getitem__(self, item):

        all_in_slices = []
        pad = []
        for dim in range(self.ndim):
            # If the slice has no length then it's a single argument.
            # If it's just an integer then we just return, this is
            # needed for the representation to work properly
            # If it's not then create a list containing None-slices
            # for dim>=1 and continue down the loop
            try:
                len(item)
            except TypeError:
                if isinstance(item, int):
                    return super().__getitem__(item)
                newitem = [slice(None)]*self.ndim
                newitem[0] = item
                item = newitem
            # We're out of items, just append noop slices
            if dim >= len(item):
                all_in_slices.append(slice(0, self.shape[dim]))
                pad.append((0, 0))
            # We're dealing with an integer (no padding even if it's
            # out of bounds)
            if isinstance(item[dim], int):
                all_in_slices.append(slice(item[dim], item[dim]+1))
                pad.append((0, 0))
            # Dealing with a slice, here it get's complicated, we need
            # to correctly deal with None start/stop as well as with
            # out-of-bound values and correct padding
            elif isinstance(item[dim], slice):
                # Placeholders for values
                start, stop = 0, self.shape[dim]
                this_pad = [0, 0]
                if item[dim].start is None:
                    start = 0
                else:
                    if item[dim].start < 0:
                        this_pad[0] = -item[dim].start
                        start = 0
                    else:
                        start = item[dim].start
                if item[dim].stop is None:
                    stop = self.shape[dim]
                else:
                    if item[dim].stop > self.shape[dim]:
                        this_pad[1] = item[dim].stop - self.shape[dim]
                        stop = self.shape[dim]
                    else:
                        stop = item[dim].stop
                all_in_slices.append(slice(start, stop))
                pad.append(tuple(this_pad))

        # Let numpy deal with slicing
        ret = super().__getitem__(tuple(all_in_slices))
        # and padding
        ret = np.pad(ret, tuple(pad), mode='constant', constant_values=0)

        return ret

可以使用如下:

>>> x = np.arange(9).reshape(3, 3)
>>> x = x.view(FunArray)
>>> x[0:2]
array([[0, 1, 2],
       [3, 4, 5]])
>>> x[-3:2]
array([[0, 0, 0],
       [0, 0, 0],
       [0, 0, 0],
       [0, 1, 2],
       [3, 4, 5]])
>>> x[-3:2, 2]
array([[0],
       [0],
       [0],
       [2],
       [5]])
>>> x[-1:4, -1:4]
array([[0, 0, 0, 0, 0],
       [0, 0, 1, 2, 0],
       [0, 3, 4, 5, 0],
       [0, 6, 7, 8, 0],
       [0, 0, 0, 0, 0]])

请注意,这可能包含错误和&#34;没有干净编码&#34;部分,除了琐碎的情况外,我从未使用过它。

答案 2 :(得分:0)

如果是一维数组,我这样做,如果有人落在这里会很有用......

def getPaddedSlice(npArray, pos, lenSegment, center = False):
    lenNpArray = len(npArray)
    if center:
        if lenSegment % 2 == 0:
            startIndex = int(pos - math.floor(lenSegment / 2.0)) + 1 
            lastIndex  = int(pos + math.ceil(lenSegment / 2.0))  + 1  

        else : 
            startIndex = int(pos - math.floor(lenSegment / 2.0))
            lastIndex  = int(pos + math.ceil(lenSegment / 2.0)) + 1 
    else:
        startIndex = pos
        lastIndex  = startIndex + lenSegment 

    if startIndex < 0:
        padded_slice = npArray[0: lastIndex]
        padded_slice = np.concatenate((np.zeros(abs(startIndex)), padded_slice))  
    else:
        if center :
            padded_slice = npArray[startIndex: lastIndex]
        else:
            padded_slice = npArray[pos: lastIndex]

    if lastIndex > len(npArray):
        if center :
            padded_slice = npArray[startIndex: pos + lenSegment]
            padded_slice = np.concatenate((padded_slice, np.zeros(lastIndex - len(a))))
        else : 
            padded_slice = npArray[pos: pos + lenSegment]
            padded_slice = np.concatenate((padded_slice, np.zeros(lastIndex - len(a))))

    return padded_slice

用法

a = np.asarray([2,2,3,1,7,6,5,4])

for i in range(len(a)):
    b = getPaddedSlice(a, i, lenSegment, True)
    print b

显示

[0 2 2 3]
[2 2 3 1]
[2 3 1 7]
[3 1 7 6]
[1 7 6 5]
[7 6 5 4]
[6 5 4 0]
[5 4 0 0]

答案 3 :(得分:0)

对于最简单的2或3级图像,下面是一个如何使用越界索引实现零填充“切片”的示例:

def padded_slice(src, sl):
    output_shape = np.asarray(np.shape(src))
    output_shape[0] = sl[1] - sl[0]
    output_shape[1] = sl[3] - sl[2]
    src = [max(sl[0], 0),
           min(sl[1], img.shape[0]),
           max(sl[2], 0),
           min(sl[3], img.shape[1])]
    dst = [src[0] - sl[0], src[1] - sl[0],
           src[2] - sl[2], src[3] - sl[2]]
    output = np.zeros(output_shape, dtype=img.dtype)
    output[dst[0]:dst[1],dst[2]:dst[3]] = img[src[0]:src[1],src[2]:src[3]]
    return output

例如,在100x100的图像上使用padded_slice(img, [-10,150,-10,150])调用此函数,它将返回160x160的零填充图像。

答案 4 :(得分:0)

有办法吗?是。复杂吗?并非如此。

import numpy as np
def fill_crop(img, pos, crop):
  '''
  Fills `crop` with values from `img` at `pos`, 
  while accounting for the crop being off the edge of `img`.
  *Note:* negative values in `pos` are interpreted as-is, not as "from the end".
  '''
  img_shape, pos, crop_shape = np.array(img.shape), np.array(pos), np.array(crop.shape),
  end = pos+crop_shape
  # Calculate crop slice positions
  crop_low = np.clip(0 - pos, a_min=0, a_max=crop_shape)
  crop_high = crop_shape - np.clip(end-img_shape, a_min=0, a_max=crop_shape)
  crop_slices = (slice(low, high) for low, high in zip(crop_low, crop_high))
  # Calculate img slice positions
  pos = np.clip(pos, a_min=0, a_max=img_shape)
  end = np.clip(end, a_min=0, a_max=img_shape)
  img_slices = (slice(low, high) for low, high in zip(pos, end))
  crop[tuple(crop_slices)] = img[tuple(img_slices)]

为什么要使用它?

如果要考虑内存问题,那么将图像复制到填充版本可能不是很好。这对于高维输入也很有效,并且很清楚如何在需要时返回索引/切片。

为什么作物是一个参数?

要指示填充值,我们可以改用np.zeros / np.full提前为农作物创建内存,然后填写所需的部分。这样,困难就不在找出要从何处复制,而是要粘贴到农作物内部的位置。

理论

让我们看一维案例:

Colourful description of 1D case

稍微想一想,就会发现:

  • crop_low远高于0,而pos远低于0,但是如果pos >= 0,则crop_low == 0
  • crop_high远低于crop.shape,而end远高于img.shape,但是如果end <= img.shape,则crop_high == crop.shape

如果我们将其放入普通的python代码中,它将看起来像这样:

crop_low = max(-pos, 0)
crop_high = crop.shape - max(end-img.shape, 0)

上面的其余代码仅用于索引。

测试

# Examples in 1 dimension
img = np.arange(10, 20)

# Normal
pos = np.array([1,])
crop = np.full([5,], 0)
fill_crop(img, pos, crop)
assert crop.tolist() == [11, 12, 13, 14, 15]

# Off end
pos = np.array([8,])
crop = np.full([5,], 0)
fill_crop(img, pos, crop)
assert crop.tolist() == [18, 19,  0,  0,  0]

# Off start
pos = np.array([-2,])
crop = np.full([5,], 0)
fill_crop(img, pos, crop)
assert crop.tolist() == [ 0,  0, 10, 11, 12]


# Example in 2 dimensions (y,x)
img = np.arange(10, 10+10*10)\
          .reshape([10, 10])
# Off Top right
pos = np.array([-2, 8])
crop = np.full([5, 5], 0)
fill_crop(img, pos, crop)
assert np.all(crop[:2] == 0) # That is, the top two rows are 0s
assert np.all(crop[:, 3:] == 0) # That is, the right 3 rows are 0s
assert np.all(crop[2:, :2] == img[:3, 8:]) 
# That is, the rows 2-5 and columns 0-1 in the crop
#  are the same as the top 3 rows and columns 8 and 9 (the last two columns)

就在那里。对原始问题的过度设计的答案。