11

There is a function np.split() which can split an array along 1 axis. I was wondering if there was a multi axis version where you can split along axes (0,1,2) for example.

4 Answers 4

21

Suppose the cube has shape (W, H, D) and you wish to break it up into N little cubes of shape (w, h, d). Since NumPy arrays have axes of fixed length, w must evenly divide W, and similarly for h and d.

Then there is a way to reshape the cube of shape (W, H, D) into a new array of shape (N, w, h, d).

For example, if arr = np.arange(4*4*4).reshape(4,4,4) (so (W,H,D) = (4,4,4)) and we wish to break it up into cubes of shape (2,2,2), then we could use

In [283]: arr.reshape(2,2,2,2,2,2).transpose(0,2,4,1,3,5).reshape(-1,2,2,2)
Out[283]: 
array([[[[ 0,  1],
         [ 4,  5]],

        [[16, 17],
         [20, 21]]],

...
       [[[42, 43],
         [46, 47]],

        [[58, 59],
         [62, 63]]]])

The idea here is to add extra axes to the array which sort of act as place markers:

 number of repeats act as placemarkers
 o---o---o
 |   |   |
 v   v   v
(2,2,2,2,2,2)
   ^   ^   ^
   |   |   |
   o---o---o
   newshape

We can then reorder the axes (using transpose) so that the number of repeats comes first, and the newshape comes at the end:

arr.reshape(2,2,2,2,2,2).transpose(0,2,4,1,3,5)

And finally, call reshape(-1, w, h, d) to squash all the placemarking axes into a single axis. This produces an array of shape (N, w, h, d) where N is the number of little cubes.


The idea used above is a generalization of this idea to 3 dimensions. It can be further generalized to ndarrays of any dimension:

import numpy as np
def cubify(arr, newshape):
    oldshape = np.array(arr.shape)
    repeats = (oldshape / newshape).astype(int)
    tmpshape = np.column_stack([repeats, newshape]).ravel()
    order = np.arange(len(tmpshape))
    order = np.concatenate([order[::2], order[1::2]])
    # newshape must divide oldshape evenly or else ValueError will be raised
    return arr.reshape(tmpshape).transpose(order).reshape(-1, *newshape)

print(cubify(np.arange(4*6*16).reshape(4,6,16), (2,3,4)).shape)
print(cubify(np.arange(8*8*8*8).reshape(8,8,8,8), (2,2,2,2)).shape)

yields new arrays of shapes

(16, 2, 3, 4)
(256, 2, 2, 2, 2)

To "uncubify" the arrays:

def uncubify(arr, oldshape):
    N, newshape = arr.shape[0], arr.shape[1:]
    oldshape = np.array(oldshape)    
    repeats = (oldshape / newshape).astype(int)
    tmpshape = np.concatenate([repeats, newshape])
    order = np.arange(len(tmpshape)).reshape(2, -1).ravel(order='F')
    return arr.reshape(tmpshape).transpose(order).reshape(oldshape)

Here is some test code to check that cubify and uncubify are inverses.

import numpy as np
def cubify(arr, newshape):
    oldshape = np.array(arr.shape)
    repeats = (oldshape / newshape).astype(int)
    tmpshape = np.column_stack([repeats, newshape]).ravel()
    order = np.arange(len(tmpshape))
    order = np.concatenate([order[::2], order[1::2]])
    # newshape must divide oldshape evenly or else ValueError will be raised
    return arr.reshape(tmpshape).transpose(order).reshape(-1, *newshape)

def uncubify(arr, oldshape):
    N, newshape = arr.shape[0], arr.shape[1:]
    oldshape = np.array(oldshape)    
    repeats = (oldshape / newshape).astype(int)
    tmpshape = np.concatenate([repeats, newshape])
    order = np.arange(len(tmpshape)).reshape(2, -1).ravel(order='F')
    return arr.reshape(tmpshape).transpose(order).reshape(oldshape)

tests = [[np.arange(4*6*16), (4,6,16), (2,3,4)],
         [np.arange(8*8*8*8), (8,8,8,8), (2,2,2,2)]]

for arr, oldshape, newshape in tests:
    arr = arr.reshape(oldshape)
    assert np.allclose(uncubify(cubify(arr, newshape), oldshape), arr)
    # cuber = Cubify(oldshape,newshape)
    # assert np.allclose(cuber.uncubify(cuber.cubify(arr)), arr)
Sign up to request clarification or add additional context in comments.

3 Comments

if i want to uncubify it what is the transpose reshape combination i need to get it back to the big cube?
@mattdns: I've added an uncubify function above. There is a difference in our definitions of reverseOrder (or what I called order in the uncubify function). It makes a difference for higher dimensional arrays. Changing the definition to self.reverseOrder = np.arange(len(self.tmpshape)).reshape(2, -1).ravel(order='F') will fix your code.
Great answer. May I suggest to cubify with: einops.rearrange(x, '(x w) (y h) (z d) -> (x y z) w h d', w=2, h=2, d=2) and uncubify with einops.rearrange(x, ' (x y z) w h d -> (x w) (y h) (z d)', x=2, y=2, z=2) ?
3

In addition to my extra question to @unutbu's answer I think I have got the reverse to work (in case you want to split a cube into cubes, apply a function to each one and then combine them back).

import numpy as np
import pdb
np.set_printoptions(precision=3,linewidth=300)

class Cubify():
    def __init__(self,oldshape,newshape):
        self.newshape = np.array(newshape)
        self.oldshape = np.array(oldshape)
        self.repeats = (oldshape / newshape).astype(int)
        self.tmpshape = np.column_stack([self.repeats, newshape]).ravel()
        order = np.arange(len(self.tmpshape))
        self.order = np.concatenate([order[::2], order[1::2]])
        self.reverseOrder = self.order.copy()
        self.reverseOrder = np.arange(len(self.tmpshape)).reshape(2, -1).ravel(order='F')
        self.reverseReshape = np.concatenate([self.repeats,self.newshape])

    def cubify(self,arr):
        # newshape must divide oldshape evenly or else ValueError will be raised
        return arr.reshape(self.tmpshape).transpose(self.order).reshape(-1, *self.newshape)

    def uncubify(self,arr):
        return arr.reshape(self.reverseReshape).transpose(self.reverseOrder).reshape(self.oldshape)

if __name__ == "__main__":
    N = 9
    x = np.arange(N**3).reshape(N,N,N)
    oldshape = x.shape
    newshape = np.array([3,3,3])
    cuber = Cubify(oldshape,newshape)
    out = cuber.cubify(x)
    back = cuber.uncubify(out)

Comments

1

I needed the cubify function from unutbu's answer written using TensorFlow functions to be able to use it as a layer in my neural network and to optimize it with @tf.function. I believe that someone might find it useful, therefore, I'm leaving it here. I did not test the overall functionality on different data but for my data it works great.

@tf.function
def cubify(inputs, output_shape):
    repeats = tf.math.floordiv(inputs.shape, output_shape)
    tmp_shape = tf.stack([repeats, output_shape], axis=1)
    tmp_shape_reshaped = tf.reshape(tmp_shape, [-1])
    range_len = tf.range(len(tmp_shape_reshaped))
    order = tf.concat([[range_len[::2], range_len[1::2]]], axis=1)
    order_reshaped = tf.reshape(order, [-1])
    output = tf.reshape(inputs, tmp_shape_reshaped)
    output_reordered = tf.transpose(output, order_reshaped)
    output_reshaped = tf.reshape(output_reordered, (-1, *output_shape))
    return output_reshaped

Comments

0

I don't think there is a multi axis version where you can split along some given axes. But you can split it up one dimension at a time. For example like this:

def split2(arys, sections, axis=[0, 1]):
    if not isinstance(arys, list):
         arys = [arys]
    for ax in axis:
        arys = [np.split(ary, sections, axis=ax) for ary in arys]
        arys = [ary for aa in arys for ary in aa]  # Flatten
    return arys

It can be used like this:

In [1]: a = np.array(range(100)).reshape(10, 10)
In [2]: split2(a, 2, axis=[0, 1])
Out[2]:
[array([[ 0,  1,  2,  3,  4],
       [10, 11, 12, 13, 14],
       [20, 21, 22, 23, 24],
       [30, 31, 32, 33, 34],
       [40, 41, 42, 43, 44]]),
 array([[ 5,  6,  7,  8,  9],
       [15, 16, 17, 18, 19],
       [25, 26, 27, 28, 29],
       [35, 36, 37, 38, 39],
       [45, 46, 47, 48, 49]]),
 array([[50, 51, 52, 53, 54],
       [60, 61, 62, 63, 64],
       [70, 71, 72, 73, 74],
       [80, 81, 82, 83, 84],
       [90, 91, 92, 93, 94]]),
 array([[55, 56, 57, 58, 59],
       [65, 66, 67, 68, 69],
       [75, 76, 77, 78, 79],
       [85, 86, 87, 88, 89],
       [95, 96, 97, 98, 99]])]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.