Python 在给定方向的三维阵列中查找连接的图元(管路)

Python 在给定方向的三维阵列中查找连接的图元(管路),python,numpy,Python,Numpy,我有一个3D阵列,例如: array = np.array([[[ 1., 1., 0.], [ 0., 0., 0.], [ 0., 0., 0.]], [[1., 0., 0.], [ 0., 0., 0.], [ 0., 0., 0.]], [[

我有一个3D阵列,例如:

array = np.array([[[ 1.,  1.,  0.],
                   [ 0.,  0.,  0.],
                   [ 0.,  0.,  0.]],
                  [[1.,  0.,  0.],
                   [ 0.,  0.,  0.],
                   [ 0.,  0.,  0.]],
                  [[ 1.,  0.,  0.],
                   [ 0.,  0.,  0.],
                   [ 0.,  0.,  0.]]])
我需要找到不同方向的每种可能尺寸的连接线。 例如,在水平方向(1,0,0)上,输出应为:

> **Runs of size 1: 2
> **Runs of size 2: 1
> **Runs of size 3: 0
> [[ 2.  4.  1.]
   [ 1.  0.  0.]
   [ 0.  0.  1.]]
> [[ 1.  4.  2.]
   [ 0.  0.  1.]
   [ 1.  0.  0.]]
在方向(0,1,0)上,沿垂直轴:

> **Runs of size 1: 4
> **Runs of size 2: 0
> **Runs of size 3: 0
在方向(0,0,1)上,沿z轴:

> **Runs of size 1: 1
> **Runs of size 2: 0
> **Runs of size 3: 1
有没有一种有效的方法来实现这一点

编辑:

我附上我正在研究的解决方案:

dire = np.array(([1, 0, 0], [0, 1, 0], [0, 0, 1])) # Possible directions
results = np.zeros((array.shape[0], len(dire)))  # Maximum runs will be 3

# Find all indices
indices = np.argwhere(array == 1) 

# Loop over each direction                 
for idire, dire in enumerate(dire):
    results[0, idire] = np.count_nonzero(array) # Count all 1 (conection 0)
    indices_to_compare = indices    

# Now we compare the list of indices with a displaced list of indices
    for irun in range(1, array.shape[0]):   
        indices_displaced = indices + dire*irun             
        aset = set([tuple(x) for x in indices_displaced])
        bset = set([tuple(x) for x in indices_to_compare])
        indices_to_compare = (np.array([x for x in aset & bset]))
        results[irun, idire] = len(indices_to_compare)

    # Rest the counts off bigger runs to the smaller ones, for duplicates
    for indx in np.arange(array.shape[0]-2,-1,-1):
        results[indx, idire] -=            
        np.sum(results[np.arange(array.shape[0]-1,indx,-1), idire])


print(results) # Each column is a direction, each row is the number of bins.

> [[ 2.  4.  3.]
   [ 1.  0.  1.]
   [ 1.  0.  0.]]
到目前为止,此代码不起作用,仅适用于方向(0,1,0),该方向没有任何连接,并且非常简单。使用此符号,预期输出应为:

> **Runs of size 1: 2
> **Runs of size 2: 1
> **Runs of size 3: 0
> [[ 2.  4.  1.]
   [ 1.  0.  0.]
   [ 0.  0.  1.]]
> [[ 1.  4.  2.]
   [ 0.  0.  1.]
   [ 1.  0.  0.]]
数组的比较来自

编辑2:

我在解释坐标时出错,似乎除了
np.argwhere
的结果外,(1,0,0)在z轴上,因此预期结果应该是:

> **Runs of size 1: 2
> **Runs of size 2: 1
> **Runs of size 3: 0
> [[ 2.  4.  1.]
   [ 1.  0.  0.]
   [ 0.  0.  1.]]
> [[ 1.  4.  2.]
   [ 0.  0.  1.]
   [ 1.  0.  0.]]

以下是沿轴向工作的矢量化解决方案:

def run_lengths(arr, axis):
    # convert to boolean, for speed - no need to store 0 and 1 as floats
    arr = arr.astype(bool)

    # move the axis in question to the start, to make the slicing below easy
    arr = np.moveaxis(arr, axis, 0)

    # find positions at the start and end of a run
    first = np.empty(arr.shape, dtype=bool)
    first[:1] = arr[:1]
    first[1:] = arr[1:] & ~arr[:-1]
    last = np.zeros(arr.shape, dtype=bool)
    last[-1:] = arr[-1]
    last[:-1] = arr[:-1] & ~arr[1:]

    # count the number in each run
    c = np.cumsum(arr, axis=0)
    lengths = c[last] - c[first]

    # group the runs by length. Note the above gives 0 for a run of length 1,
    # so result[i] is the number of runs of length (i + 1)
    return np.bincount(lengths, minlength=len(arr))

for i in range(3):
    print(run_lengths(array, axis=i))

这是一个在任何方向和方向组合上都有效的解决方案。本质上,我们使用数组创建一个图,其中真实元素对应于节点,如果在任何允许的方向上存在节点,则创建边。然后我们计算连接的组件及其大小。用于在图形中查找连接的组件,该图形可通过pip轻松安装

 import numpy as np
 import networkx

 def array_to_graph(bool_array, allowed_steps):
     """
     Arguments:
     ----------
     bool_array    -- boolean array
     allowed_steps -- list of allowed steps; e.g. given a 2D boolean array,
                      [(0, 1), (1, 1)] signifies that from coming from element
                      (i, j) only elements (i, j+1) and (i+1, j+1) are reachable

     Returns:
     --------
     g               -- networkx.DiGraph() instance
     position_to_idx -- dict mapping (i, j) position to node idx
     idx_to_position -- dict mapping node idx to (i, j) position
     """

     # ensure that bool_array is boolean
     assert bool_array.dtype == np.bool, "Input array has to be boolean!"

     # map array indices to node indices and vice versa
     node_idx = range(np.sum(bool_array))
     node_positions = zip(*np.where(bool_array))
     position_to_idx = dict(zip(node_positions, node_idx))

     # create graph
     g = networkx.DiGraph()
     for source in node_positions:
         for step in allowed_steps: # try to step in all allowed directions
             target = tuple(np.array(source) + np.array(step))
             if target in position_to_idx:
                 g.add_edge(position_to_idx[source], position_to_idx[target])

     idx_to_position = dict(zip(node_idx, node_positions))

     return g, idx_to_position, position_to_idx

 def get_connected_component_sizes(g):
     component_generator = networkx.connected_components(g)
     component_sizes = [len(component) for component in component_generator]
     return component_sizes

 def test():
     array = np.array([[[ 1.,  1.,  0.],
                        [ 0.,  0.,  0.],
                        [ 0.,  0.,  0.]],
                       [[1.,  0.,  0.],
                        [ 0.,  0.,  0.],
                        [ 0.,  0.,  0.]],
                       [[ 1.,  0.,  0.],
                        [ 0.,  0.,  0.],
                        [ 0.,  0.,  0.]]], dtype=np.bool)

     directions = [(1,0,0), (0,1,0), (0,0,1)]

     for direction in directions:
         graph, _, _ = array_to_graph(array, [direction])
         sizes = get_connected_component_sizes(graph.to_undirected())
         counts = np.bincount(sizes)

         print direction
         for ii, c in enumerate(counts):
             if ii > 1:
                 print "Runs of size {}: {}".format(ii, c)

     return

注意:“1号跑步”不算(正确),但我想你对它们也不感兴趣。否则,您可以通过计算特定方向上真实元素的总数并减去运行次数乘以运行长度来轻松地事后计算它们

我添加了一个我以前使用的代码变体,现在它似乎可以工作了:

import numpy as np


array = np.array([[[ 1.,  1.,  0.],
                   [ 0.,  0.,  0.],
                   [ 0.,  0.,  0.]],
                  [[1.,  0.,  0.],
                   [ 0.,  0.,  0.],
                   [ 0.,  0.,  0.]],
                  [[ 1.,  0.,  0.],
                   [ 0.,  0.,  0.],
                   [ 0.,  0.,  0.]]])

dire = np.array(([1, 0, 0], [0, 1, 0], [0, 0, 1])) # Possible directions
results = np.zeros((array.shape[0], len(dire)))  # Maximum runs will be 3

# Find all indices
indices = np.argwhere(array == 1) 

# Loop over each direction                 
for idire, dire in enumerate(dire):
    results[0, idire] = len(indices) # Count all 1 (conection 0)
    indices_to_compare = indices 

    for irun in range(1, array.shape[0]):
        print('dir:',dire, 'run', irun)
        indices_displaced = indices + dire*irun             
        aset = set([tuple(x) for x in indices_displaced])
        bset = set([tuple(x) for x in indices_to_compare])
        indices_to_compare = (np.array([x for x in aset & bset]))
        results[irun, idire] = len(indices_to_compare)


    for iindx in np.arange(array.shape[0]-2,-1,-1):
        for jindx in np.arange(iindx+1, array.shape[0]):
            print(iindx,jindx,  results[jindx, idire])
            results[iindx, idire] -=  results[jindx, idire] *(jindx-iindx+1)         


print('\n',results)

array.tranpose()可能很有用。也就是说,您应该显示到目前为止的所有尝试。这些是最大运行次数吗,因为dims上未减少的每个元素都会有一些运行值?向我们展示了您的最大努力,也许是一个帮助我们了解问题的循环代码?我真的被这个问题困住了。我试着从循环开始,但我能找到是否有1个连接,但没有连接的大小。到目前为止,我的代码不值得发布。如果方向是
(-1,0,1)
,该怎么办?您是否需要支持非轴向方向?如果没有,一个更简单的api就是传递
axis=n
Yes,理论上所有方向都是可能的
zip(*np.where(…)
拼写更好
argwhere
,然后你可以将
map(operator.add,source,step)
更改为
source+np.array(step)
我需要做另一个字典查找:
g.add\u-edge(position_to_idx[source],position_to_idx[target])
和数组是不可散列的……(虽然我原则上同意行很难看)。对,您仍然需要元组转换,但您至少可以放弃
映射,它在我的计算机中不起作用。可变大小(和计数)是空的。奇怪。在创建图形后,您可以添加以下行:
打印列表(graph.nodes())、列表(graph.edges())
并告诉我输出结果吗?您正在运行哪个python版本?