Approche n° 1
Voici une approche vectorielle -
m,n,r = volume.shape
x,y,z = np.mgrid[0:m,0:n,0:r]
X = x - roi[0]
Y = y - roi[1]
Z = z - roi[2]
mask = X**2 + Y**2 + Z**2 < radius**2
Amélioration possible : Nous pouvons probablement accélérer la dernière étape avec numexpr
module -
import numexpr as ne
mask = ne.evaluate('X**2 + Y**2 + Z**2 < radius**2')
Approche n°2
Nous pouvons également construire progressivement les trois plages correspondant aux paramètres de forme et effectuer la soustraction contre les trois éléments de roi
à la volée sans créer réellement les mailles comme cela a été fait précédemment avec np.mgrid
. L'utilisation de broadcasting
pour des raisons d'efficacité. L'implémentation ressemblerait à ceci -
m,n,r = volume.shape
vals = ((np.arange(m)-roi[0])**2)[:,None,None] + \
((np.arange(n)-roi[1])**2)[:,None] + ((np.arange(r)-roi[2])**2)
mask = vals < radius**2
Version simplifiée : Merci à @Bi Rico d'avoir suggéré une amélioration ici, car nous pouvons utiliser les éléments suivants np.ogrid
pour effectuer ces opérations de manière un peu plus concise, comme ceci -
m,n,r = volume.shape
x,y,z = np.ogrid[0:m,0:n,0:r]-roi
mask = (x**2+y**2+z**2) < radius**2
Test d'exécution
Définitions des fonctions -
def vectorized_app1(volume, roi, radius):
m,n,r = volume.shape
x,y,z = np.mgrid[0:m,0:n,0:r]
X = x - roi[0]
Y = y - roi[1]
Z = z - roi[2]
return X**2 + Y**2 + Z**2 < radius**2
def vectorized_app1_improved(volume, roi, radius):
m,n,r = volume.shape
x,y,z = np.mgrid[0:m,0:n,0:r]
X = x - roi[0]
Y = y - roi[1]
Z = z - roi[2]
return ne.evaluate('X**2 + Y**2 + Z**2 < radius**2')
def vectorized_app2(volume, roi, radius):
m,n,r = volume.shape
vals = ((np.arange(m)-roi[0])**2)[:,None,None] + \
((np.arange(n)-roi[1])**2)[:,None] + ((np.arange(r)-roi[2])**2)
return vals < radius**2
def vectorized_app2_simplified(volume, roi, radius):
m,n,r = volume.shape
x,y,z = np.ogrid[0:m,0:n,0:r]-roi
return (x**2+y**2+z**2) < radius**2
Horaires -
In [106]: # Setup input arrays
...: volume = np.random.rand(90,110,100) # Half of original input sizes
...: roi = np.random.rand(3)
...: radius = 3.4
...:
In [107]: %timeit _make_mask(volume, roi, radius)
1 loops, best of 3: 41.4 s per loop
In [108]: %timeit vectorized_app1(volume, roi, radius)
10 loops, best of 3: 62.3 ms per loop
In [109]: %timeit vectorized_app1_improved(volume, roi, radius)
10 loops, best of 3: 47 ms per loop
In [110]: %timeit vectorized_app2(volume, roi, radius)
100 loops, best of 3: 4.26 ms per loop
In [139]: %timeit vectorized_app2_simplified(volume, roi, radius)
100 loops, best of 3: 4.36 ms per loop
Donc, comme toujours broadcasting
montrant sa magie pour une folie presque 10,000x
par rapport au code original et plus de 10x
mieux que de créer des mailles en utilisant des opérations diffusées à la volée !
3 votes
Ces réponses vous ont-elles aidé ? Page pertinente à lire : Comment fonctionne l'acceptation d'une réponse ?
1 votes
S'il vous plaît excusez le necropost, mais A : vous devriez accepter le post de @Divakar.. C'est une merveilleuse démonstration de la vectorisation avec numpy, et B : vous devriez jeter un coup d'oeil aux arbres KD et à l'algorithme du point de balle de
scipy.spatial
. Il s'agit d'une méthode généralisable pour votre problème spécifique lorsque les données sont éparses ou ne sont pas sur une grille régulière. Bien que ce ne soit pas la meilleure méthode pour cette question exacte, c'est une très bonne chose à connaître (je l'ai récemment utilisée moi-même).1 votes
@Divakar votre explication a été très utile, merci. J'ai initialement voté en haut, mais j'ai récemment réalisé l'utilité de la coche. C'est fait.