numpy.view给出了价值误差

时间:2013-10-22 15:00:58

标签: python numpy

在libtiff保存文件的情况下进入这个,但现在我只是困惑。谁能告诉我为什么这两个不相同?

ar1 = zeros((1000,1000),dtype=uint16)
ar1 = ar1.view(dtype=uint8) # works

ar2 = zeros((1000,2000),dtype=uint16)
ar2 = ar2[:,1000:]
ar2 = ar2.view(dtype=uint8) # ValueError: new type not compatible with array.

编辑: 所以这也有效吗?

ar2 = zeros((1000,2000),dtype=uint16)
ar2 = array(ar2[:,1000:])
ar2 = ar2.view(dtype=uint8)

2 个答案:

答案 0 :(得分:3)

摘要

简而言之,只需在切片前移动视图。

而不是:

ar2 = zeros((1000,2000),dtype=uint16)
ar2 = ar2[:,1000:]
ar2 = ar2.view(dtype=uint8)

执行:

ar2 = zeros((1000,2000),dtype=uint16)
ar2 = ar2.view(dtype=uint8) # ar2 is now a 1000x4000 array...
ar2 = ar2[:,2000:] # Note the 2000 instead of 1000! 

正在发生的事情是切片数组不是连续的(如@Craig所指出的)和保守一侧的view错误,并且不会尝试重新解释非连续的内存缓冲区。 (在这种情况下,它恰好是可能的,但在某些情况下,它会导致非均匀跨越的数组,而numpy不允许这样做。)


如果您对[{1}}不是很熟悉,可能会误解numpy,而您确实需要view


astype做什么?

首先,让我们详细了解一下view的作用。在这种情况下,如果可能的话,它会将numpy数组的内存缓冲区重新解释为新的数据类型。这意味着当您使用视图时,数组中元素的数量通常会发生变化。 (您也可以使用它将数组视为view的不同子类,但我们暂时跳过该部分。)

您可能已经注意到以下情况(您的问题有点微妙),但如果没有,这是一个解释。

举个例子:

ndarray

如果您想使用新数据类型复制数组,请使用In [1]: import numpy as np In [2]: x = np.zeros(2, dtype=np.uint16) In [3]: x Out[3]: array([0, 0], dtype=uint16) In [4]: x.view(np.uint8) Out[4]: array([0, 0, 0, 0], dtype=uint8) In [5]: x.view(np.uint32) Out[5]: array([0], dtype=uint32)

astype

现在让我们来看看查看2D数组时会发生什么。

In [6]: x
Out[6]: array([0, 0], dtype=uint16)

In [7]: x.astype(np.uint8)
Out[7]: array([0, 0], dtype=uint8)

In [8]: x.astype(np.uint32)
Out[8]: array([0, 0], dtype=uint32)

请注意,数组的形状已更改,并且更改已沿最后一个轴发生(在这种情况下,已添加了额外的列)。

乍一看,似乎已经添加了额外的零。 正在插入额外的零,即In [9]: y = np.arange(4, dtype=np.uint16).reshape(2, 2) In [10]: y Out[10]: array([[0, 1], [2, 3]], dtype=uint16) In [11]: y.view(np.uint8) Out[12]: array([[0, 0, 1, 0], [2, 0, 3, 0]], dtype=uint8) 的{​​{1}}表示相当于两个uint16 s,其中一个值{{1}一个值为2的值。因此,低于255的任何uint8将导致该值和零,而超过该值的任何值将导致两个较小的2 s。举个例子:

0

幕后发生的事情

Numpy数组由一个“原始”内存缓冲区组成,它通过一个形状,一个dtype和一个strides(以及一个偏移量来解释),但现在让我们忽略它。有关更多详细信息,有几个好的概述:the official documentationthe numpy bookscipy-lectures

这允许numpy非常节省内存,并且可以通过许多不同方式“切片和切块”底层内存缓冲区而无需复制。

Strides告诉numpy在内存缓冲区内跳转多少字节沿特定轴增加一个增量。

例如:

uint16

因此,要在数组中更深一行,numpy需要在内存缓冲区中前进4个字节,而在数组中更远一列,numpy需要步骤2个字节。转置数组只是为了反转步幅(和形状,但在这种情况下,uint8是2x2):

In [13]: y * 100
Out[14]:
array([[  0, 100],
       [200, 300]], dtype=uint16)

In [15]: (y * 100).view(np.uint8)
Out[15]:
array([[  0,   0, 100,   0],
       [200,   0,  44,   1]], dtype=uint8)

当我们将数组视为In [17]: y Out[17]: array([[0, 1], [2, 3]], dtype=uint16) In [18]: y.strides Out[18]: (4, 2) 时,步幅会发生变化。我们仍然每行前进4个字节,但每列只有一个字节:

y

但是,numpy数组必须具有每个维度的一个步长。这就是“均匀跨越”的意思。换句话说,向前移动一行/列/无论如何,numpy需要能够每次通过底层内存缓冲区步进相同的量。换句话说,没有办法告诉numpy为每一行/列/其他任何步骤设置不同的金额。

出于这个原因,In [19]: y.T.strides Out[19]: (2, 4) 采取了非常保守的路线。如果数组不连续,并且视图将更改数组的形状和步幅,则不会尝试处理它。正如@Craig所指出的那样,这是因为uint8的切片与In [20]: y.view(np.uint8).strides Out[20]: (4, 1) 不起作用是不连续的。

有很多情况(你的是一个),结果数组是有效的,但view方法不会试图太聪明。

要真正发挥可能性,可以使用y或直接使用__array_interface__。这是一个很好的学习工具来试验它,但你必须真正理解你正在做些什么才能有效地使用它。

希望无论如何都有所帮助!对不起啰嗦的答案!

答案 1 :(得分:1)

这不是一个完整的答案,但可能指出我错过的细节。制作数组切片时,您不再拥有连续数据,也不拥有数据。要看这个看数组的标志:

ar2 = zeros((1000,2000),dtype=uint16)
ar2.flags

  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : True
  WRITEABLE : True
  ALIGNED : True
  UPDATEIFCOPY : False

ar2 = ar2[:,1000:]
ar2.flags

  C_CONTIGUOUS : False
  F_CONTIGUOUS : False
  OWNDATA : False
  WRITEABLE : True
  ALIGNED : True
  UPDATEIFCOPY : False

我不知道其中哪一个导致实际问题。正如您在编辑中所注意到的,如果您创建切片数组的新副本,那么事情就可以了。您可以在注意时使用array()或类似ar2=ar2[:,1000:].copy()

这样做