从可能不完整的候选列表构建2D网格

dei*_*aur 5 python numpy

问题

我需要使用一组候选位置(X和中的值Y)构建一个2D网格.然而,可能存在应该被过滤掉的假阳性候选者,以及假阴性(其中需要针对给定周围位置值的预期位置创建位置).可以预期网格的行和列是直的,并且旋转(如果小的话).

此外,我没有关于(0,0)网格位置的可靠信息.但我知道:

grid_size = (4, 4)

expected_distance = 105
Run Code Online (Sandbox Code Playgroud)

(例外距离只是网格点之间间距的粗略估计,应允许在10%的范围内变化).

示例数据

这是理想的数据,没有误报,也没有漏报.该算法需要能够处理删除多个数据点并添加错误的数据点.

X = np.array([61.43283582, 61.56626506, 62.5026738,   65.4028777, 167.03030303, 167.93965517, 170.82191781, 171.37974684, 272.02884615, 272.91089109, 274.1031746, 274.22891566, 378.81553398, 379.39534884, 380.68181818, 382.67164179])

Y = np.array([55.14427861, 160.30120482, 368.80213904, 263.12230216, 55.1030303, 263.64655172, 162.67123288, 371.36708861, 55.59615385, 264.64356436, 368.20634921, 158.37349398, 54.33980583, 160.55813953,  371.72727273,  266.68656716])
Run Code Online (Sandbox Code Playgroud)

以下函数评估候选项并返回两个词典.

第一个具有每个候选位置(作为2长度元组)作为键和值是位于右侧和下方的位置的2长度元组(使用来自图像如何显示的逻辑).这些邻居本身要么是2长的元组坐标,要么是a None.

第二个字典是第一个字典的反向查找,使得每个候选者(位置)具有支持它的其他候选者位置的列表.

import numpy as np
from collections import defaultdict

def get_neighbour_grid(X, Y, expect_dist=(105, 105)):

    t1 = (expect_dist[0] + expect_dist[1]) / 2.0 * 0.9
    t2 = t1 * 1.222

    def neighbours(x, y):

        nRight = None
        ideal = x + expect_dist[0]
        D = np.sqrt((X - ideal)**2 + (Y - y)**2)
        candidate = (X[D.argmin()], Y[D.argmin()])
        if candidate != (x, y) and x + t2 > candidate[0] > x + t1:
            nRight = candidate

        nBelow = None
        ideal = y + expect_dist[0]
        D = np.sqrt((X - x)**2 + (Y - ideal)**2)
        candidate = (X[D.argmin()], Y[D.argmin()])
        if candidate != (x, y) and y + t2 > candidate[1] > y + t1:
            nBelow = candidate

        return nRight, nBelow

    right_below_neighbours = dict()
    def _default_val(*args):
        return list()
    reverse_lookup = defaultdict(_default_val)

    for pos in np.arange(X.size):

        pos_tuple = (X[pos], Y[pos])
        n  = neighbours(*pos_tuple)
        right_below_neighbours[pos_tuple] = n
        reverse_lookup[n[0]].append(pos_tuple)
        reverse_lookup[n[1]].append(pos_tuple)

    return right_below_neighbours, reverse_lookup
Run Code Online (Sandbox Code Playgroud)

这是我被卡住的地方:

如何使用这些字典和/或XY构建最支持网格?

我有一个想法,从2个邻居支持的较低的,最右边的候选者开始,并使用reverse_lookup字典迭代地创建网格.但是这种设计有几个缺陷,最明显的是我不能指望找到较低的,最右边的候选人和它的支持邻居.

代码,虽然它不会运行,因为当我意识到它是多么有问题时,我放弃它(pre_grid = right_below_neighbours):

def build_grid(pre_grid, reverse_lookup, grid_shape=(4, 4)):

    def _default_val(*args):
        return 0

    grid_pos_support = defaultdict(_default_val)
    unsupported = 0

    for l, b in pre_grid.values():

        if l is not None:
            grid_pos_support[l] += 1
        else:
            unsupported += 1
        if b is not None:
            grid_pos_support[b] += 1
        else:
            unsupported += 1

    well_supported = list()
    for pos in grid_pos_support:
        if grid_pos_support[pos] >= 2:
            well_supported.append(pos)

    well_A = np.asarray(well_supported)
    ur_pos = well_A[well_A.sum(axis=1).argmax()]

    grid = np.zeros(grid_shape + (2,), dtype=np.float)
    grid[-1,-1,:] = ur_pos

    def _iter_build_grid(pos, ref_pos=None):

        isX = pre_grid[tuple(pos)][0] == ref_pos
        if ref_pos is not None:
            oldCoord = map(lambda x: x[0], np.where(grid == ref_pos)[:-1])
            myCoord = (oldCoord[0] - int(isX), oldCoord[1] - int(not isiX))

        for p in reverse_lookup[tuple(pos)]:

            _iter_build_grid(p, pos)

    _iter_build_grid(ur_pos)

    return grid
Run Code Online (Sandbox Code Playgroud)

第一部分可能很有用,因为它总结了对每个职位的支持.它还显示了我需要的最终输出(grid):

具有2个第一维度的3D阵列,网格的形状和具有长度2的第3维度(对于每个位置的x坐标和y坐标).

概括

所以我意识到我的尝试是如何无用的,但我不知道如何对所有候选人进行全局评估,并在任何适合的地方使用候选人的x和y值放置最受支持的网格.我希望这是一个非常复杂的问题,我真的不希望任何人给出一个完整的解决方案(尽管它会很棒),但任何类型的算法或numpy/scipy函数都可以使用的暗示非常感谢.

最后,抱歉这是一个有点冗长的问题.

编辑

绘制我想要发生的事情:

它应该如何工作的草图

星星/点是XY两个修改绘制,我删除了第一个位置并添加了一个假的,使其成为所寻求算法的完整示例.

换句话说,我想要的是映射红色圆圈位置的新坐标值(在它们旁边写的那些),以便我可以从新的(例如(1, 1) -> (170.82191781, 162.67123288))获得旧坐标.我还想要不接近真实点描述的理想网格的点(如图所示),最后使用理想网格参数(粗略地(0, 0) -> (55, 55))将空的理想网格位置(蓝色圆圈)"填充" .

我使用提供的代码@skymandr来获得理想的参数,然后执行以下操作(不是最漂亮的代码,但它可以工作).这意味着我不再使用get_neighbour_grid-function了:

def build_grid(X, Y, x_offset, y_offset, dx, dy, grid_shape=(16,24),
    square_distance_threshold=None):

    if square_distance_threshold is None:
        square_distance_threshold = ((dx + dy) / 2.0 * 0.05) ** 2

    grid = np.zeros(grid_shape + (2,), dtype=np.float)

    D = np.zeros(grid_shape)
    for i in range(grid_shape[0]):
        for j in range(grid_shape[1]):
            D[i,j] = i * (1 + 1.0 / (grid_shape[0] + 1)) + j

    rD = D.ravel().copy()
    rD.sort()

    def find_valid(x, y):

        d = (X - x) ** 2 + (Y - y) ** 2
        valid = d < square_distance_threshold
        if valid.any():
            pos = d == d[valid].min()
            if pos.sum() == 1:
                return X[pos], Y[pos]

        return x, y

    x = x_offset
    y = y_offset
    first_loop = True

    for v in rD:
        #get new position
        coord = np.where(D == v)

        #generate a reference position already passed
        if coord[0][0] > 0:
            old_coord = (coord[0] - 1, coord[1])
        elif coord[1][0] > 0:
            old_coord = (coord[0], coord[1] - 1)

        if not first_loop:
            #calculate ideal step
            x, y = grid[old_coord].ravel()
            x += (coord[0] - old_coord[0]) * dx
            y += (coord[1] - old_coord[1]) * dy

        #modify with observed point close to ideal if exists
        x, y = find_valid(x, y)

        #put in grid
        #print coord, grid[coord].shape
        grid[coord] = np.array((x, y)).reshape(grid[coord].shape)

        first_loop = False


    return grid
Run Code Online (Sandbox Code Playgroud)

它提出了另一个问题:如何很好地沿2D阵列的对角线进行迭代,但我认为这值得一个自己的问题:迭代通过2D阵列的"正交"对角线的更多numpy方式

编辑

更新了解决方案代码以更好地处理更大的网格大小,以便它使用已经作为参考的相邻网格位置作为所有位置的理想坐标.仍然必须找到一种方法来实现从链接问题迭代网格的更好方法.

sky*_*ndr 5

这是一个相当简单且便宜的解决方案,尽管我不知道它有多强大。

首先,这里有一种更好地估计间距的方法:

leeway = 1.10

XX = X.reshape((1, X.size))
dX = np.abs(XX - XX.T).reshape((1, X.size ** 2))
dxs = dX[np.where(np.logical_and(dX > expected_distance / leeway,
                                 dX < expected_distance * leeway))]
dx = dxs.mean()

YY = Y.reshape((1, Y.size))
dY = np.abs(YY - YY.T).reshape((1, Y.size ** 2))
dys = dY[np.where(np.logical_and(dY > expected_distance / leeway,
                                 dY < expected_distance * leeway))]
dy = dys.mean()
Run Code Online (Sandbox Code Playgroud)

该代码计算 X 和 Y 的内部差异,并取那些在所需间距 10% 以内的平均值。

对于第二部分,找到网格的偏移量,可以使用类似的方法:

Ndx = np.array([np.arange(grid_size[0])]) * dx
x_offsets = XX - Ndx.T
x_offset = np.median(x_offsets)

Ndy = np.array([np.arange(grid_size[1])]) * dy
y_offsets = YY - Ndy.T
y_offset = np.median(y_offsets)
Run Code Online (Sandbox Code Playgroud)

从本质上讲,这样做是为了让每个位置的X“投票”的NX = grid_size[0]位置在左下角点可能是基于X - n * dx地方n = 0是点本身进行表决,n = 1是一个点一个投票dx向左等等。这样,靠近真实原点的点将获得最多的选票,可以使用中位数找到偏移量。

我认为这种方法在所需原点周围足够对称,可以在大多数(如果不是全部)情况下使用中位数。然而,如果有许多误报,由于某种原因使中位数不起作用,则可以使用例如直方图方法找到“真实”原点。