2011-10-24 40 views
48

我有一个包含一些图像数据的numpy数组。我想绘制整个图像中横断面的“轮廓”。最简单的情况是平行于图像边缘的轮廓,所以如果图像阵列是imdat,则选定点(r,c)处的轮廓仅为imdat[r](水平)或imdat[:,c](垂直)。如何从numpy数组中提取任意一行值?

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

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

我以前使用过切片和索引,但我似乎无法得出这样一个优雅的解决方案,其中连续切片元素不在同一行或列中。谢谢你的帮助。

+0

哪条线有关系吗?不能保证在数组中的两个任意条目之间有唯一的“行”。如果这两条结尾条目位于同一行,同一列,相同对角线或反对角线上,唯一存在这样一条独特线的时间就是这样。 – talonmies

+0

确实如此,因为'线'必须以非均匀的方式切割像素,并且这可能会在不同的计算中产生不同的线条。但是,我主要关注从这个给定'方向'上从起点(r1,c1)到(r2,c2)的整个图像值的趋势。选择线路的特殊性对我的需求并不重要。 – achennu

回答

72

@斯文的答案是最简单的方式,但它是相当低效的大型阵列。如果你处理的是一个相对较小的数组,你不会注意到这个差异,如果你想从一个大的数据文件中获得一个数据文件(例如> 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

(+1)喜欢这些照片。 :-) – NPE

+0

很好的答案。我没有得到的唯一观点是*为什么*我提出的解决方案比较慢(我没有做时间安排,所以我甚至没有相信它)。 –

+1

感谢那个梦幻般的答案,以及eyecandy的+5。我从这个综合答案中学到了几件事(和新的功能!)。可能堆栈永远不会溢出你。 :) – achennu

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

我一直在测试上述程序星系图像,并认为我发现了一个小错误。我认为需要将转义添加到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

+0

我刚刚碰到了这个,并且改成 'zi = scipy.ndimage.map_coordinates(z,np.vstack((y,x)))' – gazzar

1

结合这个答案与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

+0

这只适用于'pcolormesh',因为缺少一个API调用返回'pcolormesh'的原始数据数组(在'img'中)。使用'img._meshWidth,img._meshHeight'可能会在其他地块中使用。看到这里:http://stackoverflow.com/questions/34840366/matplotlib-pcolor-get-array-returns-flattened-array-how-to-get-2d-data-ba – Demis

7

对于罐头溶液窥视scikit-imagemeasure.profile_line功能。

它是建立在scipy.ndimage.map_coordinates顶部为@Joeanswer并已一些额外的有用的功能在出炉。

+0

糟糕 - 错误的按钮,请忽略.. 。 –

相关问题