Локальные максимумы в облаке точек

У меня есть облако точек C, где каждая точка имеет связанное значение. Допустим, точки находятся в двумерном пространстве, поэтому каждую точку можно представить тройкой (x, y, v).

Я хотел бы найти подмножество точек, которые являются локальными максимумами. То есть для некоторого радиуса R я хотел бы найти подмножество точек S в C, такое что для любой точки Pi (со значением vi) в S не существует точки Pj в C в пределах R расстояния от Pi, значение vj которого равно больше, чем vi.

Я вижу, как я мог бы сделать это за время O(N^2), но это кажется расточительным. Есть ли эффективный способ сделать это?


Боковые примечания:

  • Источником этой проблемы является то, что я пытаюсь найти локальные максимумы в разреженной матрице, поэтому в моем случае x, y являются упорядоченными целочисленными индексами - если это упрощает проблему, дайте мне знать!
  • Я совершенно счастлив, если решение только для манхэттенского расстояния или чего-то еще.
  • Я работаю на питоне, поэтому, если есть какой-то хороший векторизованный способ сделать это, это просто здорово.

person Peter    schedule 20.11.2014    source источник
comment
вы как будто сами себе противоречите. если точки находятся на регулярной сетке, это полностью меняет проблему, но в замечании вы говорите, что они произвольны.   -  person agentp    schedule 23.11.2014
comment
Я не понимаю, как. В вашем первом решении у вас были точки, представленные в виде плотного массива, то есть прямоугольной сетки, которая была просто конкретным примером более общего случая произвольных местоположений.   -  person Peter    schedule 24.11.2014
comment
в конкретном матричном случае вам нужны только индексы поиска, которые находятся рядом. Это упрощает работу с Заказом N.   -  person agentp    schedule 25.11.2014


Ответы (3)


Следуя предложению Ива, вот ответ, в котором используется ссылка scipy KDTree:

from scipy.spatial.kdtree import KDTree
import numpy as np

def locally_extreme_points(coords, data, neighbourhood, lookfor = 'max', p_norm = 2.):
    '''
    Find local maxima of points in a pointcloud.  Ties result in both points passing through the filter.

    Not to be used for high-dimensional data.  It will be slow.

    coords: A shape (n_points, n_dims) array of point locations
    data: A shape (n_points, ) vector of point values
    neighbourhood: The (scalar) size of the neighbourhood in which to search.
    lookfor: Either 'max', or 'min', depending on whether you want local maxima or minima
    p_norm: The p-norm to use for measuring distance (e.g. 1=Manhattan, 2=Euclidian)

    returns
        filtered_coords: The coordinates of locally extreme points
        filtered_data: The values of these points
    '''
    assert coords.shape[0] == data.shape[0], 'You must have one coordinate per data point'
    extreme_fcn = {'min': np.min, 'max': np.max}[lookfor]
    kdtree = KDTree(coords)
    neighbours = kdtree.query_ball_tree(kdtree, r=neighbourhood, p = p_norm)
    i_am_extreme = [data[i]==extreme_fcn(data[n]) for i, n in enumerate(neighbours)]
    extrema, = np.nonzero(i_am_extreme)  # This line just saves time on indexing
    return coords[extrema], data[extrema]
person Peter    schedule 24.11.2014
comment
Примечание. Если вам нужна скорость, не используйте KDTree от SciPy — вместо этого используйте SKLearn, это примерно в 300 раз быстрее. - person Peter; 20.01.2015
comment
Этот ответ по-прежнему золотой! отлично работает с: 'из sklearn.neighbors import KDTree; kdtree = KDTree (координаты); соседи = kdtree.query_radius(координаты, r=5)' - person HyperCube; 18.02.2021

Используйте 2D-дерево (2D-экземпляр kD-дерева). После предварительной обработки времени N.Log(N) это позволит вам выполнять поиск ближайших соседей с фиксированным радиусом вокруг всех ваших точек примерно за время Log(N) + K (в среднем найдено K соседей), всего N. Лог(N)+ К.Н. Он прекрасно уживется с манхэттенской дистанцией.

person Yves Daoust    schedule 22.11.2014

Я нашел это решение, но, вероятно, O (N ^ 2):

import numpy as np

# generate test data
n = 10
foo = np.random.rand(n,n)

# fixed test data for visual back-checking
# foo = np.array([[ 0.12439309,  0.88878825,  0.21675684,  0.21422532,  0.7016789 ],
#                 [ 0.14486462,  0.40642871,  0.4898418 ,  0.41611303,  0.12764404],
#                 [ 0.41853585,  0.22216484,  0.36113181,  0.5708699 ,  0.3874901 ],
#                 [ 0.24314391,  0.22488507,  0.22054467,  0.25387521,  0.46272496],
#                 [ 0.99097341,  0.76083447,  0.37941783,  0.932519  ,  0.9668254 ]])

# list to collect local maxima
local_maxima = []

# distance in x / y to define region of interest around current center coordinate
# roi = 1 corresponds to a region of interest of 3x3 (except at borders)
roi = 1

# give pseudo-coordinates
x,y = np.meshgrid(range(foo.shape[0]), range(foo.shape[1]))

for i in range(foo.shape[0]):
    for j in range(foo.shape[1]):
        x0 = x[i,j]
        y0 = y[i,j]
        z0 = foo[i,j]
        # index calculation to avoid out-of-bounds error when taking sub-matrix
        mask_x = abs(x - x0) <= roi
        mask_y = abs(y - y0) <= roi
        mask = mask_x & mask_y
        if np.max(foo[mask]) == z0:
            local_maxima.append((i, j))

print local_maxima

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

Однако я надеюсь, что мой анзац в какой-то степени полезен...

РЕДАКТИРОВАТЬ: здесь другое решение, которое должно быть быстрее, чем первое, но все же O (N ^ 2), и оно не зависит от прямолинейных данных с координатной сеткой:

import numpy as np

# generate test data
# points = np.random.rand(10,3)

points = np.array([[ 0.08198248,  0.25999721,  0.07041999],
                   [ 0.19091977,  0.05404123,  0.25826508],
                   [ 0.8842875 ,  0.90132467,  0.50512316],
                   [ 0.33320528,  0.74069399,  0.36643752],
                   [ 0.27789568,  0.14381512,  0.13405309],
                   [ 0.73586202,  0.4406952 ,  0.52345838],
                   [ 0.76639731,  0.70796547,  0.70692905],
                   [ 0.09164532,  0.53234394,  0.88298593],
                   [ 0.96164975,  0.60700481,  0.22605181],
                   [ 0.53892635,  0.95173308,  0.22371167]])

# list to collect local maxima
local_maxima = []

# distance in x / y to define region of interest around current center coordinate
radius = 0.25

for i in range(points.shape[0]):
        # radial mask with radius radius, could be beautified via numpy.linalg
        mask = np.sqrt((points[:,0] - points[i,0])**2 + (points[:,1] - points[i,1])**2) <= radius
        # if current z value equals z_max in current region of interest, append to result list
        if points[i,2] == np.max(points[mask], axis = 0)[2]:
            local_maxima.append(tuple(points[i]))

Результат:

local_maxima = [
 (0.19091976999999999, 0.054041230000000003, 0.25826507999999998), 
 (0.33320527999999999, 0.74069399000000002, 0.36643752000000002), 
 (0.73586202000000001, 0.44069520000000001, 0.52345838), 
 (0.76639731, 0.70796546999999999, 0.70692904999999995), 
 (0.091645320000000002, 0.53234393999999996, 0.88298593000000003), 
 (0.53892635, 0.95173308000000001, 0.22371167)
]
person jkalden    schedule 20.11.2014
comment
Спасибо. Хотя проблема более общая. Это решает проблему, когда точки располагаются в прямоугольной сетке и за время O (N ^ 2) из-за маскирования (хотя это может быть быстрее при использовании срезов). В моей задаче точки находятся в произвольных (x, y) местах. - person Peter; 21.11.2014
comment
Отредактировано, чтобы быть быстрее, чем O (N ^ 2) и справляться с произвольными местоположениями. - person jkalden; 22.11.2014
comment
Спасибо, но все же N ^ 2, потому что для каждой точки вы проверяете, находится ли каждая другая точка в пределах радиуса для формирования маски. Похоже, что решение меньше N ^ 2 будет включать использование дерева KD или какой-то умной сортировки. - person Peter; 24.11.2014
comment
Хорошо, понятно... Я пока не увлекаюсь kdtree, так что на данный момент я закончил! Удачи! - person jkalden; 25.11.2014