pythonmultidimensional-arraynumpy-slicingarray-indexing

Is there a way to extract a solid box slice of an arbitrary multidimensional Python array given two sets of corner index coordinates?


Suppose I have a = np.arange(16).reshape(4,4), which is

array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15]])

And want to slice a as I could with a[0:3,1:4], which results in

array([[ 1,  2,  3],
       [ 5,  6,  7],
       [ 9, 10, 11]])

using the supplied coordinates, [(0,1), (2,3)], which are the indexes of the corners of that box slice.

I'd like to make a function that takes any n-dimensional array and two sets of index coordinates like this and slices the array between these two coordinates, inclusive. (Maybe to be Pythonic, I would not include the last index, so the previously mentioned index coordinates would be [(0,1), (3,4)]. This detail isn't important.)

An example:

import numpy as np

def box_slice(array, start, stop):
    # will return slice
    pass

a = np.arange(3*5*6).reshape(3,5,6)

a is now

array([[[ 0,  1,  2,  3,  4,  5],
        [ 6,  7,  8,  9, 10, 11],
        [12, 13, 14, 15, 16, 17],
        [18, 19, 20, 21, 22, 23],
        [24, 25, 26, 27, 28, 29]],

       [[30, 31, 32, 33, 34, 35],
        [36, 37, 38, 39, 40, 41],
        [42, 43, 44, 45, 46, 47],
        [48, 49, 50, 51, 52, 53],
        [54, 55, 56, 57, 58, 59]],

       [[60, 61, 62, 63, 64, 65],
        [66, 67, 68, 69, 70, 71],
        [72, 73, 74, 75, 76, 77],
        [78, 79, 80, 81, 82, 83],
        [84, 85, 86, 87, 88, 89]]])

This should be equivalent to a[0:3, 1:4, 2:5], assuming the Pythonic implementation:

box_slice(a, [0,1,2], [3,4,5])

Output:

array([[[ 8,  9, 10],
        [14, 15, 16],
        [20, 21, 22]],

       [[38, 39, 40],
        [44, 45, 46],
        [50, 51, 52]],

       [[68, 69, 70],
        [74, 75, 76],
        [80, 81, 82]]])

This could be achieved with eval(), but I don't want to follow that approach unless I have to. Is there already a function that can achieve this with minimal manipulation of the inputs? I prefer NumPy usage, but solutions using other libraries or raw Python are also encouraged.

The solution needs to support any number of dimensions without modification.


Solution

  • I am not sure about a numpy way of doing this but you can use slice and zip to do this.

    import numpy as np
    
    def box_slice(arr, start, stop):
        return arr[tuple(slice(*i) for i in zip(start, stop))]
    
    a = np.arange(16).reshape(4, 4)
    print(box_slice(a, [0, 1], [3, 4]))
    
    a = np.arange(3 * 5 * 6).reshape(3, 5, 6)
    print(box_slice(a, [0, 1, 2], [3, 4, 5]))
    

    Output

    [[ 1  2  3]
     [ 5  6  7]
     [ 9 10 11]]
    
    
    
    [[[ 8  9 10]
      [14 15 16]
      [20 21 22]]
    
     [[38 39 40]
      [44 45 46]
      [50 51 52]]
    
     [[68 69 70]
      [74 75 76]
      [80 81 82]]]