Подтвердить что ты не робот

Эффективный метод расчета плотности нерегулярно разнесенных точек

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

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

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

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

Edit

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

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

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

enter image description here

Полный код, который генерирует мои первоначальные 3 попытки, решение сглаживания коробки, предоставленное 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()
4b9b3361

Ответ 1

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

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

Ответ 2

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

Алгоритм

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

Вычисление суммы ящика можно сделать очень быстрым и независимым от N, используя таблицу сумм. Для каждого вычисления просто требуется два сканирования матрицы... общая сложность - это O (S + WHP), где 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, когда вы нажимаете точку и ноль, когда вы этого не делаете?

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

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

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

enter image description here

Ответ 3

Гистограмма

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

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

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

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

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

Алгоритмы обнаружения наивного контакта входят в O (N ^ 2) в оперативном или процессорном времени, но есть алгоритм, по праву или ошибочно приписанный Munjiza в колледже St. Mary в Лондоне, который работает в линейном времени и оперативной памяти.

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

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

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

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

Вам понадобится Cython и ac-компилятор, чтобы он работал, и он был протестирован и работает в среде * nix environemnts, если вы находитесь в окнах, вам понадобится компилятор 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, чтобы увидеть реализации класса, это тривиальные держатели данных с полезными конструкторами.)

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

Ответ 4

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

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

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

Ответ 5

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

Ответ 6

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