Построение 2D-сетки из потенциально неполного списка кандидатов

<Сильный> Проблема

Мне нужно построить 2D-сетку, используя набор позиций кандидатов (значения в X и Y ). Тем не менее, могут быть ошибочные позитивные кандидаты, которые должны быть отфильтрованы, а также ложные негативы (где позиция должна быть создана для ожидаемой позиции с учетом значений окружающих позиций). Можно ожидать, что строки и столбцы сетки будут прямыми, а вращение - маленькое.

Кроме того, у меня нет достоверной информации о том, где находится позиция сетки (0, 0). Однако я знаю:

grid_size = (4, 4)

expected_distance = 105

(Исключенное расстояние является приблизительной оценкой расстояния между точками сетки и должно быть разрешено варьироваться в пределах 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])

<Сильный> Код

Следующая функция оценивает кандидатов и возвращает два словаря.

Первая имеет каждую позицию кандидата (как двухстрочный кортеж) в качестве ключей, а значения представляют собой двухстрочные кортежи позиций справа и ниже соседа (используя логику отображения изображений). Эти соседи сами по себе являются либо 2-х длинными координатами кортежей, либо 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

Вот где я застрял:

Как использовать эти словари и/или X и Y для построения наиболее поддерживаемой сетки?

У меня возникла идея начать с более низкого, самого правого кандидата, поддерживаемого двумя соседями, и итеративно создать сетку, используя словарь 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

Первая часть может быть полезна, поскольку она суммирует поддержку для каждой позиции. Он также показывает, что мне нужно в качестве окончательного вывода ( grid ):

3D-массив с двумя первыми размерами формы сетки и 3-й с длиной 2 (для x-координаты и y-координаты для каждой позиции).

<Сильный> Резюме

Поэтому я понимаю, как моя попытка была бесполезной, но я теряю информацию о том, как сделать глобальную оценку всех кандидатов и разместить наиболее поддерживаемую сетку, используя значения х и у кандидатов, где бы они ни находились. Поскольку это, я ожидаю, довольно сложный вопрос, я действительно не ожидаю, что кто-нибудь даст полное решение (хотя это было бы замечательно), но любой намек на то, какие типы алгоритмов или функции numpy/scipy могли бы использоваться, будем очень благодарны.

Наконец, жаль, что это вопрос довольно длительный.

<Сильный> Edit

Рисунок того, что я хочу:

Sketch of how it should work

Звезды/точки - это X и Y , построенные с двумя модификациями, я удалил первую позицию и добавил ложную, чтобы сделать это полным примером искомого алгоритма.

What I want is to, in other words, map the red-circled positions' new coordinate values (the ones written beside them) so that I can obtain the old coordinate from the new (e.g. (1, 1) -> (170.82191781, 162.67123288)). I also want points that don't approximate the ideal grid that the true points describe to be discarded (as shown), and finally the empty ideal grid positions (blue circle) to be 'filled' using the ideal grid parameters (roughly (0, 0) -> (55, 55)).

<Сильный> Решение

Я использовал код @skymandr для получения идеальных параметров, а затем сделал следующее (не самый красивый код, но он работает). Это означает, что я больше не использую get_neighbour_grid -функция:

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

It poses another question: how to nicely iterate along the diagonals of an 2D-array, but I suppose that is worthy of a question of its own: More numpy way of iterating through the 'orthogonal' diagonals of a 2D array

<Сильный> Edit

Обновлен код решения, чтобы лучше справляться с большими размерами сетки, чтобы он использовал соседнюю позицию сетки, уже переданную в качестве ссылки для идеальной координаты для всех позиций. Все еще нужно найти способ реализовать лучший способ итерации по сетке из связанного вопроса.

5
nl ja de
добавлено автор andrew cooke, источник
Добавлен пример рисования @AdamCadien ... и я согласен с тем, что структура данных, которыми я управляла до сих пор, - это не столько сетка, но, возможно, рисунок помогает выяснить, почему мне нравится называть это сеткой.
добавлено автор deinonychusaur, источник
@ ken.ganong что-то вроде этого, см. рисунок, я надеюсь, что это объяснит далее.
добавлено автор deinonychusaur, источник
@andrewcooke, спасибо, что выглядит многообещающим, хотя я немного неясен, если он имеет дело с ложными срабатываниями.
добавлено автор deinonychusaur, источник
Вы хотите сказать, что работаете с динамической неструктурированной сеткой и пытаетесь построить вокруг нее статическую структурированную сетку? Это может быть полезно, если бы вы представили пример того, что идеальный выход, учитывая ваш идеальный вход. Кроме того, из вашего кода я думаю, что, возможно, сетка не является правильным словом для того, что вы хотите, может быть, вы имеете в виду сеть или дерево или список подключений?
добавлено автор Adam Cadien, источник
Будет ли это верным повторением вашего вопроса: вы хотите найти сетку, поддерживаемую данными, которые отличаются от «идеальной» сетки?
добавлено автор ken.ganong, источник

1 ответы

Вот довольно простое и дешевое решение, хотя я не знаю, насколько он прочен.

Прежде всего, вот способ получить более точную оценку интервала:

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()

Код вычисляет внутренние различия в 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)

По существу, это означает, что каждая позиция в X "голосует" за NX = grid_size [0] позиции, где нижняя левая точка может быть, на основе X - n * dx , где n = 0 является голосованием за самую точку, n = 1 является голосованием за точку 1 dx влево и т. д. Таким образом, точки возле истинного источника получат наибольшее количество голосов, а смещение можно найти, используя медианную.

Я думаю, что этот метод достаточно симметричен вокруг искомого начала, что медиану можно использовать в большинстве (если не во всех) случаях. Если, однако, существует много ложных срабатываний, из-за которых медиана не работает по какой-либо причине, «истинное» происхождение можно найти, например, гистограммный метод.

4
добавлено
Таким образом, это создает идеальную сетку, если я правильно ее понимаю, и тогда мне нужно подставить эти позиции для моего обнаруженного, где они близки?
добавлено автор deinonychusaur, источник
Да, он направлен только на создание идеальной сетки (равномерно распределенной, не вращенной). В процессе нахождения ближайшей точки данных вы можете получить меру качества подгонки в виде среднего (или, возможно, среднего) евклидова расстояния от каждой идеальной точки сетки до ближайшего (допустимого) значения в наборе данных. Однако эта мера будет в основном полезной при сравнении сопоставлений с подобными хорошо подготовленными наборами данных, поскольку недостающая точка сетки будет иметь большой эффект (по крайней мере, в тривиальной реализации).
добавлено автор skymandr, источник
Python
Python
7 654 участник(ов)

Уютный чат для профессионалов, занимающихся поиском питоньих мудростей. Как не получить бан: https://t.me/ru_python/577926

Python beginners
Python beginners
4 449 участник(ов)

Вопросы про Python для чайников. Cпам и троллинг неприемлем. Не злоупотребляйте стикерами. Частозадаваемые вопросы: https://github.com/ru-python-beginners/faq/blob/master/README.md Статистика тут: https://grstats.me/chat/x4qym2k5uvfkr3al6at7

pro.python
pro.python
1 090 участник(ов)

Сообщество разработчиков под Python Создатель: @rodgelius

Rude Python
Rude Python
971 участник(ов)

Python без „девочек”, здесь матерятся и унижают Django. Not gay friendly. Правила: t.me/rudepython/114107 @rudepython | t.me/rudepython

rupython
rupython
509 участник(ов)

Группа создана с целью оперативного получения ответов на возникающие вопросы по разработке на яп python, смежные темы, а также человеческого общения. Приветствую!

Python-programming
Python-programming
266 участник(ов)

Чат группы вконтакте https://vk.com/python_community