简体   繁体   中英

How to get many rolling window slices in numpy?

I have the following numpy array:

[[[1], [2], [3], [1], [2], [3]],
 [[4], [5], [6], [4], [5], [6]],
 [[7], [8], [9], [7], [8], [9]]]

And I want each of the elements in the last dimension, [1] , [2] , [3] etc. to be concatenate with the following n arrays in the second dimension. In case of overflow, elements can be filled with 0. For example, for n = 2 :

[[[1, 2, 3], [2, 3, 1], [3, 1, 2], [1, 2, 3], [2, 3, 0], [3, 0, 0]],
 [[4, 5, 6], [5, 6, 4], [6, 4, 5], [4, 5, 6], [5, 6, 0], [6, 0, 0]],
 [[7, 8, 9], [8, 9, 7], [9, 7, 8], [7, 8, 9], [8, 9, 0], [9, 0, 0]]]

I want to do this with the built in numpy functions for good performance and also want to do it in reverse ie, a shift of n = -2 is fair game. How to do this?

For n = -2 :

[[[0, 0, 1], [0, 1, 2], [1, 2, 3], [2, 3, 1], [3, 1, 2], [1, 2, 3]],
 [[0, 0, 4], [0, 4, 5], [4, 5, 6], [5, 6, 4], [6, 4, 5], [4, 5, 6]],
 [[0, 0, 7], [0, 7, 8], [7, 8, 9], [8, 9, 7], [9, 7, 8], [7, 8, 9]]]

For n = 3

[[[1, 2, 3, 1], [2, 3, 1, 2], [3, 1, 2, 3], [1, 2, 3, 0], [2, 3, 0, 0], [3, 0, 0, 0]],
 [[4, 5, 6, 4], [5, 6, 4, 5], [6, 4, 5, 6], [4, 5, 6, 0], [5, 6, 0, 0], [6, 0, 0, 0]],
 [[7, 8, 9, 7], [8, 9, 7, 8], [9, 7, 8, 9], [7, 8, 9, 0], [8, 9, 0, 0], [9, 0, 0, 0]]]

If the current shape of the array is (height, width, 1) , after the operation, the shape will be (height, width, abs(n) + 1) .

How to generalize this so that the numbers 1, 2, 3 etc. can themselves be numpy arrays?

Here is a way to do it:

from skimage.util import view_as_windows

if n>=0:
  a = np.pad(a.reshape(*a.shape[:-1]),((0,0),(0,n)))
else:
  n *= -1
  a = np.pad(a.reshape(*a.shape[:-1]),((0,0),(n,0)))

b = view_as_windows(a,(1,n+1))
b = b.reshape(*b.shape[:-2]+(n+1,))

a is your input array and b is your output:

n=2 :

[[[1 2 3]
  [2 3 1]
  [3 1 2]
  [1 2 3]
  [2 3 0]
  [3 0 0]]

 [[4 5 6]
  [5 6 4]
  [6 4 5]
  [4 5 6]
  [5 6 0]
  [6 0 0]]

 [[7 8 9]
  [8 9 7]
  [9 7 8]
  [7 8 9]
  [8 9 0]
  [9 0 0]]]

n=-2 :

[[[0 0 1]
  [0 1 2]
  [1 2 3]
  [2 3 1]
  [3 1 2]
  [1 2 3]]

 [[0 0 4]
  [0 4 5]
  [4 5 6]
  [5 6 4]
  [6 4 5]
  [4 5 6]]

 [[0 0 7]
  [0 7 8]
  [7 8 9]
  [8 9 7]
  [9 7 8]
  [7 8 9]]]

Explanation :

  • np.pad(a.reshape(*a.shape[:-1]),((0,0),(0,n))) pads enough zeros to the right side of array for overflow of windows (similarly padding left side for negative n )
  • view_as_windows(a,(1,n+1)) creates windows of shape (1,n+1) from the array as desired by the question.
  • b.reshape(*b.shape[:-2]+(n+1,)) gets rid of the extra dimension of length 1 created by (1,n+1) -shaped windows and reshape b to desired shape. Note the argument *b.shape[:-2]+(n+1,) is simply concatenation of two tuples to create a single tuple as shape.

This sounds like a textbook application for the monster that is as_strided . One of the nice things about it is that it does not require any additional imports. The general idea is this:

  1. You have an array with shape (3, 6, 1) and strides (6, 1, 1) * element_size .

     x =... n =... # Must not be zero, but you can special-case it to return the original array
  2. You want to transform this into an array that has shape (3, 6, |n| + 1) and therefore strides (6 * (|n| + 1), |n| + 1, 1) * element_size .

  3. To do this, you first pad the left or the right with |n|zeros:

     pad = np.zeros((x.shape[0], np.abs(n), x.shape[2])) x_pad = np.concatenate([x, pad][::np.sign(n)], axis=1)
  4. Now you can index directly into the buffer with a custom shape and strides to get the result you want. Instead of using the proper strides (6 * (|n| + 1), |n| + 1, 1) * element_size , we will index each repeated element directly into the same buffer of the original array, meaning that the strides will be adjusted. The middle dimension will move by one element, rather than the proper |n| + 1 |n| + 1 . That way, the columns can start exactly where you want them to:

     new_shape = (x.shape[0], x.shape[1], x.shape[2] + np.abs(n)) new_strides = (x_pad.strides[0], x_pad.strides[2], x_pad.strides[2]) result = np.lib.stride_tricks.as_strided(x_pad, shape=new_shape, strides=new_strides)

There are many caveats here. The biggest thing to be aware of is that multiple array elements access the same memory. My advice is to make a proper fleshed-out copy if you plan to do anything besides just reading the data:

result = result.copy()

This will give you a buffer of the correct size rather than a crazy view into the original data with padding.

You can also do this (requires numpy version 1.20.x+

import numpy as np

arr = np.array([[[1], [2], [3], [1], [2], [3]],
            [[4], [5], [6], [4], [5], [6]],
            [[7], [8], [9], [7], [8], [9]]])

n = 2 # your n value
nslices = n+1

# reshape into 2d array
arr = arr.reshape((3, -1)) # <-- add n zeros as padding here

# perform the slicing
slices = np.lib.stride_tricks.sliding_window_view(arr, (1, nslices))
slices = slices[:, :, 0]

print(slices)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM