If you don't care about the output shape, you can do this operation just by changing the metadata
x = np.array([[1, 2], [3, 4]])
np.lib.stride_tricks.as_strided(x, (4,2,2), (8,0,0))
Output:
array([[[1, 1],
[1, 1]],
[[2, 2],
[2, 2]],
[[3, 3],
[3, 3]],
[[4, 4],
[4, 4]]])
further, np.block
will give the desired output shape (by copying)
x = np.lib.stride_tricks.as_strided(x, (4,2,2), (8,0,0))
np.block([[x[0], x[1]],
[x[2], x[3]]])
Output:
array([[1, 1, 2, 2],
[1, 1, 2, 2],
[3, 3, 4, 4],
[3, 3, 4, 4]])
The np.block
can also be simulated by using transpose
and reshape
:
x = np.lib.stride_tricks.as_strided(x, (1,2,2,2,2), (32,16,0,8,0))
x.reshape(4,4) # copy!
Output:
array([[1, 1, 2, 2],
[1, 1, 2, 2],
[3, 3, 4, 4],
[3, 3, 4, 4]])
Although, this will copy memory just like np.block
. You can verify this trying to set the shape directly by x.shape = (4,4)
:
AttributeError: Incompatible shape for in-place modification. Use `.reshape()` to make a copy with the desired shape.
In the same fashion the output can be down-sampled just by changing the shape and strides:
np.lib.stride_tricks.as_strided(x, (2,2), (8*8,8*2))
Output:
array([[1, 2],
[3, 4]])
Notice that this is done without any copying. The reason i multiply with 8
is that it is the size in bytes of a 64 bit integer.
Generalizable solution
def upsample(x, k):
return np.lib.stride_tricks.as_strided(x, (np.prod(x.shape), k, k), (x.dtype.itemsize, 0, 0))