Using numpy `as_strided` function to create patches, tiles, rolling or sliding windows of arbitrary dimension
EDIT JAN 2020: Changed the iterable return from a list to a generator to save memory.
EDIT OCT 2020: Put the generator in a separate function, since mixing generators and return
statements doesn't work intiutively.
Here's the recipe I have so far:
def window_nd(a, window, steps = None, axis = None, gen_data = False): """ Create a windowed view over `n`-dimensional input that uses an `m`-dimensional window, with `m <= n` Parameters ------------- a : Array-like The array to create the view on window : tuple or int If int, the size of the window in `axis`, or in all dimensions if `axis == None` If tuple, the shape of the desired window. `window.size` must be: equal to `len(axis)` if `axis != None`, else equal to `len(a.shape)`, or 1 steps : tuple, int or None The offset between consecutive windows in desired dimension If None, offset is one in all dimensions If int, the offset for all windows over `axis` If tuple, the steps along each `axis`. `len(steps)` must me equal to `len(axis)` axis : tuple, int or None The axes over which to apply the window If None, apply over all dimensions if tuple or int, the dimensions over which to apply the window gen_data : boolean returns data needed for a generator Returns ------- a_view : ndarray A windowed view on the input array `a`, or `a, wshp`, where `whsp` is the window shape needed for creating the generator """ ashp = np.array(a.shape) if axis != None: axs = np.array(axis, ndmin = 1) assert np.all(np.in1d(axs, np.arange(ashp.size))), "Axes out of range" else: axs = np.arange(ashp.size) window = np.array(window, ndmin = 1) assert (window.size == axs.size) | (window.size == 1), "Window dims and axes don't match" wshp = ashp.copy() wshp[axs] = window assert np.all(wshp <= ashp), "Window is bigger than input array in axes" stp = np.ones_like(ashp) if steps: steps = np.array(steps, ndmin = 1) assert np.all(steps > 0), "Only positive steps allowed" assert (steps.size == axs.size) | (steps.size == 1), "Steps and axes don't match" stp[axs] = steps astr = np.array(a.strides) shape = tuple((ashp - wshp) // stp + 1) + tuple(wshp) strides = tuple(astr * stp) + tuple(astr) as_strided = np.lib.stride_tricks.as_strided a_view = np.squeeze(as_strided(a, shape = shape, strides = strides)) if gen_data : return a_view, shape[:-wshp.size] else: return a_viewdef window_gen(a, window, **kwargs): #Same docstring as above, returns a generator _ = kwargs.pop(gen_data, False) a_view, shp = window_nd(a, window, gen_data = True, **kwargs) for idx in np.ndindex(shp): yield a_view[idx]
Some test cases:
a = np.arange(1000).reshape(10,10,10)window_nd(a, 4).shape # sliding (4x4x4) windowOut: (7, 7, 7, 4, 4, 4)window_nd(a, 2, 2).shape # (2x2x2) blocksOut: (5, 5, 5, 2, 2, 2)window_nd(a, 2, 1, 0).shape # sliding window of width 2 over axis 0Out: (9, 2, 10, 10)window_nd(a, 2, 2, (0,1)).shape # tiled (2x2) windows over first and second axesOut: (5, 5, 2, 2, 10)window_nd(a,(4,3,2)).shape # arbitrary sliding windowOut: (7, 8, 9, 4, 3, 2)window_nd(a,(4,3,2),(1,5,2),(0,2,1)).shape #arbitrary windows, steps and axisOut: (7, 5, 2, 4, 2, 3) # note shape[-3:] != window as axes are out of order