如何从numpy数组中提取任意一行值?

时间:2011-10-24 15:54:48

标签: python numpy indexing slice

我有一个包含一些图像数据的numpy数组。我想绘制在图像上绘制的横断面的“轮廓”。最简单的情况是平行于图像边缘运行的轮廓,因此如果图像阵列为imdat,则选定点(r,c)处的轮廓仅为imdat[r](水平)或imdat[:,c](垂直)。

现在,我想将两个点(r1,c1)(r2,c2)作为输入,两个点位于imdat内。我想在连接这两点的线上绘制值的轮廓。

沿着这样一条线从numpy数组中获取值的最佳方法是什么?更一般地说,沿着路径/多边形?

之前我曾经使用过切片和索引,但是对于连续切片元素不在同一行或列中的情况,我似乎无法达到优雅的解决方案。谢谢你的帮助。

6 个答案:

答案 0 :(得分:80)

@Sven的回答是简单的方法,但对于大型数组来说效率相当低。如果您正在处理一个相对较小的数组,您将不会注意到差异,如果您想要一个大的配置文件(例如> 50 MB),您可能想尝试其他几种方法。不过,你需要在“像素”坐标中工作,所以还有一层额外的复杂性。

还有两种更有效的内存方式。 1)如果需要双线性或三次插值,请使用scipy.ndimage.map_coordinates。 2)如果你只想要最近邻采样,那么只需直接使用索引。

作为第一个例子:

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

使用最近邻插值的等价物看起来像这样:

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

但是,如果你使用的是最近邻居,你可能只想在每个像素处采样,所以你可能会做更像这样的事情,而不是......

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

答案 1 :(得分:17)

可能最简单的方法是使用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)

答案 2 :(得分:16)

我一直用Galaxy图像测试上述例程,并认为我发现了一个小错误。我认为需要将转置添加到Joe提供的其他优秀解决方案中。这是他的代码的略微修改版本,揭示了错误。如果你在没有转置的情况下运行它,你可以看到配置文件没有匹配;转置它看起来没问题。这在Joe的解决方案中并不明显,因为他使用的是对称图像。

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

这里是没有转置的版本。请注意,根据图像,左边只有一小部分应该是明亮的,但是图中几乎一半的图表显示为明亮。

Without Transpose

这是带转置的版本。在此图像中,图表似乎与您对图像中红线的期望相匹配。

With Transpose

答案 3 :(得分:10)

对于固定解决方案,请查看scikit-image的{​​{3}}函数。

它建立在scipy.ndimage.map_coordinates之上,与measure.profile_line@Joe一样,并且有一些额外的有用功能。

答案 4 :(得分:2)

将此答案与Event Handling example on MPL's documentation结合起来,这里的代码允许基于GUI的拖动来绘制/更新切片,方法是拖动绘图数据(这是针对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:
        http://stackoverflow.com/questions/7878398/how-to-extract-an-arbitrary-line-of-values-from-a-numpy-array
        http://stackoverflow.com/questions/34840366/matplotlib-pcolor-get-array-returns-flattened-array-how-to-get-2d-data-ba
        '''

        x0,y0 = self.p1[0], self.p1[1]  # get user's selected coordinates
        x1,y1 = self.p2[0], self.p2[1]
        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 with nearest-neighbor pixel value:
        # get temp. data from the pcolor plot
        zi = self.data[x.astype(np.int), y.astype(np.int)]
        # Extract the values along the line, using cubic interpolation:
        #import scipy.ndimage
        #zi = scipy.ndimage.map_coordinates(self.data, np.vstack((x,y)))

        # if plots exist, delete them:
        if self.markers != None:
            if isinstance(self.markers, list):
                self.markers[0].remove()
            else:
                self.markers.remove()
        if self.arrow != None:
            self.arrow.remove()

        # plot the endpoints
        self.markers = self.img.axes.plot([x0, x1], [y0, y1], 'wo')   
        # plot an arrow:
        self.arrow = self.img.axes.annotate("",
                    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)

在拖动pcolor图后,这会导致以下(在添加轴标签等之后): User Clicked+Dragged to create line-slice where the white arrow is drawn

答案 5 :(得分:1)

这是一种不使用 scipy 包的方法。它应该运行得更快并且易于理解。基本上,点 1 (pt1) 和点 2 (pt2) 之间的任何坐标对都可以转换为 x 和 y 像素整数,因此我们不需要任何插值。

import numpy as np
from PIL import Image
import matplotlib.pyplot as plt

def euclideanDistance(coord1,coord2):
    return np.sqrt((coord1[0]-coord2[0])**2+(coord1[1]-coord2[1])**2)

def getLinecut(image,X,Y,pt1,pt2):
    row_col_1, row_col_2 = getRowCol(pt1,X,Y), getRowCol(pt2,X,Y)
    row1,col1 = np.asarray(row_col_1).astype(float)
    row2,col2 = np.asarray(row_col_2).astype(float)
    dist = np.sqrt((pt1[0]-pt2[0])**2+(pt1[1]-pt2[1])**2)
    N = int(euclideanDistance(row_col_1,row_col_2))#int(np.sqrt((row1-row2)**2+(col1-col2)**2))
    rowList = [int(row1 + (row2-row1)/N*ind) for ind in range(N)]
    colList = [int(col1 + (col2-col1)/N*ind) for ind in range(N)]
    distList = [dist/N*ind for ind in range(N)]
    return distList,image[rowList,colList]#rowList,colList

def getRowCol(pt,X,Y):
    if X.min()<=pt[0]<=X.max() and Y.min()<=pt[1]<=Y.max():
        pass
    else:
        raise ValueError('The input center is not within the given scope.')
    center_coord_rowCol = (np.argmin(abs(Y-pt[1])),np.argmin(abs(X-pt[0])))
    return center_coord_rowCol

image = np.asarray(Image.open('./Picture1.png'))[:,:,1]
image_copy = image.copy().astype(float)

X = np.linspace(-27,27,np.shape(image)[1])#[::-1]
Y = np.linspace(-15,15,np.shape(image)[0])[::-1]

pt1, pt2 = (-12,-14), (20,13)
distList, linecut = getLinecut(image_copy,X,Y,pt1,pt2)
plt.plot(distList, linecut)

plt.figure()
plt.pcolormesh(X,Y,image_copy)
plt.plot([pt1[0],pt2[0]],[pt1[1],pt2[1]],color='red')
plt.gca().set_aspect(1)

enter image description here

Picture1.png 使用的图: enter image description here 请参阅此处了解更多详情: https://github.com/xuejianma/fastLinecut_radialLinecut

代码还有一个功能:取几条角度均匀的线的平均值。 enter image description here