1

I am not sure what is right terminology here. Please correct. I have a grid (2D array) that is looped. By this I mean first row is the next after last row. Same for columns.

I want to slice subset of big grid having this looped rule in mind. So, having grid:

[[ 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]
 [90 91 92 93 94 95 96 97 98 99]]

And I want subset with size 3 by 3 centered in the middle (5,5), I would get:

[[44 45 46]
 [54 55 56]
 [64 65 66]]

But if I want it to be centered in (0,0) I would get:

[[99 90 91]
 [ 9  0  1]
 [19 10 11]]

In my current solution I've combined np.roll with slicing. It's working, but I am looking for more performant solution.

My current solution:

def get_centered_section(arr, center, side_size):
    if side_size % 2 is 0:
        raise "size shuold be odd number"
    half_side_size = int((side_size - 1) / 2)
    w, h = arr.shape
    x, y = center

    ystart = y - half_side_size
    if ystart < 0:
        arr = np.roll(arr, abs(ystart), 0)
        ystart = 0
    elif ystart + side_size >= h:
        overflow = ystart + side_size - h
        ystart -= overflow
        arr = np.roll(arr, -overflow, 0)

    xstart = x - half_side_size
    if xstart < 0:
        arr = np.roll(arr, abs(xstart), 1)
        xstart = 0
    elif xstart + side_size >= w:
        overflow = xstart + side_size - w
        xstart -= overflow
        arr = np.roll(arr, -overflow, 1)

    return arr[ystart:ystart+side_size,xstart:xstart+side_size]

test_a1 = np.reshape(np.arange(10*10), (10, 10))
get_centered_section(test_a1, (0, 0), 3)

Maybe there is a way to cache my way out. My specific usage will require going through each cell getting this kinda slice.

Divakar
  • 218,885
  • 19
  • 262
  • 358
Aleksandr Motsjonov
  • 1,230
  • 3
  • 14
  • 25
  • 1
    The right terminology for what you call *looped* is *wrapping around*. Searching for 'numpy wrap around', you arrive at [this question](http://stackoverflow.com/questions/17739543/wrapping-around-slices-in-python-numpy), the answer with `numpy.take` seems a good solution. [This](http://stackoverflow.com/questions/21396121/wrap-slice-around-edges-of-a-2d-array-in-numpy) and [this](http://stackoverflow.com/questions/4148292/) also seem very closely related. I suggest to close this question as a duplicate of one of these ... – Bas Swinckels Dec 03 '16 at 11:25

1 Answers1

2

One approach would involve padding with wrap around using np.pad and then slicing, like so -

def get_centered_section(a, center, side_size):
    ext_size = (side_size[0]-1)/2, (side_size[1]-1)//2
    a_pad = np.lib.pad(a, ([ext_size[0]],[ext_size[1]]), 'wrap')
    return a_pad[center[0]:center[0]+side_size[0], \
                 center[1]:center[1]+side_size[1]]

Few sample runs -

In [94]: a
Out[94]: 
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],
       [90, 91, 92, 93, 94, 95, 96, 97, 98, 99]])

In [95]: get_centered_section(a, center = (0,0), side_size = (3,3))
Out[95]: 
array([[99, 90, 91],
       [ 9,  0,  1],
       [19, 10, 11]])

In [97]: get_centered_section(a, center = (5,5), side_size = (5,5))
Out[97]: 
array([[33, 34, 35, 36, 37],
       [43, 44, 45, 46, 47],
       [53, 54, 55, 56, 57],
       [63, 64, 65, 66, 67],
       [73, 74, 75, 76, 77]])

In [98]: get_centered_section(a, center = (7,2), side_size = (3,5))
Out[98]: 
array([[60, 61, 62, 63, 64],
       [70, 71, 72, 73, 74],
       [80, 81, 82, 83, 84]])
Divakar
  • 218,885
  • 19
  • 262
  • 358
  • That's funny. I thought of similar solution second before refreshing and seeing it =) I thought that if I pad once with maximum I need and then slice normally in for-loops using modified, padded grid. Big difference is that I didn't know how to do this padding. – Aleksandr Motsjonov Dec 03 '16 at 09:21
  • This solution many times slower then mine. With 10x10 grid and 3x3 kernel my takes ~ 0.05ms and this one ~0.3ms – Aleksandr Motsjonov Dec 03 '16 at 09:33
  • But maybe when I iterate it will take less time. Let me check. – Aleksandr Motsjonov Dec 03 '16 at 09:35
  • @AleksandrMotsjonov If you have to iterate, it would make sense to store the padded version and slice on the padded version iteratively, rather than creating the padded version again and again. – Divakar Dec 03 '16 at 09:38
  • Of cause. That's what I was thinking in the first comment. And that's what I just did. Seems like it's 15 times faster then mine! Thx. – Aleksandr Motsjonov Dec 03 '16 at 09:48