将值分配给numpy数组的封装切片

3
我有一张大图像A和一张小图像B,它们都表示为2-D的numpy数组。我想用A作为画布,在上面写入B的翻译副本,排列成六边形。我无法理解的部分是如何处理它,使得图像在垂直和水平方向上都能够包裹 - 本质上,我想要的是将(必要时填充)子图块规则地镶嵌到环面上。
我看到了Python / numpy中切片的包装讨论中对numpy.takenumpy.roll的讨论,并且那向我展示了如何访问和返回数组的包装切片的副本,但我想要赋值给它 - 即,对于任意的整数rowOffsetcolumnOffset,我想要执行以下操作:
  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 

我从问题的评论和对numpy数组表示方式的一瞬间反思中发现,无法以这种方式返回包装的切片作为所需的view

是否存在(Y)一种将数组的包装切片分配给这种方式,或者(X)执行我正在尝试实现的镶嵌类型的现有实用程序?


它怎么会是六边形的?B不是正方形吗? - maxymoo
我的意思是复制的B的中心将分布在A上打包六边形的角落,而不是B本身以某种方式呈六边形。我猜最容易处理的变体是B的尺寸小于六边形,这样就不会有重叠。 - jez
我猜如果你必须使用strides中的视图,你可以将列和行附加到A中,然后再进行索引,当然这要考虑内存限制。创建填充版本的方法如下:A1 = np.column_stack((A,A[:,:B.shape[1]-1])), A2 = np.row_stack((A1,A1[:B.shape[0]-1])) - Divakar
3个回答

3

np.putnp.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])

该文档建议使用np.placenp.putmask (以及np.copyto)。虽然我没有多少使用经验,但可能可以构造一个掩码和B的重新排列来完成复制。

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

下面是一个使用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)

所以我有一个与A相同大小的mask,其中有一个大小为B的“洞”。
我可以滚动mask和B,并以wrapped方式将值放置在A中。
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]])

使用2D滚动

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]])

“np.place(arr, mask, vals)”不就是“arr[mask] = vals”吗? - Eric
我们需要查看编译后的 _insert 做了什么。A[m]=b.flat 做了同样的事情。 - hpaulj
谢谢,这让我找到了通用解决方案的轨迹(请参见我的发布的答案)。 - jez

2

您当前的代码可以分解为__getitem____setitem__。正如您所指出的,__getitem__没有返回视图,因此__setitem__最终只会修改副本。

您需要在一个__setitem__(即一组括号)中完成整个操作:

A[wrappedRowIndices[:,np.newaxis], wrappedColumnIndices] = B

由于广播,这等同于:
A[wrappedRowIndices[:,np.newaxis], wrappedColumnIndices[np.newaxis,:]] = B

当使用多个数组进行索引时,规则如下:

# ... here is NOT the python Ellipsis!
y = x[a, b, c, ...]
y[i, j, ..] = x[a[i,j,...], b[i,j,...], ...]

实际上,这个功能有一个内置函数叫做np.ix_()

A[np.ix_(wrappedRowIndices, wrappedColumnIndices)] = B

将其推广到 ND,您会得到:

def place_wrapped(canvas, brush, position):
    assert canvas.ndim == brush.ndim == len(position)
    ind = np.ix_(*(
        (np.arange(b_dim) + shift) % c_dim
        for b_dim, c_dim, shift in zip(brush.shape, canvas.shape, position)
    ))
    canvas[ind] = brush


这个答案教会了我一个宝贵的东西,那就是你实际上可以只用一对方括号从一个数组中取矩形块,但你建议的赋值却失败了,出现了“ValueError:数组无法广播到正确的形状”的错误。 - jez
如果元素数量正确,您可以使用一个或两个“flat”来修复问题。 - hpaulj
我本以为您所说的规则需要[np.newaxis,:]而不是[:, np.newaxis]作为第二索引数组。但两种变体都可以(用于获取块的副本),并且它们在设置块内容时都会出现相同的ValueError。在 RHS 后添加.flat没有任何区别。在 LHS 后附加.flat可消除异常,但现在似乎我们又在使用__getitem__副本:A保持不变。 - jez
@jez:请看我的更新。我的切片是应该转置的。你可以通过比较 A[...].shapeB.shape 来发现这个问题。 - Eric

1
这里有一个函数可以根据hpaulj的回答解决我的问题Y。然而,这可能不是解决X的最有效方法,因为所有重活都由numpy.roll完成。
import numpy

def place_wrapped(canvas, brush, position):
    mask = numpy.zeros(canvas.shape, bool)
    mask[[slice(extent) for extent in brush.shape]] = True
    for axis, shift in enumerate(position):
        canvas_extent = canvas.shape[axis]
        brush_extent = brush.shape[axis]
        shift %= canvas_extent
        if shift:
            mask = numpy.roll(mask, shift, axis=axis)
            nwrapped = shift + brush_extent - canvas_extent
            if nwrapped > 0: brush = numpy.roll(brush, nwrapped, axis=axis)
    numpy.place(canvas, mask, brush.flat)


A = numpy.zeros((5,11), int)
B = numpy.array([[1,2,3,4,5,6,7]]) * numpy.array([[10,100,1000]]).T
print(B)

rowOffset = 3
columnOffset = 7
place_wrapped(A, B, (rowOffset, columnOffset))
print(A)

Eric的改进方法也有效,我想它肯定更加高效,因为它不需要复制任何数据:

def place_wrapped2(canvas, brush, position):
    ind = [
        ((numpy.arange(brush.shape[axis]) + shift) % canvas.shape[axis]).reshape([
            extent if i == axis else 1
            for i, extent in enumerate(brush.shape)
        ])
        for axis, shift in enumerate(position)
    ]
    canvas[ind] = brush

A *= 0 # reset
place_wrapped2(A, B, (rowOffset, columnOffset))
print(A)

请看我更正后的答案 - 不幸的是,你所有的尝试都没有解决问题。 - Eric

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接