<Сильный> Проблема
Мне нужно построить 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
Рисунок того, что я хочу:

Звезды/точки - это 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
Обновлен код решения, чтобы лучше справляться с большими размерами сетки, чтобы он использовал соседнюю позицию сетки, уже переданную в качестве ссылки для идеальной координаты для всех позиций. Все еще нужно найти способ реализовать лучший способ итерации по сетке из связанного вопроса.