【问题标题】:Intersect multiple 2D np arrays for determining zones相交多个 2D np 阵列以确定区域
【发布时间】:2018-06-10 15:22:42
【问题描述】:

使用这个可重现的小示例,到目前为止,我无法从 3 个包含所有三个输入数组的唯一分组的数组中生成一个新的整数数组。

数组与地形属性有关:

import numpy as np
asp = np.array([8,1,1,2,7,8,2,3,7,6,4,3,6,5,5,4]).reshape((4,4))  #aspect
slp = np.array([9,10,10,9,9,12,12,9,10,11,11,9,9,9,9,9]).reshape((4,4))  #slope
elv = np.array([13,14,14,13,14,15,16,14,14,15,16,14,13,14,14,13]).reshape((4,4)) #elevation

这个想法是使用 GIS 例程将地理轮廓分为 3 个不同的属性:

  • 1-8 表示坡向(1 = 朝北,2 = 朝东北等)
  • 9-12 用于坡度(9=平缓坡度...12=最陡坡度)
  • 13-16 表示海拔(13=最低海拔...16=最高海拔)

下面的小图试图描述我所追求的结果(数组显示在左下方)。请注意,图中给出的“答案”只是一种可能的答案。我不关心结果数组中整数的最终排列,只要最终数组在每个行/列索引处包含一个整数,用于标识唯一分组。

例如,[0,1] 和 [0,2] 处的数组索引具有相同的坡向、坡度和高程,因此在结果数组中接收相同的整数标识符。

numpy 是否有针对此类事情的内置例程?

【问题讨论】:

标签: python numpy multidimensional-array numpy-ndarray


【解决方案1】:

网格中的每个位置都与一个由一个值组成的元组相关联 aspslpelv。例如,左上角有元组(8,9,13)。 我们想将此元组映射到一个唯一标识此元组的数字。

一种方法是将(8,9,13) 视为 3D 数组的索引 np.arange(9*13*17).reshape(9,13,17)。选择了这个特定的数组 容纳aspslpelv 中的最大值:

In [107]: asp.max()+1
Out[107]: 9

In [108]: slp.max()+1
Out[108]: 13

In [110]: elv.max()+1
Out[110]: 17

现在我们可以将元组 (8,9,13) 映射到数字 1934:

In [113]: x = np.arange(9*13*17).reshape(9,13,17)

In [114]: x[8,9,13]
Out[114]: 1934

如果我们对网格中的每个位置都执行此操作,那么我们会为每个位置获得一个唯一编号。 我们可以在这里结束,让这些唯一的数字作为标签。

或者,我们可以生成更小的整数标签(从 0 开始并增加 1) 通过使用np.unique with return_inverse=True:

uniqs, labels = np.unique(vals, return_inverse=True)
labels = labels.reshape(vals.shape)

例如,

import numpy as np

asp = np.array([8,1,1,2,7,8,2,3,7,6,4,3,6,5,5,4]).reshape((4,4))  #aspect
slp = np.array([9,10,10,9,9,12,12,9,10,11,11,9,9,9,9,9]).reshape((4,4))  #slope
elv = np.array([13,14,14,13,14,15,16,14,14,15,16,14,13,14,14,13]).reshape((4,4)) #elevation

x = np.arange(9*13*17).reshape(9,13,17)
vals = x[asp, slp, elv]
uniqs, labels = np.unique(vals, return_inverse=True)
labels = labels.reshape(vals.shape)

产量

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

只要aspslpelv 中的值是小整数,上述方法就可以正常工作。如果整数太大,它们的最大值的乘积可能会溢出可以传递给np.arange 的最大允许值。此外,生成如此大的数组将是低效的。 如果这些值是浮点数,则它们不能被解释为 3D 数组 x 的索引。

所以为了解决这些问题,首先使用np.uniqueaspslpelv中的值转换为唯一的整数标签:

indices = [ np.unique(arr, return_inverse=True)[1].reshape(arr.shape) for arr in [asp, slp, elv] ]
M = np.array([item.max()+1 for item in indices])
x = np.arange(M.prod()).reshape(M)
vals = x[indices]
uniqs, labels = np.unique(vals, return_inverse=True)
labels = labels.reshape(vals.shape)

产生与上图相同的结果,但即使 aspslpelv 是浮点数和/或大整数也可以。


最后,我们可以避免np.arange的生成:

x = np.arange(M.prod()).reshape(M)
vals = x[indices]

通过将vals 计算为索引和步幅的乘积:

M = np.r_[1, M[:-1]]
strides = M.cumprod()
indices = np.stack(indices, axis=-1)
vals = (indices * strides).sum(axis=-1)

所以把它们放在一起:

import numpy as np

asp = np.array([8,1,1,2,7,8,2,3,7,6,4,3,6,5,5,4]).reshape((4,4))  #aspect
slp = np.array([9,10,10,9,9,12,12,9,10,11,11,9,9,9,9,9]).reshape((4,4))  #slope
elv = np.array([13,14,14,13,14,15,16,14,14,15,16,14,13,14,14,13]).reshape((4,4)) #elevation

def find_labels(*arrs):
    indices = [np.unique(arr, return_inverse=True)[1] for arr in arrs]
    M = np.array([item.max()+1 for item in indices])
    M = np.r_[1, M[:-1]]
    strides = M.cumprod()
    indices = np.stack(indices, axis=-1)
    vals = (indices * strides).sum(axis=-1)
    uniqs, labels = np.unique(vals, return_inverse=True)
    labels = labels.reshape(arrs[0].shape)
    return labels

print(find_labels(asp, slp, elv))

# [[ 3  7  7  0]
#  [ 6 10 12  4]
#  [ 8  9 11  4]
#  [ 2  5  5  1]]

【讨论】:

  • 一个很好的实现并且非常快速。它还使直方图的生成更加容易。
【解决方案2】:

这可以使用numpy.unique() 完成,然后使用如下映射:

代码:

combined = 10000 * asp + 100 * slp + elv
unique = dict(((v, i + 1) for i, v in enumerate(np.unique(combined))))
combined_unique = np.vectorize(unique.get)(combined)

测试代码:

import numpy as np

asp = np.array([8, 1, 1, 2, 7, 8, 2, 3, 7, 6, 4, 3, 6, 5, 5, 4]).reshape((4, 4))  # aspect
slp = np.array([9, 10, 10, 9, 9, 12, 12, 9, 10, 11, 11, 9, 9, 9, 9, 9]).reshape((4, 4))  # slope
elv = np.array([13, 14, 14, 13, 14, 15, 16, 14, 14, 15, 16, 14, 13, 14, 14, 13]).reshape((4, 4))

combined = 10000 * asp + 100 * slp + elv
unique = dict(((v, i + 1) for i, v in enumerate(np.unique(combined))))
combined_unique = np.vectorize(unique.get)(combined)

print(combined_unique)

结果:

[[12  1  1  2]
 [10 13  3  4]
 [11  9  6  4]
 [ 8  7  7  5]]

【讨论】:

    【解决方案3】:

    这似乎与标记图像中的唯一区域类似的问题。这是我为此编写的一个函数,但您首先需要将 3 个数组连接到 1 个 3D 数组。

    def labelPix(pix):
        height, width, _ = pix.shape
        pixRows = numpy.reshape(pix, (height * width, 3))
        unique, counts = numpy.unique(pixRows, return_counts = True, axis = 0)
    
        unique = [list(elem) for elem in unique]
    
        labeledPix = numpy.zeros((height, width), dtype = int)
        offset = 0
        for index, zoneArray in enumerate(unique):
            index += offset
            zone = list(zoneArray)
            zoneArea = (pix == zone).all(-1)
            elementsArray, numElements = scipy.ndimage.label(zoneArea)
    
            elementsArray[elementsArray!=0] += offset
    
            labeledPix[elementsArray!=0] = elementsArray[elementsArray!=0]
    
            offset += numElements
    
        return labeledPix
    

    这将标记唯一的 3 值组合,同时还将单独的标签分配给具有相同 3 值组合但彼此不接触的区域。

    asp = numpy.array([8,1,1,2,7,8,2,3,7,6,4,3,6,5,5,4]).reshape((4,4))  #aspect
    slp = numpy.array([9,10,10,9,9,12,12,9,10,11,11,9,9,9,9,9]).reshape((4,4))  #slope
    elv = numpy.array([13,14,14,13,14,15,16,14,14,15,16,14,13,14,14,13]).reshape((4,4)) #elevation
    
    pix = numpy.zeros((4,4,3))
    pix[:,:,0] = asp
    pix[:,:,1] = slp
    pix[:,:,2] = elv
    
    print(labelPix(pix))
    

    返回:

    [[ 0  1  1  2]
     [10 12  3  4]
     [11  9  6  4]
     [ 8  7  7  5]]
    

    【讨论】:

      【解决方案4】:

      这是一个使用 itertools.groupby 的简单 Python 技术。它要求输入是一维列表,但这不应该是一个主要问题。策略是将列表与索引号一起压缩,然后对结果列进行排序。然后,我们将相同的列分组在一起,在比较列时忽略索引号。然后我们从每个组中收集索引号,并使用它们来构建最终的输出列表。

      from itertools import groupby
      
      def show(label, seq):
          print(label, ' '.join(['{:2}'.format(u) for u in seq]))
      
      asp = [8, 1, 1, 2, 7, 8, 2, 3, 7, 6, 4, 3, 6, 5, 5, 4] 
      slp = [9, 10, 10, 9, 9, 12, 12, 9, 10, 11, 11, 9, 9, 9, 9, 9] 
      elv = [13, 14, 14, 13, 14, 15, 16, 14, 14, 15, 16, 14, 13, 14, 14, 13]
      
      size = len(asp)
      a = sorted(zip(asp, slp, elv, range(size)))
      groups = sorted([u[-1] for u in g] for _, g in groupby(a, key=lambda t:t[:-1]))
      final = [0] * size
      for i, g in enumerate(groups, 1):
          for j in g:
              final[j] = i
      
      show('asp', asp)
      show('slp', slp)
      show('elv', elv)
      show('out', final)
      

      输出

      asp  8  1  1  2  7  8  2  3  7  6  4  3  6  5  5  4
      slp  9 10 10  9  9 12 12  9 10 11 11  9  9  9  9  9
      elv 13 14 14 13 14 15 16 14 14 15 16 14 13 14 14 13
      out  1  2  2  3  4  5  6  7  8  9 10  7 11 12 12 13
      

      没有必要进行第二次排序,我们可以使用普通的列表组合

      groups = [[u[-1] for u in g] for _, g in groupby(a, key=lambda t:t[:-1])]
      

      或生成器表达式

      groups = ([u[-1] for u in g] for _, g in groupby(a, key=lambda t:t[:-1]))
      

      我这样做只是为了让我的输出与问题中的输出相匹配。

      【讨论】:

        【解决方案5】:

        这是使用基于字典的查找来解决此问题的一种方法。

        from collections import defaultdict
        import itertools
        
        group_dict = defaultdict(list)
        idx_count = 0
        
        for a, s, e in np.nditer((asp, slp, elv)):
             asp_tuple = (a.tolist(), s.tolist(), e.tolist())
             if asp_tuple not in group_dict:
                 group_dict[asp_tuple] = [idx_count+1]
                 idx_count += 1
             else:
                 group_dict[asp_tuple].append(group_dict[asp_tuple][-1])
        
        
        list1d = list(itertools.chain(*list(group_dict.values())))
        
        np.array(list1d).reshape(4, 4)
        
        # result 
        array([[ 1,  2,  2,  3],
               [ 4,  5,  6,  7],
               [ 7,  8,  9, 10],
               [11, 12, 12, 13]])
        

        【讨论】:

          猜你喜欢
          • 2019-07-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-07-02
          • 2021-01-29
          • 2018-05-07
          • 1970-01-01
          • 2013-06-02
          相关资源
          最近更新 更多