Zero space cuts off end of array in numpy

In numpy, is there a way to get null entries in the pad if I slice the end of the end of the array to get what is the size of the required fragment?

For instance,

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

Visually, I would like areas outside the limits to be filled with zeros:

enter image description here

I deal with images and would like the pad zero to indicate the movement of the image for my application.

My current plan is to use np.pad to make the whole array larger before slicing, but indexing seems a bit complicated. Is there an easier way?

+7
source share
5 answers

As far as I know, there is no numpy solution for this problem (nor in any package that I know). You could do it yourself, but it will be really, really complicated, even if you only want a basic cut. I suggest you manually np.pad your array and just shift your start / stop / step before you actually cut it.

However, if all you need to support is integers and fragments without a step. For this, I have a "working code":

 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 a single argument. # If it just an integer then we just return, this is # needed for the representation to work properly # If it 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 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 

This can be used as follows:

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

Please note that this may contain errors and "non-encoded" parts, I have never used this, except in trivial cases.

+4
source

This class can handle your first test ( x[1:4, 1:4] ) and can be modified to handle your other test (for example, adding zeros to the beginning) if you wish.

 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 

Here is a usage example:

 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 
+4
source

In the case of a 1D array, I did this, it can be useful if someone falls here ...

 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 

Using

 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 

Display

 [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] 
0
source

For the simplest case of rank 2 or 3 images, here is an example of how to implement β€œslice” with zero padding using indices outside the bounds:

 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 

For example, call this function with padded_slice(img, [-10,150,-10,150]) on a 100x100 image and it will return an image with zero padding of 160x160.

0
source

Is there any way? Yes. It's complicated? Not particularly.

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

Why use this?

If memory is a problem, then copying the image to the augmented version may not be very good. This also works well for higher dimensional inputs, and it makes it clear how to return indexes / slices if you need them.

Why is framing a parameter?

To indicate the filled value, instead, we can pre-create the memory for framing using np.zeros / np.full , and then fill in the part that we need. Then the difficulty lies not in where to copy, but in where to paste the crop.

theory

Let's look at a one-dimensional case:

Colorful description of 1D case

If you think about this a little, you will see that:

  • crop_low much higher than 0 as pos lower than 0 , but if pos >= 0 , crop_low == 0
  • crop_high is just as far below crop.shape as end above img.shape , but if end <= img.shape , then crop_high == crop.shape

If we put this in regular Python code, it will look like this:

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

The rest of the code above is for indexing only.

testing

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

And there we have it. An overly designed answer to the original question.

0
source

Source: https://habr.com/ru/post/1261296/


All Articles