Quick way to upsample numpy array by nearest neighbor tiling

I have a 2D array of integers that is MxN, and I would like to expand the array to (BM)x(BN) where B is the length of a square tile side thus each element of the input array is repeated as a BxB block in the final array. Below is an example with a nested for loop. Is there a quicker/builtin way?

import numpy as np

a = np.arange(9).reshape([3,3])            # input array - 3x3
B=2.                                       # block size - 2  
A = np.zeros([a.shape[0]*B,a.shape[1]*B])  # output array - 6x6

# Loop, filling A with tiled values of a at each index
for i,l in enumerate(a):                   # lines in a
    for j,aij in enumerate(l):             # a[i,j]
        A[B*i:B*(i+1),B*j:B*(j+1)] = aij

Result …

a=      [[0 1 2]
         [3 4 5]
         [6 7 8]]

A =     [[ 0.  0.  1.  1.  2.  2.]
         [ 0.  0.  1.  1.  2.  2.]
         [ 3.  3.  4.  4.  5.  5.]
         [ 3.  3.  4.  4.  5.  5.]
         [ 6.  6.  7.  7.  8.  8.]
         [ 6.  6.  7.  7.  8.  8.]]

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

One option is

>>> a.repeat(2, axis=0).repeat(2, axis=1)
array([[0, 0, 1, 1, 2, 2],
       [0, 0, 1, 1, 2, 2],
       [3, 3, 4, 4, 5, 5],
       [3, 3, 4, 4, 5, 5],
       [6, 6, 7, 7, 8, 8],
       [6, 6, 7, 7, 8, 8]])

This is slightly wasteful due to the intermediate array but it’s concise at least.

Method 2

Here’s a potentially fast way using stride tricks and reshaping:

from numpy.lib.stride_tricks import as_strided

def tile_array(a, b0, b1):
    r, c = a.shape                                    # number of rows/columns
    rs, cs = a.strides                                # row/column strides 
    x = as_strided(a, (r, b0, c, b1), (rs, 0, cs, 0)) # view a as larger 4D array
    return x.reshape(r*b0, c*b1)                      # create new 2D array

The underlying data in a is copied when reshape is called, so this function does not return a view. However, compared to using repeat along multiple axes, fewer copying operations are required.

The function can be then used as follows:

>>> a = np.arange(9).reshape(3, 3)
>>> a
array([[0, 1, 2],
       [3, 4, 5],
       [6, 7, 8]])

>>> tile_array(a, 2, 2)
array([[0, 0, 1, 1, 2, 2],
       [0, 0, 1, 1, 2, 2],
       [3, 3, 4, 4, 5, 5],
       [3, 3, 4, 4, 5, 5],
       [6, 6, 7, 7, 8, 8],
       [6, 6, 7, 7, 8, 8]])

>>> tile_array(a, 3, 4)
array([[0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2],
       [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2],
       [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2],
       [3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5],
       [3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5],
       [3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5],
       [6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8],
       [6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8],
       [6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8]])

Now, for small blocks, this method is a little slower than using repeat but faster than kron.

For slightly larger blocks, however, it becomes quicker than other alternatives. For instance, using a block shape of (20, 20):

>>> %timeit tile_array(a, 20, 20)
100000 loops, best of 3: 18.7 µs per loop

>>> %timeit a.repeat(20, axis=0).repeat(20, axis=1)
10000 loops, best of 3: 26 µs per loop

>>> %timeit np.kron(a, np.ones((20,20), a.dtype))
10000 loops, best of 3: 106 µs per loop

The gap between the methods increases as the block size increases.

Also if a is a large array, it may be quicker than alternatives:

>>> a2 = np.arange(1000000).reshape(1000, 1000)
>>> %timeit tile_array(a2, 2, 2)
100 loops, best of 3: 11.4 ms per loop

>>> %timeit a2.repeat(2, axis=0).repeat(2, axis=1)
1 loops, best of 3: 30.9 ms per loop

Method 3

Probably not the fastest, but..

np.kron(a, np.ones((B,B), a.dtype))

It does the Kronecker product, so it involves a multiplication for each element in the output.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x