Comment extraire une ligne arbitraire de valeurs d'un tableau de numpy?

j'ai un tableau numpy qui contient des données d'image. Je voudrais tracer le profil d'un transect à travers l'image. Le cas le plus simple est un profil parallèle au bord de l'image, donc si le tableau d'image est imdat , alors le profil à un point sélectionné (r,c) est simplement imdat[r] (horizontal) ou imdat[:,c] (vertical).

maintenant, je veux prendre comme entrée deux points (r1,c1) et (r2,c2) , tous les deux à l'intérieur imdat . Je voudrais tracer le profil des valeurs le long de la ligne reliant ces deux points.

Quelle est la meilleure façon d'obtenir des valeurs d'un tableau numpy, le long d'une telle ligne? Plus généralement, le long d'un chemin/polygone?

j'ai déjà utilisé le tranchage et l'indexation avant, mais je ne peux pas sembler arriver à une solution élégante pour une telle où les éléments consécutifs de tranche ne sont pas dans la même rangée ou colonne. Merci pour votre aide.

51
demandé sur m0nhawk 2011-10-24 19:54:47

5 réponses

la réponse de @Sven est la voie facile, mais elle est plutôt inefficace pour les grands tableaux. Si vous avez affaire à un tableau relativement petit, vous ne remarquerez pas la différence, si vous voulez un profil à partir d'un grand (par exemple >50 Mo) vous pouvez vouloir essayer quelques autres approches. Vous aurez besoin de travailler dans "pixel" coordonnées pour ceux-ci, cependant, il ya une couche supplémentaire de complexité.

il y a deux autres façons d'économiser la mémoire. 1) utiliser scipy.ndimage.map_coordinates si vous avez besoin d'une interpolation bilinéaire ou cubique. 2) Si vous voulez juste l'échantillonnage du voisin le plus proche, puis il suffit d'utiliser l'indexation directement.

comme exemple du premier:

import numpy as np
import scipy.ndimage
import matplotlib.pyplot as plt

#-- Generate some data...
x, y = np.mgrid[-5:5:0.1, -5:5:0.1]
z = np.sqrt(x**2 + y**2) + np.sin(x**2 + y**2)

#-- Extract the line...
# Make a line with "num" points...
x0, y0 = 5, 4.5 # These are in _pixel_ coordinates!!
x1, y1 = 60, 75
num = 1000
x, y = np.linspace(x0, x1, num), np.linspace(y0, y1, num)

# Extract the values along the line, using cubic interpolation
zi = scipy.ndimage.map_coordinates(z, np.vstack((x,y)))

#-- Plot...
fig, axes = plt.subplots(nrows=2)
axes[0].imshow(z)
axes[0].plot([x0, x1], [y0, y1], 'ro-')
axes[0].axis('image')

axes[1].plot(zi)

plt.show()

enter image description here

l'équivalent utilisant l'interpolation du plus proche voisin ressemblerait à quelque chose comme ceci:

import numpy as np
import matplotlib.pyplot as plt

#-- Generate some data...
x, y = np.mgrid[-5:5:0.1, -5:5:0.1]
z = np.sqrt(x**2 + y**2) + np.sin(x**2 + y**2)

#-- Extract the line...
# Make a line with "num" points...
x0, y0 = 5, 4.5 # These are in _pixel_ coordinates!!
x1, y1 = 60, 75
num = 1000
x, y = np.linspace(x0, x1, num), np.linspace(y0, y1, num)

# Extract the values along the line
zi = z[x.astype(np.int), y.astype(np.int)]

#-- Plot...
fig, axes = plt.subplots(nrows=2)
axes[0].imshow(z)
axes[0].plot([x0, x1], [y0, y1], 'ro-')
axes[0].axis('image')

axes[1].plot(zi)

plt.show()

enter image description here

cependant, si vous utilisez voisin le plus proche, vous ne voulez probablement des échantillons qu'à chaque pixel, donc vous feriez probablement quelque chose de plus comme ça, à la place...

import numpy as np
import matplotlib.pyplot as plt

#-- Generate some data...
x, y = np.mgrid[-5:5:0.1, -5:5:0.1]
z = np.sqrt(x**2 + y**2) + np.sin(x**2 + y**2)

#-- Extract the line...
# Make a line with "num" points...
x0, y0 = 5, 4.5 # These are in _pixel_ coordinates!!
x1, y1 = 60, 75
length = int(np.hypot(x1-x0, y1-y0))
x, y = np.linspace(x0, x1, length), np.linspace(y0, y1, length)

# Extract the values along the line
zi = z[x.astype(np.int), y.astype(np.int)]

#-- Plot...
fig, axes = plt.subplots(nrows=2)
axes[0].imshow(z)
axes[0].plot([x0, x1], [y0, y1], 'ro-')
axes[0].axis('image')

axes[1].plot(zi)

plt.show()

enter image description here

79
répondu Joe Kington 2011-10-24 19:19:47

probablement la façon la plus facile de faire ceci est d'utiliser scipy.interpolate.interp2d() :

# construct interpolation function
# (assuming your data is in the 2-d array "data")
x = numpy.arange(data.shape[1])
y = numpy.arange(data.shape[0])
f = scipy.interpolate.interp2d(x, y, data)

# extract values on line from r1, c1 to r2, c2
num_points = 100
xvalues = numpy.linspace(c1, c2, num_points)
yvalues = numpy.linspace(r1, r2, num_points)
zvalues = f(xvalues, yvalues)
17
répondu Sven Marnach 2011-10-24 17:02:51

j'ai testé les routines ci-dessus avec des images de galaxy et je pense avoir trouvé une petite erreur. Je pense qu'une transposition doit être ajoutée à la solution par ailleurs excellente fournie par Joe. Voici une version légèrement modifiée de son code qui révèle l'erreur. Si vous l'exécutez sans les transposer, vous pouvez voir le profil ne correspond pas; avec le transposer semble ok. Ce n'est pas évident dans la solution de Joe puisqu'il utilise une image symétrique.

import numpy as np
import scipy.ndimage
import matplotlib.pyplot as plt
import scipy.misc # ADDED THIS LINE

#-- Generate some data...
x, y = np.mgrid[-5:5:0.1, -5:5:0.1]
z = np.sqrt(x**2 + y**2) + np.sin(x**2 + y**2)
lena = scipy.misc.lena()  # ADDED THIS ASYMMETRIC IMAGE
z = lena[320:420,330:430] # ADDED THIS ASYMMETRIC IMAGE

#-- Extract the line...
# Make a line with "num" points...
x0, y0 = 5, 4.5 # These are in _pixel_ coordinates!!
x1, y1 = 60, 75
num = 500
x, y = np.linspace(x0, x1, num), np.linspace(y0, y1, num)

# Extract the values along the line, using cubic interpolation
zi = scipy.ndimage.map_coordinates(z, np.vstack((x,y))) # THIS DOESN'T WORK CORRECTLY
zi = scipy.ndimage.map_coordinates(np.transpose(z), np.vstack((x,y))) # THIS SEEMS TO WORK CORRECTLY

#-- Plot...
fig, axes = plt.subplots(nrows=2)
axes[0].imshow(z)
axes[0].plot([x0, x1], [y0, y1], 'ro-')
axes[0].axis('image')

axes[1].plot(zi)

plt.show()

voici le version sans la transposition. Notez que seule une petite fraction sur la gauche devrait être brillante selon l'image, mais la parcelle montre presque la moitié de la parcelle comme brillante.

Without Transpose

Voici la version avec la transposition. Dans cette image, le tracé semble bien correspondre à ce que vous attendez de la ligne rouge de l'image.

With Transpose

16
répondu acrider 2014-05-25 00:16:49

pour une solution en boîte, regardez dans la fonction scikit-image s measure.profile_line .

il est construit sur le dessus de scipy.ndimage.map_coordinates comme dans @Joe 's réponse et a quelques fonctionnalités supplémentaires utiles cuit dans.

9
répondu David Hoffman 2017-05-23 12:18:28

combinant cette réponse avec le exemple de traitement D'événement sur la documentation de MPL , voici le code pour permettre à l'interface graphique de tirer/mettre à jour votre tranche, en faisant glisser sur les données de la parcelle (c'est codé pour les parcelles pcolormesh):

import numpy as np 
import matplotlib.pyplot as plt  

# Handle mouse clicks on the plot:
class LineSlice:
    '''Allow user to drag a line on a pcolor/pcolormesh plot, and plot the Z values from that line on a separate axis.

    Example
    -------
    fig, (ax1, ax2) = plt.subplots( nrows=2 )    # one figure, two axes
    img = ax1.pcolormesh( x, y, Z )     # pcolormesh on the 1st axis
    lntr = LineSlice( img, ax2 )        # Connect the handler, plot LineSlice onto 2nd axis

    Arguments
    ---------
    img: the pcolormesh plot to extract data from and that the User's clicks will be recorded for.
    ax2: the axis on which to plot the data values from the dragged line.


    '''
    def __init__(self, img, ax):
        '''
        img: the pcolormesh instance to get data from/that user should click on
        ax: the axis to plot the line slice on
        '''
        self.img = img
        self.ax = ax
        self.data = img.get_array().reshape(img._meshWidth, img._meshHeight)

        # register the event handlers:
        self.cidclick = img.figure.canvas.mpl_connect('button_press_event', self)
        self.cidrelease = img.figure.canvas.mpl_connect('button_release_event', self)

        self.markers, self.arrow = None, None   # the lineslice indicators on the pcolormesh plot
        self.line = None    # the lineslice values plotted in a line
    #end __init__

    def __call__(self, event):
        '''Matplotlib will run this function whenever the user triggers an event on our figure'''
        if event.inaxes != self.img.axes: return     # exit if clicks weren't within the `img` axes
        if self.img.figure.canvas.manager.toolbar._active is not None: return   # exit if pyplot toolbar (zooming etc.) is active

        if event.name == 'button_press_event':
            self.p1 = (event.xdata, event.ydata)    # save 1st point
        elif event.name == 'button_release_event':
            self.p2 = (event.xdata, event.ydata)    # save 2nd point
            self.drawLineSlice()    # draw the Line Slice position & data
    #end __call__

    def drawLineSlice( self ):
        ''' Draw the region along which the Line Slice will be extracted, onto the original self.img pcolormesh plot.  Also update the self.axis plot to show the line slice data.'''
        '''Uses code from these hints:
        /q/how-to-extract-an-arbitrary-line-of-values-from-a-numpy-array-76026/"",
                    xy=(x0, y0),    # start point
                    xycoords='data',
                    xytext=(x1, y1),    # end point
                    textcoords='data',
                    arrowprops=dict(
                        arrowstyle="<-",
                        connectionstyle="arc3", 
                        color='white',
                        alpha=0.7,
                        linewidth=3
                        ),

                    )

        # plot the data along the line on provided `ax`:
        if self.line != None:
            self.line[0].remove()   # delete the plot
        self.line = self.ax.plot(zi)
    #end drawLineSlice()

#end class LineTrace


# load the data:
D = np.genfromtxt(DataFilePath, ...)
fig, ax1, ax2 = plt.subplots(nrows=2, ncols=1)

# plot the data
img = ax1.pcolormesh( np.arange( len(D[0,:]) ), np.arange(len(D[:,0])), D )

# register the event handler:
LnTr = LineSlice(img, ax2)    # args: the pcolor plot (img) & the axis to plot the values on (ax2)

il en résulte ce qui suit (après ajout d'étiquettes axiales, etc.), après avoir traîné sur le tracé de pcolor: User Clicked+Dragged to create line-slice where the white arrow is drawn

1
répondu Demis 2016-01-19 19:42:53