优化Numpy欧式距离和方向函数

时间:2018-09-21 04:44:49

标签: python numpy

我试图从一个numpy数组中的源坐标计算欧几里得距离和方向。

图形示例 Example of the desired output

这是我能想到的,但是对于大型阵列来说,速度相对较慢。基于源坐标的欧几里得距离和方向在很大程度上依赖于每个像元的索引。这就是为什么我要遍历每一行和每一列。我研究了scipy cdist,pdist和np linalg。

import numpy as np
from math import atan, degrees, sqrt
from timeit import default_timer

def euclidean_from_source(input_array, y_index, x_index):
    # copy arrays
    distance = np.empty_like(input_array, dtype=float)
    direction = np.empty_like(input_array, dtype=int)

    # loop each row
    for i, row in enumerate(X):
        # loop each cell
        for c, cell in enumerate(row):
            # get b
            b = x_index - i
            # get a
            a = y_index - c

            hypotenuse = sqrt(a * a + b * b) * 10
            distance[i][c] = hypotenuse
            direction[i][c] = get_angle(a, b)

    return [distance, direction]

def calibrate_angle(a, b, angle):
    if b > 0 and a > 0:
        angle+=90
    elif b < 0 and a < 0:
        angle+=270
    elif b > 0 > a:
        angle+=270
    elif a > 0 > b:
        angle+=90
    return angle

def get_angle(a, b):
    # get angle
    if b == 0 and a == 0:
        angle = 0
    elif b == 0 and a >= 0:
        angle = 90
    elif b == 0 and a < 0:
        angle = 270
    elif a == 0 and b >= 0:
        angle = 180
    elif a == 0 and b < 0:
        angle = 360
    else:
        theta = atan(b / a)
        angle = degrees(theta)

    return calibrate_angle(a, b, angle)

if __name__ == "__main__":
    dimension_1 = 5
    dimension_2 = 5

    X = np.random.rand(dimension_1, dimension_2)
    y_index = int(dimension_1/2)
    x_index = int(dimension_2/2)

    start = default_timer()
    distance, direction = euclidean_from_source(X, y_index, x_index)
    print('Total Seconds {{'.format(default_timer() - start))

    print(distance)
    print(direction)

更新 我能够使用广播功能以所需的速度完成我所需要的一切。但是我仍在弄清楚如何在整个矩阵中将角度校准为0、360(在这种情况下,取模将无效)。

import numpy as np
from math import atan, degrees, sqrt
from timeit import default_timer


def euclidean_from_source_update(input_array, y_index, x_index):
    size = input_array.shape
    center = (y_index, x_index)

    x = np.arange(size[0])
    y = np.arange(size[1])

    # use broadcasting to get euclidean distance from source point
    distance = np.multiply(np.sqrt((x - center[0]) ** 2 + (y[:, None] - center[1]) ** 2), 10)

    # use broadcasting to get euclidean direction from source point
    direction = np.rad2deg(np.arctan2((x - center[0]) , (y[:, None] - center[1])))

    return [distance, direction]

def euclidean_from_source(input_array, y_index, x_index):
    # copy arrays
    distance = np.empty_like(input_array, dtype=float)
    direction = np.empty_like(input_array, dtype=int)

    # loop each row
    for i, row in enumerate(X):
        # loop each cell
        for c, cell in enumerate(row):
            # get b
            b = x_index - i
            # get a
            a = y_index - c

            hypotenuse = sqrt(a * a + b * b) * 10
            distance[i][c] = hypotenuse
            direction[i][c] = get_angle(a, b)
    return [distance, direction]

def calibrate_angle(a, b, angle):
    if b > 0 and a > 0:
        angle+=90
    elif b < 0 and a < 0:
        angle+=270
    elif b > 0 > a:
        angle+=270
    elif a > 0 > b:
        angle+=90
    return angle

def get_angle(a, b):
    # get angle
    if b == 0 and a == 0:
        angle = 0
    elif b == 0 and a >= 0:
        angle = 90
    elif b == 0 and a < 0:
        angle = 270
    elif a == 0 and b >= 0:
        angle = 180
    elif a == 0 and b < 0:
        angle = 360
    else:
        theta = atan(b / a)
        angle = degrees(theta)

    return calibrate_angle(a, b, angle)

if __name__ == "__main__":
    dimension_1 = 5
    dimension_2 = 5

    X = np.random.rand(dimension_1, dimension_2)
    y_index = int(dimension_1/2)
    x_index = int(dimension_2/2)

    start = default_timer()
    distance, direction = euclidean_from_source(X, y_index, x_index)
    print('Total Seconds {}'.format(default_timer() - start))

    start = default_timer()
    distance2, direction2 = euclidean_from_source_update(X, y_index, x_index)
    print('Total Seconds {}'.format(default_timer() - start))

    print(distance)
    print(distance2)

    print(direction)
    print(direction2)

更新2 感谢大家的反馈,在测试方法之后,这两种方法是最快的,并且产生了我需要的结果。我仍然愿意接受你们能想到的任何优化。

def get_euclidean_direction(input_array, y_index, x_index):
    rdist = np.arange(input_array.shape[0]).reshape(-1, 1) - x_index
    cdist = np.arange(input_array.shape[1]).reshape(1, -1) - y_index
    direction = np.mod(np.degrees(np.arctan2(rdist, cdist)), 270)

    direction[y_index:, :x_index]+= -90
    direction[y_index:, x_index:]+= 270
    direction[y_index][x_index] = 0

    return direction

def get_euclidean_distance(input_array, y_index, x_index):
    size = input_array.shape
    center = (y_index, x_index)

    x = np.arange(size[0])
    y = np.arange(size[1])
    return np.multiply(np.sqrt((x - center[0]) ** 2 + (y[:, None] - center[1]) ** 2), 10)

2 个答案:

答案 0 :(得分:2)

此操作非常容易矢量化。一方面,ab根本不需要以2D计算,因为它们仅取决于数组中的一个方向。可以使用np.hypot计算距离。广播会将形状转换为正确的2D形式。

您的角度函数几乎完全等同于将np.degrees应用于np.arctan2

不清楚为什么用x标记行,而用y标记行,而不是用标准的方式标记行,但是只要保持一致,就可以了。

这是向量化版本:

def euclidean_from_source(input_array, c, r):
    rdist = np.arange(input_array.shape[0]).reshape(-1, 1) - r
    # Broadcasting doesn't require this second reshape
    cdist = np.arange(input_array.shape[1]).reshape(1, -1) - c
    distance = np.hypot(rdist, cdist) * 10
    direction = np.degrees(np.arctan2(rdist, cdist))
    return distance, direction

我将把它留给读者练习,以确定是否需要任何其他处理来微调角度,如果需要,以矢量化的方式实现。

答案 1 :(得分:1)

通过数组或元组传递想要度量的cordrints可能会更容易。另外,尽管可能需要更多的内存,但我认为使用np.indices可能会更快一些(因为它允许np.einsum发挥其神奇作用)。

def euclidean_from_source(input_array, coord):    
    grid = np.indices(input_array.shape)
    grid -= np.asarray(coord)[:, None, None]
    distance = np.einsum('ijk, ijk -> jk', grid, grid) ** .5
    direction = np.degrees(np.arctan2(grid[0], grid[1]))
    return distance, direction

该方法还可以扩展到n-d(尽管显然角度计算会有些棘手