jez jez - 1 month ago 10
Python Question

assigning to a wrapped slice of a numpy array

I have a large image

A
and a smaller image
B
, both expressed as 2-D
numpy
arrays. I want to use
A
as the canvas, and write translated copies of
B
all over it, packed in a hexagonal arrangement. The part I can't get my head around is how to handle it such that the image wraps both vertically and horizontally—essentially what I want is regular tessellation of a (padded, as necessary) sub-image onto a torus.

I've seen the discussion of
numpy.take
and
numpy.roll
at
wrapping around slices in Python / numpy and that shows me how to access and return a copy of a wrapped slice of an array, but I want to assign to that—i.e., for arbitrary integers
rowOffset
and
columnOffset
I want to do the equivalent of:

A = numpy.zeros((5,11), int)
B = numpy.array([[1,2,3,4,5,6,7]]) * numpy.array([[10,100,1000]]).T
# OK, we wouldn't be able to fit more than one or two copies of B into A, but they demonstrate the wrapped placement problem

wrappedRowIndices = ( numpy.arange(B.shape[0]) + rowOffset ) % A.shape[0]
wrappedColumnIndices = ( numpy.arange(B.shape[1]) + columnOffset ) % A.shape[1]
A[ wrappedRowIndices, : ][ :, wrappedColumnIndices ] = B


I see from a comment on the question,
and from a moment's reflection on the way
numpy
arrays are represented, that there's no way a wrapped slice can be returned as a
view
in the way this demands.

Is there (Y) a way of assigning to wrapped slices of an array in this way, or (X) an existing utility for performing the kind of tessellation I'm trying to achieve?

Answer

np.put is a 1d equivalent to np.take:

In [1270]: A=np.arange(10)
In [1271]: np.take(A,[8,9,10,11],mode='wrapped')
Out[1271]: array([8, 9, 0, 1])
In [1272]: np.put(A,[8,9,10,11],[10,11,12,13],mode='wrapped')
In [1273]: A
Out[1273]: array([12, 13,  2,  3,  4,  5,  6,  7, 10, 11])
In [1274]: np.take(A,[8,9,10,11],mode='wrapped')
Out[1274]: array([10, 11, 12, 13])

Its docs suggest np.place and np.putmask (and np.copyto). I haven't used those much, but it might be possible to construct a mask, and rearrangement of B that would do the copy.

=================

Here's an experiment with place:

In [1313]: A=np.arange(24).reshape(4,6)
In [1314]: mask=np.zeros(A.shape,bool)
In [1315]: mask[:3,:4]=True
In [1316]: B=-np.arange(12).reshape(3,4)

So I have mask the same size as A, with a 'hole' the size of B.

I can roll both the mask and B, and place the values in A in a wrapped fashion.

In [1317]: np.place(A, np.roll(mask,-2,0), np.roll(B,1,0).flat)
In [1318]: A
Out[1318]: 
array([[ -8,  -9, -10, -11,   4,   5],
       [  6,   7,   8,   9,  10,  11],
       [  0,  -1,  -2,  -3,  16,  17],
       [ -4,  -5,  -6,  -7,  22,  23]])

And with 2d rolls

In [1332]: m=np.roll(np.roll(mask,-2,0),-1,1)
In [1333]: m
Out[1333]: 
array([[ True,  True,  True, False, False,  True],
       [False, False, False, False, False, False],
       [ True,  True,  True, False, False,  True],
       [ True,  True,  True, False, False,  True]], dtype=bool)
In [1334]: b=np.roll(np.roll(B,1,0),-1,1)
In [1335]: b
Out[1335]: 
array([[ -9, -10, -11,  -8],
       [ -1,  -2,  -3,   0],
       [ -5,  -6,  -7,  -4]])
In [1336]: A=np.zeros((4,6),int)
In [1337]: np.place(A, m, b.flat)
In [1338]: A
Out[1338]: 
array([[ -9, -10, -11,   0,   0,  -8],
       [  0,   0,   0,   0,   0,   0],
       [ -1,  -2,  -3,   0,   0,   0],
       [ -5,  -6,  -7,   0,   0,  -4]])