简体   繁体   English

在python中在零的2d网格上的两点之间插值线

[英]In python interpolate a line of values between two points on a 2d grid of zeroes

TLDR: TLDR:

After finding 2 points in a 2d numpy array how do I interpolate a line of 1s between them in an array of 0s? 在2d numpy数组中找到2个点后,如何在0s数组中插入它们之间的1s线?

Context: 内容:

Currently I am trying to do a 2d operation on a 3d array from binarized medical image data (0 and 1). 目前,我正在尝试从二进制化的医学图像数据(0和1)对3d数组进行2d操作。 The ultimate goal is to add a line of 1s between the starting and ending point of the filled voxels/pixels (ie the first and last instance). 最终目标是在填充的体素/像素的起点和终点(即第一个和最后一个实例)之间添加1s线。

For this I use SimpleITK to slice out a single row, which I then convert to a numpy array. 为此,我使用SimpleITK切出一行,然后将其转换为numpy数组。 Following other examples I have written functions that return a set of arrays that show the filled (1) pixels and empty (0) pixels. 在其他示例之后,我编写了函数,这些函数返回一组数组,这些数组显示填充的(1)像素和空的(0)像素。

From the earliest and last instances I "fill" the points between by adding 1 to all the points and then replaces the 2's with 1. What I want to do is then add this line back to the 2d array and, ultimately, back to the 3d array. 从最早和最后的实例开始,我将所有点加1,然后将2替换为1,以“填充”点之间的距离。我要做的是将这条线添加回2d数组,最终添加回2d数组。 3d阵列。

I know that I have a list of coordinates returned though the scipy.ndimage.map_coordinates(np.transpose(z), line_array), but I have no idea how to apply that to the original array. 我知道我有一个通过scipy.ndimage.map_coordinates(np.transpose(z),line_array)返回的坐标列表,但是我不知道如何将其应用于原始数组。

In my mind I could simply see creating a 2d array of 0s that may then simply be added to the original 2d array. 在我看来,我可以简单地看到创建一个0的2d数组,然后可以将其简单地添加到原始2d数组中。 Even so I can't figure out how to interpolate the line onto the 2d array, and then onto the 3d array. 即使这样,我也无法弄清楚如何将线插值到2d数组,然后插值到3d数组。 The desire to work in 2d first is because these arrays can be very very large. 首先要在2d中工作的愿望是因为这些数组可能非常大。 Any help would be very much appreciated. 任何帮助将不胜感激。

import numpy as np
import scipy
import matplotlib.pyplot as plt
import simpleITK as sitk
from timeit import default_timer as timer

#Functions used are shown below

#Read in a CT scan using SimpleITK
ctscan = sitk.ReadImage("example.mhd")

#Extract a slice along the Z axis (Simple ITK uses x, y, z indexing)
z = ctscan[:,:,150:151]

#Convert the slice to a numpy array, which is then z, y, x indexing
z = sitk.GetArrayFromImage(z)

#Drop the 3rd dimension to view in matplotlib
z = z[0, :, :]
plt.imshow(z)

#Get the line and the line array
line, line_array = extract_line(0, 0, z.shape[1], z.shape[0], z)

#Returned from scipy.ndimage.map_coordinates
#1d array of 490 elements
#In [153]: len(line)
#Out[153]: 490

#2d tuple with coordinates 
#In [154]: line_array.shape
#Out[154]: (2, 490)

#Get the indices that are filled, the first and last contiguous set
points, start, end = get_line_limits(line)

#Number of arrays that are filled with 1s
#In [158]: len(points)
#Out[158]: 36

#First instance of contiguous 1's
#In [159]: start
#Out[159]: array([35, 36, 37], dtype=int64)

#Last instance of contiguous 1's
#In [160]: end
#Out[160]: array([424, 425], dtype=int64)

#If I am interpreting this then that should be the first point
#In [161]: x1 = line_array[0][35]
#In [162]: y1 = line_array[1][35]

#And this the last
#In [161]: x2 = line_array[0][425]
#In [162]: y2 = line_array[1][425]

#From here I know that I can create a "blank" array, but I don't know how
#To "place" the line...
V = np.zeros((z.shape[0], z.shape[1]))

In [166]: V = np.zeros((z.shape[0], z.shape[1]))

'''
In [167]: V
Out[167]:
array([[0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.],
       ...,
       [0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.]])
'''

#and then 
new = z + V

#Hopefully this would then show the line between the two defined points #array.
plt.imshow(new)


"""
Functions. It's likely that the answer is already somewhere in here but I've missed it from sheer idiocy.
"""

def extract_line(x0, y0, x1, y1, z):
    """
    Extract a line from a 2d slice.
    :param x0: Starting point of line on x axis, usually 0
    :param y0: Starting point of line on y axis, usually 0
    :param x1: Ending point of line on x axis, usually the len
    :param y1: Ending point of line on x axis, usually the len
    :param z: The 2d Numpy array
    :return: Returns an interpolated line with filled pixels and their coordinates. Taken from https://stackoverflow.com/questions/7878398/how-to-extract-an-arbitrary-line-of-values-from-a-numpy-array
    """

    start = timer()
    x0, y0 = float(x0), float(y0) # These are in pixel coordinates!!
    x1, y1 = float(x1), float(y1)
    x_len = abs(x0 - x1)
    y_len = abs(y0 - y1)
    line_length = int(np.sqrt((x_len**2) + (y_len**2))) #Length of line
    x, y = np.linspace(x0, x1, line_length), np.linspace(y0, y1, line_length)
    line_array = np.vstack((x, y))
    # Extract the values along the line, using cubic interpolation
    zi = scipy.ndimage.map_coordinates(np.transpose(z), line_array)
    '''
    #Uncomment this is you want to view the results.
    #Also I understand matplotlib clearly does this somehow, any way to
    #Simple return the array? 
    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()
    '''
    stop = timer()
    print(abs(start - stop))
    return zi, line_array

def consecutive(data, stepsize=1):
    """
    Function to find consective elements in an array. 
    :param data: numpy array.
    :param stepsize: how many values between elements before splitting the array. 
    :return: Returns an array broken along the step size.
    """
    consecutive = np.split(data, np.where(np.diff(data) != stepsize)[0]+1)
    return consecutive


def get_line_limits(line):
    """
    Function to find the first and last instance of the filed pixels as determined by extract_line.
    :param line: numpy array returned from extract line.
    :return: Returns the indeces of the filled (i.e. 1) pixels, along with the first and last set. 
    """
    start = timer()
    line_points = np.where(line==1)
    #Call the consecutive function to get the contiguous points.
    line_points = consecutive(line_points[0])
    line_start = line_points[0]
    line_end = line_points[(len(line_points) - 1)]
    stop = timer()
    print("It took {} seconds to get limits.".format(abs(start-stop)))
    return line_points, line_start, line_end


def place_line(x0, y0, x1, y1, z):
    """
    Mystical function that doesn't exist yet because I can't seem to work it out. 
    """

The expected result would be an array that looked like the original but with a clear line from the start to the end of the "filled" pixels. 预期结果将是一个看起来像原始数组的数组,但从“填充”像素的开始到末尾都有清晰的线条。

So thanks to Ardweaden for suggesting I find a simple way to state my objective, and Mad Physicist for pointing me in the direction at the Bresenham line algorithm. 因此,感谢Ardweaden建议我找到一种简单的方法来陈述我的目标,并感谢Mad Physicist向我指出布雷森汉姆线算法的方向。 After that I was able to find a couple python implementation that bridged the gap. 之后,我能够找到一些缩小差距的python实现。 The working code for two versions is below (if anyone has a faster way to do it I will mark that as the accepted answer): 下面是两个版本的工作代码(如果有人可以用更快的方式完成代码,我将其标记为可接受的答案):

#Starting from the line limits obtained in the original questions code
x0 = np.ceil(line_array[0][start[0]]) #first filled element on x
y0 = np.ceil(line_array[1][start[0]]) #first filled element on y

x1 = np.floor(line_array[0][end[(len(end) - 1)]]) #last filled element on x
y1 = np.floor(line_array[1][end[(len(end) - 1)]]) #last filled element on y

#The skimage implementation of Bresenham's line algorithm 
# https://scikit-image.org/docs/dev/api/skimage.draw.html#skimage.draw.line
from skimage.draw import line

#Make a 2d array of 0s that is the same size as the original 2d array z
V = np.zeros((z.shape[0], z.shape[1]), dtype=np.uint8)

#This flips the y and x from how I think of it...
rr, cc = line(int(y0), int(x0), int(y1), int(x1))

#Cast the line coordinates as 1s
V[rr, cc] = 1

#Then add the new array to the old array (although could just write the line on z).
new_z = V + z

#Use numpy where to change the 2 values back to 1 values.
new_z[np.where(new_z==2)] = 1

#Show the final image
plt.imshow(new_z)


# This implementation is a short script from https://github.com/encukou/bresenham and can just be 
# brought into the existing code base without importing an external library. 

# The code can be modified, but as is doesn't have a return so place it into a list
new_line = list(bresenham(int(y0), int(x0), int(y1), int(x1)))

#Use list comprehension to get the arrays
x_array = np.array([item[0] for item in new_line])
y_array = np.array([item[1] for item in new_line])

#Then cast them as 1's
V[x_array, y_array] = 1

#Combine the original and the new array
new_z = V + z

#Recast the 2's to 1's
new_z[np.where(new_z==2)] = 1

#And view
plt.imshow(new_z)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM