Эффективный метод расчета плотности неравномерно расположенных точек - PullRequest
42 голосов
/ 11 июля 2011

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

Я просмотрел библиотеки numpy, pyplot и scipy, и ближе всего я смог найти numpy.histogram2d. Как вы можете видеть на рисунке ниже, вывод histogram2d довольно грубый. (Каждое изображение включает точки, наложенные на тепловую карту для лучшего понимания)

enter image description here Моя вторая попытка состояла в том, чтобы перебрать все точки данных, а затем вычислить значение горячей точки как функцию расстояния. Это привело к получению более привлекательного изображения, однако оно слишком медленное для использования в моем приложении. Так как это O (n), он работает нормально с 100 точками, но выходит из строя, когда я использую свой фактический набор данных с 30000 точками.

Моя последняя попытка состояла в том, чтобы сохранить данные в KDTree и использовать ближайшие 5 точек для вычисления значения горячей точки. Этот алгоритм O (1), намного быстрее с большим набором данных. Это все еще не достаточно быстро, требуется около 20 секунд, чтобы сгенерировать растровое изображение 256x256, и я бы хотел, чтобы это произошло примерно за 1 секунду.

Редактировать

Решение сглаживания boxsum, предоставляемое 6502, хорошо работает на всех уровнях масштабирования и намного быстрее, чем мои оригинальные методы.

Решение фильтра Гаусса, предложенное Люком и Нилом Дж, является самым быстрым.

Вы можете увидеть все четыре подхода ниже, используя всего 1000 точек данных, при 3-кратном увеличении видно около 60 точек.

enter image description here

Полный код, который генерирует мои первоначальные 3 попытки, решение сглаживания boxsum, предоставленное 6502, и гауссов фильтр, предложенный Люком (улучшенный для лучшей обработки краев и позволяющий увеличивать масштаб), здесь:

import matplotlib
import numpy as np
from matplotlib.mlab import griddata
import matplotlib.cm as cm
import matplotlib.pyplot as plt
import math
from scipy.spatial import KDTree
import time
import scipy.ndimage as ndi


def grid_density_kdtree(xl, yl, xi, yi, dfactor):
    zz = np.empty([len(xi),len(yi)], dtype=np.uint8)
    zipped = zip(xl, yl)
    kdtree = KDTree(zipped)
    for xci in range(0, len(xi)):
        xc = xi[xci]
        for yci in range(0, len(yi)):
            yc = yi[yci]
            density = 0.
            retvalset = kdtree.query((xc,yc), k=5)
            for dist in retvalset[0]:
                density = density + math.exp(-dfactor * pow(dist, 2)) / 5
            zz[yci][xci] = min(density, 1.0) * 255
    return zz

def grid_density(xl, yl, xi, yi):
    ximin, ximax = min(xi), max(xi)
    yimin, yimax = min(yi), max(yi)
    xxi,yyi = np.meshgrid(xi,yi)
    #zz = np.empty_like(xxi)
    zz = np.empty([len(xi),len(yi)])
    for xci in range(0, len(xi)):
        xc = xi[xci]
        for yci in range(0, len(yi)):
            yc = yi[yci]
            density = 0.
            for i in range(0,len(xl)):
                xd = math.fabs(xl[i] - xc)
                yd = math.fabs(yl[i] - yc)
                if xd < 1 and yd < 1:
                    dist = math.sqrt(math.pow(xd, 2) + math.pow(yd, 2))
                    density = density + math.exp(-5.0 * pow(dist, 2))
            zz[yci][xci] = density
    return zz

def boxsum(img, w, h, r):
    st = [0] * (w+1) * (h+1)
    for x in xrange(w):
        st[x+1] = st[x] + img[x]
    for y in xrange(h):
        st[(y+1)*(w+1)] = st[y*(w+1)] + img[y*w]
        for x in xrange(w):
            st[(y+1)*(w+1)+(x+1)] = st[(y+1)*(w+1)+x] + st[y*(w+1)+(x+1)] - st[y*(w+1)+x] + img[y*w+x]
    for y in xrange(h):
        y0 = max(0, y - r)
        y1 = min(h, y + r + 1)
        for x in xrange(w):
            x0 = max(0, x - r)
            x1 = min(w, x + r + 1)
            img[y*w+x] = st[y0*(w+1)+x0] + st[y1*(w+1)+x1] - st[y1*(w+1)+x0] - st[y0*(w+1)+x1]

def grid_density_boxsum(x0, y0, x1, y1, w, h, data):
    kx = (w - 1) / (x1 - x0)
    ky = (h - 1) / (y1 - y0)
    r = 15
    border = r * 2
    imgw = (w + 2 * border)
    imgh = (h + 2 * border)
    img = [0] * (imgw * imgh)
    for x, y in data:
        ix = int((x - x0) * kx) + border
        iy = int((y - y0) * ky) + border
        if 0 <= ix < imgw and 0 <= iy < imgh:
            img[iy * imgw + ix] += 1
    for p in xrange(4):
        boxsum(img, imgw, imgh, r)
    a = np.array(img).reshape(imgh,imgw)
    b = a[border:(border+h),border:(border+w)]
    return b

def grid_density_gaussian_filter(x0, y0, x1, y1, w, h, data):
    kx = (w - 1) / (x1 - x0)
    ky = (h - 1) / (y1 - y0)
    r = 20
    border = r
    imgw = (w + 2 * border)
    imgh = (h + 2 * border)
    img = np.zeros((imgh,imgw))
    for x, y in data:
        ix = int((x - x0) * kx) + border
        iy = int((y - y0) * ky) + border
        if 0 <= ix < imgw and 0 <= iy < imgh:
            img[iy][ix] += 1
    return ndi.gaussian_filter(img, (r,r))  ## gaussian convolution

def generate_graph():    
    n = 1000
    # data points range
    data_ymin = -2.
    data_ymax = 2.
    data_xmin = -2.
    data_xmax = 2.
    # view area range
    view_ymin = -.5
    view_ymax = .5
    view_xmin = -.5
    view_xmax = .5
    # generate data
    xl = np.random.uniform(data_xmin, data_xmax, n)    
    yl = np.random.uniform(data_ymin, data_ymax, n)
    zl = np.random.uniform(0, 1, n)

    # get visible data points
    xlvis = []
    ylvis = []
    for i in range(0,len(xl)):
        if view_xmin < xl[i] < view_xmax and view_ymin < yl[i] < view_ymax:
            xlvis.append(xl[i])
            ylvis.append(yl[i])

    fig = plt.figure()


    # plot histogram
    plt1 = fig.add_subplot(221)
    plt1.set_axis_off()
    t0 = time.clock()
    zd, xe, ye = np.histogram2d(yl, xl, bins=10, range=[[view_ymin, view_ymax],[view_xmin, view_xmax]], normed=True)
    plt.title('numpy.histogram2d - '+str(time.clock()-t0)+"sec")
    plt.imshow(zd, origin='lower', extent=[view_xmin, view_xmax, view_ymin, view_ymax])
    plt.scatter(xlvis, ylvis)


    # plot density calculated with kdtree
    plt2 = fig.add_subplot(222)
    plt2.set_axis_off()
    xi = np.linspace(view_xmin, view_xmax, 256)
    yi = np.linspace(view_ymin, view_ymax, 256)
    t0 = time.clock()
    zd = grid_density_kdtree(xl, yl, xi, yi, 70)
    plt.title('function of 5 nearest using kdtree\n'+str(time.clock()-t0)+"sec")
    cmap=cm.jet
    A = (cmap(zd/256.0)*255).astype(np.uint8)
    #A[:,:,3] = zd  
    plt.imshow(A , origin='lower', extent=[view_xmin, view_xmax, view_ymin, view_ymax])
    plt.scatter(xlvis, ylvis)

    # gaussian filter
    plt3 = fig.add_subplot(223)
    plt3.set_axis_off()
    t0 = time.clock()
    zd = grid_density_gaussian_filter(view_xmin, view_ymin, view_xmax, view_ymax, 256, 256, zip(xl, yl))
    plt.title('ndi.gaussian_filter - '+str(time.clock()-t0)+"sec")
    plt.imshow(zd , origin='lower', extent=[view_xmin, view_xmax, view_ymin, view_ymax])
    plt.scatter(xlvis, ylvis)

    # boxsum smoothing
    plt3 = fig.add_subplot(224)
    plt3.set_axis_off()
    t0 = time.clock()
    zd = grid_density_boxsum(view_xmin, view_ymin, view_xmax, view_ymax, 256, 256, zip(xl, yl))
    plt.title('boxsum smoothing - '+str(time.clock()-t0)+"sec")
    plt.imshow(zd, origin='lower', extent=[view_xmin, view_xmax, view_ymin, view_ymax])
    plt.scatter(xlvis, ylvis)

if __name__=='__main__':
    generate_graph()
    plt.show()

Ответы [ 6 ]

29 голосов
/ 12 июля 2011

Этот подход повторяет некоторые предыдущие ответы: увеличивать пиксель для каждого пятна, затем сглаживать изображение с помощью гауссовского фильтра. Размер изображения 256x256 на моем 6-летнем ноутбуке составляет около 350 мс.

import numpy as np
import scipy.ndimage as ndi

data = np.random.rand(30000,2)           ## create random dataset
inds = (data * 255).astype('uint')       ## convert to indices

img = np.zeros((256,256))                ## blank image
for i in xrange(data.shape[0]):          ## draw pixels
    img[inds[i,0], inds[i,1]] += 1

img = ndi.gaussian_filter(img, (10,10))
19 голосов
/ 11 июля 2011

Очень простая реализация, которая может быть сделана (с C) в реальном времени и которая занимает всего доли секунды в чистом python, - это просто вычислить результат в экранном пространстве.

Алгоритм:

  1. Выделите окончательную матрицу (например, 256x256) со всеми нулями
  2. Для каждой точки в наборе данных увеличьте соответствующую ячейку
  3. Замените каждую ячейку в матрице суммойзначения матрицы в прямоугольнике NxN с центром в ячейке.Повторите этот шаг несколько раз.
  4. Результат масштабирования и вывод

Вычисление суммы ячеек можно сделать очень быстро и независимо от N с помощью таблицы сумм.Каждое вычисление требует только двух сканирований матрицы ... общая сложность равна O (S + W H P), где S - количество точек;W, H - ширина и высота вывода, а P - число проходов сглаживания.

Ниже приведен код для чисто Python-реализации (также очень неоптимизированный);с 30000 точками и выходным полутоновым изображением 256x256 вычисление составляет 0,5 с, включая линейное масштабирование до 0,255 и сохранение файла .pgm (N = 5, 4 прохода).

def boxsum(img, w, h, r):
    st = [0] * (w+1) * (h+1)
    for x in xrange(w):
        st[x+1] = st[x] + img[x]
    for y in xrange(h):
        st[(y+1)*(w+1)] = st[y*(w+1)] + img[y*w]
        for x in xrange(w):
            st[(y+1)*(w+1)+(x+1)] = st[(y+1)*(w+1)+x] + st[y*(w+1)+(x+1)] - st[y*(w+1)+x] + img[y*w+x]
    for y in xrange(h):
        y0 = max(0, y - r)
        y1 = min(h, y + r + 1)
        for x in xrange(w):
            x0 = max(0, x - r)
            x1 = min(w, x + r + 1)
            img[y*w+x] = st[y0*(w+1)+x0] + st[y1*(w+1)+x1] - st[y1*(w+1)+x0] - st[y0*(w+1)+x1]

def saveGraph(w, h, data):
    X = [x for x, y in data]
    Y = [y for x, y in data]
    x0, y0, x1, y1 = min(X), min(Y), max(X), max(Y)
    kx = (w - 1) / (x1 - x0)
    ky = (h - 1) / (y1 - y0)

    img = [0] * (w * h)
    for x, y in data:
        ix = int((x - x0) * kx)
        iy = int((y - y0) * ky)
        img[iy * w + ix] += 1

    for p in xrange(4):
        boxsum(img, w, h, 2)

    mx = max(img)
    k = 255.0 / mx

    out = open("result.pgm", "wb")
    out.write("P5\n%i %i 255\n" % (w, h))
    out.write("".join(map(chr, [int(v*k) for v in img])))
    out.close()

import random

data = [(random.random(), random.random())
        for i in xrange(30000)]

saveGraph(256, 256, data)

Редактировать

Конечно, само определение плотности в вашем случае зависит от радиуса разрешения, или плотность равна просто + inf, когда вы достигаете точки, и ноль, когда вы этого не делаете?

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

  1. используется sqrt(average of squared values) вместо sum для прохода усреднения
  2. с цветовой кодировкой результатов
  3. растягивая результат, чтобы всегда использовать полноцветную шкалу
  4. нарисованные сглаженные черные точки, где точки данных
  5. , создали анимацию, увеличив радиус с 2 до 40

Общее время вычислений 39 кадров следующей анимации в этой косметической версии составляет 5,4 секунды для PyPy и 26 секунд для стандартного Python.

enter image description here

4 голосов
/ 11 июля 2011

Гистограмма

Способ гистограммы не самый быстрый, и он не может определить разницу между сколь угодно малым разделением точек и 2 * sqrt(2) * b (где b - ширина ячейки).

Даже если вы строите x-бункеры и y-бины отдельно (O (N)), вам все равно придется выполнить некоторую сверточную сверточку (число бинов в каждом направлении), которая близка к N ^ 2 для любой плотной системы, и еще больше для разреженного (ну, ab >> N ^ 2 в разреженной системе.)

Глядя на приведенный выше код, вы, похоже, имеете цикл в grid_density(), который перебирает количество бинов в y внутри цикла количества бинов в x, поэтому вы получаете O (N ^ 2) производительность (хотя, если у вас уже есть порядок N, который вы должны отобразить на разных количествах элементов, то вам просто нужно будет выполнить меньше кода за цикл).

Если вы хотите использовать функцию фактического расстояния, вам нужно начать поиск алгоритмов обнаружения контактов.

Обнаружение контакта

Наивные алгоритмы обнаружения контактов входят в O (N ^ 2) либо в ОЗУ, либо во время ЦП, но есть алгоритм, по праву или неверности приписываемый Манджизе в лондонском колледже Святой Марии, который работает в линейном времени и в ОЗУ.

Вы можете прочитать об этом и реализовать его самостоятельно из его книги , если хотите.

Я сам написал этот код, на самом деле

Я написал реализованную в Python реализацию C в 2D, которая на самом деле не готова к производству (она все еще однопоточная и т. Д.), Но будет работать так близко к O (N), насколько позволит ваш набор данных , Вы устанавливаете «размер элемента», который действует как размер ячейки (код будет вызывать взаимодействия со всем в пределах b другой точки, а иногда и между b и 2 * sqrt(2) * b), назначая ему массив (список собственных питонов) ) объектов со свойствами x и y и мой модуль C будет вызывать функцию Python по вашему выбору для запуска функции взаимодействия для соответствующих пар элементов. Он предназначен для выполнения моделирования DEM с использованием контактной силы, но он также будет хорошо работать и для этой проблемы.

Поскольку я еще не выпустил его, потому что другие фрагменты библиотеки еще не готовы, мне придется дать вам краткий обзор моего текущего источника, но часть обнаружения контактов не вызывает сомнений. Код LGPL'd.

Вам понадобится компилятор Cython и ac, чтобы он работал, и он только протестирован и работает под * nix environemnts, если вы работаете в Windows, вам понадобится компилятор mingw c для работы с Cython все .

После установки Cython сборка / установка pynet должна быть в случае запуска setup.py.

Интересующая вас функция: pynet.d2.run_contact_detection(py_elements, py_interaction_function, py_simulation_parameters) (и вы должны проверить классы Element и SimulationParameters на одном уровне, если вы хотите, чтобы он генерировал меньше ошибок - посмотрите в файле на archive-root/pynet/d2/__init__.py, чтобы увидеть реализации классов это тривиальные держатели данных с полезными конструкторами.)

(я обновлю этот ответ публичным ртутным репо, когда код будет готов к более общему выпуску ...)

0 голосов
/ 04 августа 2012

Просто обратите внимание, функция histogram2d должна нормально работать для этого. Вы играли с разными размерами бункеров? Ваш начальный histogram2d график, кажется, просто использует размеры бина по умолчанию ... но нет никаких оснований ожидать, что размеры по умолчанию дадут вам желаемое представление. Тем не менее, многие другие решения тоже впечатляют.

0 голосов
/ 11 июля 2011

Вы можете сделать это с помощью 2D, отделимой свертки (scipy.ndimage.convolve1d) вашего исходного изображения с гауссовой формой ядра. При размере изображения MxM и размере фильтра P сложность составляет O (PM ^ 2) с использованием разделяемой фильтрации. Сложность «Big-Oh», несомненно, больше, но вы можете воспользоваться эффективными операциями с массивами, которые должны значительно ускорить ваши вычисления.

0 голосов
/ 11 июля 2011

Ваше решение в порядке, но одна очевидная проблема состоит в том, что вы получаете темные области, несмотря на то, что есть точка прямо в середине.

Я бы вместо этого центрировал n-мерный гауссиан в каждой точкеи оцените сумму по каждой точке, которую вы хотите отобразить.Чтобы уменьшить его до линейного времени в общем случае, используйте query_ball_point, чтобы рассмотреть только точки в пределах пары стандартных отклонений.

Если вы обнаружите, что KDTree действительно медленный, почему бы не вызывать query_ball_point один раз в пятьпикселей с немного большим порогом?Не слишком больно оценивать слишком много гауссиан.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...