web-dev-qa-db-ja.com

不規則な間隔のポイントの密度を計算する効率的な方法

ホットスポット、つまりデータポイントの密度が高いマップ上の領域を特定するのに役立つマップオーバーレイ画像を生成しようとしています。私が試したアプローチはどれも、自分のニーズに十分なほど速くはありません。注:このアルゴリズムは、ズームのシナリオが低い場合と高い場合(またはデータポイントの密度が低い場合と高い場合)の両方でうまく機能することを忘れていました。

私はnumpy、pyplot、およびscipyライブラリを調べましたが、最も近いのはnumpy.histogram2dでした。下の画像からわかるように、histogram2dの出力はかなり粗雑です。 (各画像には、理解を深めるためにヒートマップをオーバーレイするポイントが含まれています)

enter image description here 私の2番目の試みは、すべてのデータポイントを反復処理してから、ホットスポット値を距離の関数として計算することでした。これにより見栄えの良い画像が生成されましたが、アプリケーションで使用するには遅すぎます。 O(n)なので、100ポイントでも問題なく動作しますが、実際の30000ポイントのデータセットを使用するとうまくいきません。

私の最後の試みは、データをKDTreeに格納し、最も近い5ポイントを使用してホットスポット値を計算することでした。このアルゴリズムはO(1)であり、大規模なデータセットで非常に高速です。それでもまだ十分な速度ではありません。256x256ビットマップを生成するには約20秒かかります。これを約1秒で実行したいと思います。

編集

6502が提供するボックスサムスムージングソリューションは、すべてのズームレベルで適切に機能し、元の方法よりもはるかに高速です。

ルークとニールGによって提案されたガウスフィルターソリューションが最速です。

以下の4つのアプローチすべてを見ることができます。合計1000のデータポイントを使用すると、3倍のズームで約60ポイントが表示されます。

enter image description here

私の元の3つの試行を生成する完全なコード、6502によって提供されるボックスサム平滑化ソリューション、およびLukeによって提案されたガウスフィルター(エッジをより適切に処理してズームインできるように改善された)は次のとおりです。

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()
44
Ivo Bosticky

このアプローチは、以前のいくつかの回答の線に沿っています:各スポットのピクセルをインクリメントし、ガウスフィルターで画像を平滑化します。 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))
29
Luke

リアルタイムで(Cを使用して)実行できる非常に単純な実装であり、純粋なpython)でほんの数秒しかかかりません。画面スペースで結果を計算するだけです。

アルゴリズムは

  1. 最終行列(256x256など)をすべて0に割り当てます
  2. データセットの各ポイントについて、対応するセルをインクリメントします
  3. 行列の各セルを、セルを中心としたNxNボックスの行列の値の合計で置き換えます。このステップを数回繰り返します。
  4. 結果と出力のスケーリング

ボックス合計の計算は、合計テーブルを使用することにより、非常に高速でNに依存しないようにすることができます。すべての計算は、マトリックスの2つのスキャンを必要とするだけです...全体の複雑さは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. 平均化パスにsumではなくsqrt(average of squared values)を使用
  2. 結果を色分け
  3. 結果を引き伸ばして常にフルカラースケールを使用する
  4. データポイントがある場所にアンチエイリアス処理された黒い点を描画
  5. 半径を2から40に増やしてアニメーションを作成しました

このコスメティックバージョンでの次のアニメーションの39フレームの合計計算時間は、PyPyで5.4秒、標準Pythonで26秒です。

enter image description here

20
6502

ヒストグラム

ヒストグラムの方法は最速ではなく、ポイントの任意の小さな間隔と2 * sqrt(2) * bbはビンの幅)の違いを区別できません。

Xビンとyビンを別々に構成した場合(O(N))でも、いくつかのab畳み込み(各方向のビンの数)を実行する必要があります。これは、高密度システムではN ^ 2に近く、まばらなもの(まあ、まばらなシステムではab >> N ^ 2。)

上記のコードを見ると、grid_density()にループがあり、xのビンの数のループ内でyのビンの数を超えているため、O(N ^ 2)パフォーマンス(すでにNを注文していて、異なる数の要素をプロットして確認する必要がある場合は、サイクルごとに 少ないコードを実行 する必要があります)。

実際の距離関数が必要な場合は、接触検出アルゴリズムを検討する必要があります。

接触検出

素朴な接触検出アルゴリズムはO(N ^ 2)でRAMまたはCPU時間のいずれかで受信されますが、セントメアリーズカレッジロンドンのムンジザに起因するアルゴリズムがあり、線形時間で実行されます。そしてRAM。

あなたはそれについて読んで、あなたが好きなら 彼の本 からそれを自分で実装することができます。

私は実際にこのコードを自分で書きました

私はこれのpythonでラップされたC実装を2Dで記述しましたが、これは実際には生産の準備ができていません(まだシングルスレッドなどです)が、O(N)の近くで実行されますデータセットは許可されます。ビンサイズとして機能する「要素サイズ」を設定します(コードは、別のポイントのb内、およびb2 * sqrt(2) * bの間のすべてのインタラクションを呼び出します)、xプロパティとyプロパティを持つオブジェクトの配列(ネイティブpythonリスト)を指定すると、私のCモジュールは選択したpython関数にコールバックして、相互作用関数を実行します一致した要素のペア。接触力DEMシミュレーションを実行するように設計されていますが、この問題でも問題なく機能します。

まだリリースしていないので、ライブラリの他のビットの準備がまだ整っていないため、現在のソースのZipを提供する必要がありますが、接触検出部分はしっかりしています。コードはLGPLされています。

これを機能させるには、Cythonとacコンパイラが必要です。また、* nix環境でのみテストされ、動作します。Windowsを使用している場合は、必要です Cythonのmingw cコンパイラがまったく機能するため

Cythonがインストールされたら、building/installing 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
tehwalrus

ただのメモ、histogram2d関数はこれで正常に動作するはずです。ビンのサイズを変えて遊んでみましたか?最初のhistogram2d plotはデフォルトのビンサイズを使用しているようです...しかし、デフォルトのサイズがあなたの望む表現を与えると期待する理由はありません。そうは言っても、他のソリューションの多くも印象的です。

0
ely

これは、ガウス形状のカーネルを使用して、元の画像の2Dの分離可能なたたみ込み(scipy.ndimage.convolve1d)で行うことができます。画像サイズがMxMでフィルターサイズがPの場合、分離可能フィルターを使用すると複雑度はO(PM ^ 2)になります。 「Big-Oh」の複雑さは間違いなく大きいですが、計算を大幅に高速化するnumpyの効率的な配列演算を利用できます。

0
benpro

あなたの解決策は大丈夫ですが、1つの明確な問題は、それらの真ん中に点があるにもかかわらず、暗い領域を取得していることです。

代わりに、n次元のガウス分布を各ポイントに集中させ、表示する各ポイントの合計を評価します。一般的なケースで線形時間に短縮するには、query_ball_pointを使用して、いくつかの標準偏差内のポイントのみを考慮します。

彼のKDTreeが本当に遅いことがわかった場合は、しきい値を少し大きくして5ピクセルごとにquery_ball_pointを呼び出してみませんか?少数のガウシアンを評価してもそれほど害はありません。

0
Neil G