Je tente de générer des images de superposition de carte qui aideraient à identifier les points chauds, c'est-à-dire des zones sur la carte qui ont une densité élevée de points de données. Aucune des approches que j'ai essayées n'est assez rapide pour mes besoins. Remarque: J'ai oublié de mentionner que l'algorithme devrait bien fonctionner sous des scénarios de zoom bas et élevés (ou densité de points de données faible et élevée).
J'ai regardé à travers des bibliothèques engendrées, pyplot et scipes, et le plus proche que je puisse trouver, a été numpy.histogramme2D. Comme vous pouvez le constater dans l'image ci-dessous, la sortie histogramme2D est plutôt brute. (Chaque image comprend des points superposant la chaleur pour une meilleure compréhension)
Ma deuxième tentative était de itérer sur tous les points de données, puis de calculer la valeur de point chaud en fonction de la distance. Cela a produit une meilleure image, mais il est trop lent à utiliser dans mon application. Étant donné que c'est O (n), cela fonctionne bien avec 100 points, mais souffle lorsque j'utilise mon jeu de données réel de 30000 points.
Ma dernière tentative était de stocker les données dans une KDTREE et d'utiliser les 5 points les plus proches pour calculer la valeur de point chaud. Cet algorithme est O (1), tant plus rapidement avec un ensemble de données volumineux. Il n'est toujours pas assez rapide, il faut environ 20 secondes pour générer un bitmap de 256x256 et j'aimerais que cela se produise dans environ une seconde fois.
éditer
La solution de lissage BoxSUM fournie par 6502 fonctionne bien à tous les niveaux de zoom et est beaucoup plus rapide que mes méthodes d'origine.
La solution de filtre gaussien suggérée par Luke et Neil G est la plus rapide.
Vous pouvez voir les quatre approches ci-dessous, en utilisant 1000 points de données au total, à un zoom 3X, il y a environ 60 points visibles.
Code complet qui génère mes 3 tentatives d'origine, la solution de lissage de la boîte fournie par 6502 et le filtre gaussien suggéré par Luke (amélioré pour gérer les bords mieux et permettre le zoom avant) est ici:
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()
Cette approche est le long des lignes de certaines réponses précédentes: incrémentez un pixel pour chaque point, puis lisse l'image avec un filtre gaussien. Une image 256x256 fonctionne vers environ 350 ms sur mon ordinateur portable de 6 ans.
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))
Une implémentation très simple qui pourrait être faite (avec c) en temps réel et qui ne prend que des fractions d'une seconde en pure python consiste simplement à calculer le résultat dans l'espace d'écran.
L'algorithme est
Le calcul de la boîte peut être rendu très rapide et indépendant sur N en utilisant une table de somme. Chaque calcul nécessite simplement deux balayages de la matrice ... La complexité totale est O (S + W H p) où S est le nombre de points; W, H sont la largeur et la hauteur de la sortie et P est le nombre de passes de lissage.
Vous trouverez ci-dessous le code pour un pur python=== (également très optimisé); avec 30000 points et une image en niveaux de gris de taille 256x256, le calcul est de 0,5 secondé comprenant une échelle linéaire à 0..255 et la sauvegarde d'un fichier .pgm (n = 5, 4 passes).
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)
Bien sûr, la définition même de la densité dans votre cas dépend d'un rayon de résolution ou est la densité juste + inf fois lorsque vous frappez un point et zéro quand vous ne le faites pas?
Ce qui suit est une animation construite avec le programme ci-dessus avec quelques modifications cosmétiques:
sqrt(average of squared values)
au lieu de sum
pour le passage de la moyenneLa durée totale de calcul des 39 images de l'animation suivante avec cette version cosmétique est de 5,4 secondes avec pypy et 26 secondes avec python standard.
histogrammes
La manière histogramme n'est pas la plus rapide et ne peut pas dire la différence entre une séparation arbitrairement petite des points et 2 * sqrt(2) * b
(où b
est la largeur de la corbeille).
Même si vous construisez les bacs x et les bacs y séparément (O (n)), vous devez toujours effectuer une certaine convolution AB (nombre de bacs à chaque sens), qui est proche de N ^ 2 pour tout système dense, et même plus grand pour un (puits, ab >> n ^ 2 dans un système de plaide.)
En regardant le code ci-dessus, vous semblez avoir une boucle dans grid_density()
qui fonctionne sur le nombre de bacs dans Y à l'intérieur d'une boucle du nombre de bacs dans x, c'est pourquoi vous obtenez O (n ^ 2) Performances (bien que si vous commandez déjà n, que vous devez tracer sur différents nombres d'éléments à voir, vous allez simplement avoir à devoir exécuter moins de code par cycle).
Si vous souhaitez une fonction de distance réelle, vous devez commencer à regarder des algorithmes de détection de contact.
Détection de contact
Les algorithmes de détection des contacts naïfs entrent dans O (n ^ 2) dans l'un ou l'autre RAM ou le temps de la CPU, mais il y a un algorithme, à tort ou à tort attribué à Munjiza au Saint-Mary's College London, qui fonctionne en temps linéaire et en bélier.
vous pouvez en lire à ce sujet et la mettre en œuvre vous-même de - son livre , si vous le souhaitez.
J'ai écrit ce code moi-même, en fait
J'ai écrit une implémentation C enveloppée par Python en 2D, qui n'est pas vraiment prête pour la production (elle est encore unique filetée, etc.), mais elle s'exécutera aussi près de O(N) = Au fur et à mesure que votre jeu de données permettra. Vous définissez la "taille de l'élément", qui agit comme une taille de poubelle (le code appellera des interactions sur tout dans b
d'un autre point, et parfois entre b
et 2 * sqrt(2) * b
), donnez-lui un tableau (native python) d'objets avec une propriété X et Y et mon module C rappellera à un python Fonction de votre choix Pour exécuter une fonction d'interaction pour des paires d'éléments appariés. Il est conçu pour la confrontation de contacts de contact des simulations de DEM, mais cela fonctionnera également sur ce problème.
Comme je ne l'ai pas encore publié, car les autres bits de la bibliothèque ne sont pas encore prêts, je vais devoir vous donner un zip de ma source actuelle, mais la partie de détection de contact est solide. Le code est lgpl'd.
Vous aurez besoin de CYTHON et de CA COMPILER pour le faire fonctionner, et cela n'a été testé que sur * NIX Environnemts, si vous êtes sous Windows, vous aurez besoin le compilateur Mingw C pour Cython pour travailler du tout .
Une fois que Cython installé, bâtiment/installation pynet doit être un cas d'exécution Setup.py.
La fonction que vous êtes intéressé est pynet.d2.run_contact_detection(py_elements, py_interaction_function, py_simulation_parameters)
(et vous devez consulter l'élément de classes et les simulationsParameters au même niveau si vous souhaitez que la tâche ait moins d'erreurs - regardez dans le fichier à archive-root/pynet/d2/__init__.py
Pour voir les implémentations de la classe, ils sont des détenteurs de données triviaux avec des constructeurs utiles.)
(Je mettrai à jour cette réponse avec un repo Mercurial public lorsque le code est prêt pour une version plus générale ...)
Juste une note, le histogram2d
La fonction devrait fonctionner correctement pour cela. Avez-vous joué avec différentes tailles de bacs? Votre initiale histogram2d
L'intrigue semble simplement utiliser les tailles des bacs par défaut ... mais il n'y a aucune raison d'attendre que les tailles par défaut vous donnent la représentation souhaitée. Cela dit, beaucoup d'autres solutions sont également impressionnantes.
Vous pouvez le faire avec une convolution 2D séparable (scipedimage.convolve1d) de votre image d'origine avec un noyau de forme gaussienne. Avec une taille d'image de MXM et une taille de filtre de P, la complexité est O (PM ^ 2) en utilisant un filtrage séparable. La complexité "Big-Oh" est sans doute plus grande, mais vous pouvez profiter des opérations de réseau efficaces de NUMPY qui devraient considérablement accélérer vos calculs.
Votre solution est correcte, mais un problème clair est que vous obtenez des régions sombres malgré un point au milieu d'eux.
Je voudrais plutôt centrer un gaussien N-dimensionnel sur chaque point et évaluer la somme sur chaque point que vous souhaitez afficher. Pour réduire le temps linéaire dans le cas commun, utilisez query_ball_point
Pour considérer que des points dans quelques écarts types.
Si vous constatez que Kdtree est vraiment lent, pourquoi ne pas appeler query_ball_point
Une fois tous les cinq pixels avec un seuil légèrement plus grand? Cela ne fait pas trop mal pour évaluer quelques gaussiens trop nombreux.