简体   繁体   English

如何使用 Scipy 从 3D 体积中提取任意二维切片?

[英]How to extract arbitrary 2D slice from 3D volume using Scipy?

I'm using Scipy for rendering planes from 3D data (vector 200x200x200).我正在使用 Scipy 从 3D 数据(矢量 200x200x200)渲染平面。 I can specify the wanted plane by 2 vectors or vector and an angle.我可以通过 2 个向量或向量和一个角度来指定想要的平面。 I want to extract such an arbitrary slice from this 3D volume.我想从这个 3D 卷中提取这样一个任意切片。 I found how to do it in Matlab: http://www.mathworks.com/help/techdoc/ref/slice.html How do I do it in Scipy? I found how to do it in Matlab: http://www.mathworks.com/help/techdoc/ref/slice.html How do I do it in Scipy?

您可以使用scipy.ndimage.interpolation.rotate将3d阵列旋转到所需的任何角度(它使用样条曲线插值),然后可以从中取出一个切片。

def extract_slice(data, triplet): def extract_slice(数据,三元组):
""" “””
Algorithm: 算法:
1. find intersections of the plane with the data box edges 1.找到平面与数据框边缘的交点
2. for these pts, find axis-oriented b-box 2.对于这些点,找到面向轴的b-box
3. find the "back" trans (A,T) from R2 to R3, like X' = AX + T 3.找到从R2到R3的“反向”反(A,T),例如X'= AX + T
use (0,0), (0,h), (w,0) which are easy to calculate 使用易于计算的(0,0),(0,h),(w,0)
4. use the trans (with trilinear-interpolation) for every value in the 2D (w,h) image 4.对2D(w,h)图像中的每个值使用trans(带有三线性插值)
""" “””

I will release the code properly in a few months I believe as a part of this project. 我相信,作为该项目的一部分,我将在几个月后正确发布代码。

I was addressing a similar task as the OP so I came up with this code based on numpy (not scipy) to extract any given slice from a volume given a position vector of any point of the plane and three orthogonal orientation vectors.我正在处理与 OP 类似的任务,因此我提出了基于numpy (不是 scipy)的代码,以从给定平面任意点的 position 向量和三个正交方向向量的体积中提取任何给定切片。

I apologise for the length of my answer, but given the complexity of the problem at hand i thought it would be better to give this amount of detail.我为我的答案的长度道歉,但考虑到手头问题的复杂性,我认为最好提供这么多的细节。

For my particular problem these vectors were defined in mm instead of pixels so the spacing (ie distance between two consecutive volume pixels at each direction) was also used as input.对于我的特定问题,这些向量以毫米而不是像素定义,因此间距(即每个方向上两个连续体积像素之间的距离)也用作输入。 I have used a nearest neighbour approach to interpolate the subpixel points of the slice.我使用了最近邻方法来插入切片的子像素点。

reslice_volume (volume, spacing, o1, o2, n, pos)

The main steps behind this algorithm are as follow.该算法背后的主要步骤如下。 Note that i use plane and slice interchangeably:请注意,我可以互换使用平面和切片:

1. Get intersection lines between the desired plane and the bounds of the volume. 1.获取所需平面和体积边界之间的相交线

    def PlaneBoundsIntersectionsLines (n, pos):
  """Outputs points and vectors defining the lines that the view creates by intersecting the volume's bounds.
      Input:
        Normal vector of the given plane and the coords of a point belonging to the plane.
      Output:
        normals_line, points_line
        """
    def intersectionPlanePlane(n1,p1,n2,p2):
        # Get direction of line
        nout = np.cross(n1.reshape((1,3)),n2.reshape((1,3))).reshape(3,1)
        nout = normalizeLength(nout)
        M =  np.concatenate((n1.reshape(1,3),n2.reshape(1,3)), axis=0)
        b = np.zeros((2,1))
        # print(n1.shape, p1.shape)
        b[0,0]=np.dot(n1,p1)
        b[1,0]=np.dot(n2,p2)
        pout,resid,rank,s = np.linalg.lstsq(M,b, rcond=None)
        return pout, nout
      # ... For each face
      normalFaces = np.concatenate((np.eye(3,3),np.eye(3,3)), axis = 1)
      pointsFaces = np.array([[0,0,0],[0,0,0],[0,0,0], [379.9872, 379.9872, 169.5], [379.9872, 379.9872, 169.5], [379.9872, 379.9872, 169.5]]).transpose()
      points_line = np.zeros((3,6))
      normals_line = np.zeros((3,6))
      for face in range(6):
        n1 = normalFaces[:,face].reshape(3,)
        p1 = pointsFaces[:,face].reshape(3,)
        pout, nout = intersectionPlanePlane(n1,p1,n,pos)
        points_line[:,face] = pout.reshape((3,))
        normals_line[:,face] = nout.reshape((3,))
      return normals_line, points_line

2. Get intersection points between these lines that are close enough to the borders of the volume to be considered corners of the intersection between the plane and the volume. 2.获取这些线之间足够接近体积边界的交点,以将其视为平面和体积之间相交的角点。

def FindPlaneCorners(normals_line, points_line):
  """Outputs the points defined by the intersection of the input lines that 
  are close enough to the borders of the volume to be considered corners of the view plane.
      Input:
        Points and vectors defining lines
      Output:
        p_intersection, intersecting_lines
        """
  def intersectionLineLine(Up,P0,Uq,Q0):
    # Computes the closest point between two lines
    # Must be column points
    b = np.zeros((2,1))
    b[0,0] = -np.dot((P0-Q0),Up)
    b[1,0] = -np.dot((P0-Q0),Uq)
    A = np.zeros((2,2))
    A[0,0] = np.dot(Up,Up)
    A[0,1] = np.dot(-Uq,Up)
    A[1,0] = np.dot(Up,Uq)
    A[1,1] = np.dot(-Uq,Uq)
    if ( np.abs(np.linalg.det(A)) < 10^(-10) ):
      point = np.array([np.nan, np.nan, np.nan]).reshape(3,1)
    else:
      lbd ,resid,rank,s = np.linalg.lstsq(A,b, rcond=None)
      # print('\n')
      # print(lbd)
      P1 = P0 + lbd[0]*Up;
      Q1 = Q0 + lbd[1]*Uq;
      point = (P1+Q1)/2;
    return point
  # ... ... Get closest point for every possible pair of lines and select only the ones inside the box
  npts = 0
  p_intersection = []
  intersecting_lines = []
  # ... Get all possible pairs of lines
  possible_pairs = np.array(list(itertools.combinations(np.linspace(0,5,6), 2)))
  for pair in possible_pairs:
    k = int(pair[0])
    j = int(pair[1])
    Up = normals_line[:,k]
    P0 = points_line[:,k]
    Uq = normals_line[:,j]
    Q0 = points_line[:,j]
    closest_point = intersectionLineLine(Up,P0,Uq,Q0)
    epsilon = 2.2204e-10
    # ... ... Is point inside volume? Is it close to the border?
    if closest_point[0] <= 379.9872 + epsilon and closest_point[0] >= 0 - epsilon and \
                closest_point[1] <= 379.9872 + epsilon and closest_point[1] >= 0 - epsilon and \
                closest_point[2] <= 169.5 + epsilon and closest_point[2] >= 0 - epsilon:
      # ... ...  Is it close to the border? 25 mm?
      th = 25
      if 379.9872 - closest_point[0] <= th or closest_point[0] - 0 <= th or \
          379.9872 - closest_point[1] <= th or closest_point[1] - 0 <= th or \
          169.5 - closest_point[2] <= th or closest_point[2] - 0 <= th:
        # print('It is close to teh border')
        npts += 1
        p_intersection.append(closest_point)
        intersecting_lines.append([k,j])

  p_intersection = np.array(p_intersection).transpose()

  return p_intersection, intersecting_lines

3. Transform the points found into the slice's reference frame (sRF) (we can center the RF arbitrarily within the slice plane). 3.将找到的点转换为切片的参考框架(sRF)(我们可以在切片平面内任意居中 RF)。

dim = volume.shape
  # ... Get intersection lines between plane and volume bounds
  normals_line, points_line = PlaneBoundsIntersectionsLines (n, pos)

  # ... Get intersections between generated lines to get corners of view plane
  p_intersection, intersecting_lines = FindPlaneCorners (normals_line, points_line)

  # ... Calculate parameters of the 2D slice
  # ... ... Get corners of slice from volume RF (vrf) to slice RF (srf) - in this case centered in the middle of teh slice
  # ... ... ... Define T_vrf2srf
  Pose_slice_vrf = M_creater(o1,o2,n,pos)
  # ... ... ... Apply transform
  p_intersection_slicerf = np.zeros(p_intersection.shape)
  for corner in range(p_intersection.shape[1]):
    pt_arr = np.concatenate((p_intersection[:,corner],np.ones((1,))) ,axis = 0).reshape((4,1))
    p_intersection_slicerf[:,corner] = np.matmul(np.linalg.inv(Pose_slice_vrf), pt_arr)[:-1].reshape((3,))

4. Get minimum x and y coordinates across these points and define a corner point that will be used as origin of the plane/slice. 4.获取这些点的最小 x 和 y 坐标,并定义一个角点,用作平面/切片的原点。 Transform this origin point back to the volume's RF (vRF) and define a new transform matrix that shifts RF from vRF to a sRF but now centered on said origin point.将此原点转换回体积的 RF (vRF) 并定义一个新的转换矩阵,将 RF 从 vRF 转换为 sRF,但现在以所述原点为中心。

5. From these inslice points coordinates we can determine the size of the slice and then use it to generate all possible inslice indexes of the target slice. 5.根据这些切点坐标,我们可以确定切片的大小,然后用它来生成目标切片的所有可能的切点索引。

 # ... ... Get slice size based on corners and spacing
  spacing_slice = [1,1,8]
  min_bounds_slice_xy = np.min(p_intersection_slicerf,axis=1)
  max_bounds_slice_xy = np.max(p_intersection_slicerf,axis=1)
  size_slice_x = int(np.ceil((max_bounds_slice_xy[0] - min_bounds_slice_xy[0] - 1e-6) / spacing_slice[0]))
  size_slice_y = int(np.ceil((max_bounds_slice_xy[1] - min_bounds_slice_xy[1] - 1e-6) / spacing_slice[1]))
  slice_size = [size_slice_x, size_slice_y, 1]
  print('slice_size')
  print(slice_size)

  # ... ... Get corner in slice coords and redefine transform mat - make corner origin of the slice
  origin_corner_slice = np.array([min_bounds_slice_xy[0],min_bounds_slice_xy[1],0])
  pt_arr = np.concatenate((origin_corner_slice,np.ones((1,))) ,axis = 0).reshape((4,1))
  origin_corner_slice_vrf = np.matmul(Pose_slice_vrf, pt_arr)[:-1].reshape((3,))
  Pose_slice_origin_corner_vrf = M_creater(o1,o2,n,origin_corner_slice_vrf)

  # ... ... Get every possible inslice coordinates
  xvalues = np.linspace(0,size_slice_x-1,size_slice_x)
  yvalues = np.linspace(0,size_slice_y-1,size_slice_y)
  zvalues = np.linspace(0,0,1)
  xx, yy = np.meshgrid(xvalues, yvalues)
  xx = xx.transpose()
  yy = yy.transpose()
  zz = np.zeros(xx.shape)
  inslice_coords = np.concatenate((xx.reshape(-1,1), yy.reshape(-1,1), zz.reshape(-1,1)), axis = 1)

6. Next step is to use the newly defined transform matrix (step 4) to map every possible inslice index to the volume's reference frame . 6.下一步是使用新定义的变换矩阵(步骤 4)将map 每个可能的 inslice 索引到卷的参考帧

# ... ... Map every point of slice into volume's RF
  inslice_coords_vrf = np.zeros(inslice_coords.shape)
  for coord_set in range(inslice_coords.shape[0]):
    pt_arr = np.concatenate((inslice_coords[coord_set,:],np.ones((1,))) ,axis = 0).reshape((4,1))
    inslice_coords_vrf[coord_set,:] = np.matmul(Pose_slice_origin_corner_vrf, pt_arr)[:-1].reshape((3,))

7. We now have all the vRF coordinates that the slice encompasses that should be promptly converted into pixel values by dividing them by the respective spacing . 7.我们现在有了切片包含的所有 vRF 坐标,应该通过将它们除以各自的间距来迅速将其转换为像素值 At this step we find that we end up with non-integer pixel values as the slices passes through subpixel locations of the volume.在这一步,我们发现当切片通过体积的子像素位置时,我们最终得到了非整数像素值。 We round the pixel value to its nearest integer - nearest neighbour interpolation .我们将像素值四舍五入到最接近的 integer -最近邻插值

# ... ... ... Convert to pixel coord - here we used teh resampled spacing
  inslice_coords_vrf_px = inslice_coords_vrf.copy()
  inslice_coords_vrf_px[:,0] = inslice_coords_vrf[:,0] / spacing[0]
  inslice_coords_vrf_px[:,1] = inslice_coords_vrf[:,1] / spacing[1]
  inslice_coords_vrf_px[:,2] = inslice_coords_vrf[:,2] / spacing[2]

  # ... ... Interpolate pixel value at each mapped point - nearest neighbour int
  # ... ... ... Convert pixel value to its closest existing value in the volume
  inslice_coords_vrf_px = np.round(inslice_coords_vrf_px, 0).astype(int)

8. Next, we determine which pixels of the slice are actually within the bounds of the volume and get their values. 8.接下来,我们确定切片的哪些像素实际上在体积的范围内并获取它们的值。 Pixels outside volume are padded to 0.体积外的像素被填充为 0。

# ... ... Find slice voxels within volume bounds
  in_mask = np.zeros((inslice_coords_vrf_px.shape[0], 1))
  idx_in = []
  for vox in range(in_mask.shape[0]):
    if not np.any(inslice_coords_vrf_px[vox,:]<0) and \
                  inslice_coords_vrf_px[vox,0]<dim[0] and \
                  inslice_coords_vrf_px[vox,1]<dim[1] and \
                  inslice_coords_vrf_px[vox,2]<dim[2]:
      in_mask[vox] = 1
      idx_in.append(vox)
  idx_in = np.array(idx_in)
  # ... ... Get pixel value from volume based on interpolated pixel indexes
  extracted_slice = np.zeros((inslice_coords_vrf_px.shape[0], 1))
  for point in range(inslice_coords_vrf_px.shape[0]):
    if point in idx_in:
      vol_idx = inslice_coords_vrf_px[point,:]
      extracted_slice[point] = volume[vol_idx[0], vol_idx[1], vol_idx[2]]
  # ... ... Reshape to slice shape
  extracted_slice = extracted_slice.reshape((slice_size[0], slice_size[1]))

I added a plot for extra clarity.为了更加清晰,我添加了 plot。 Here the volume is defined by the bounding box in black.这里的体积由黑色的边界框定义。 Line intersections of the slice with the planes defined by the faces of the box/volume in dotted orange.切片与由盒子/体积的面定义的平面的线交叉点以橙色虚线表示。 In blue the intersection points between the previous lines.蓝色是前几行之间的交点。 Points in pink belong to the slice and the orange ones belong to the slice and are within the volume.粉红色的点属于切片,橙色的点属于切片并且在体积内。

In my case i was dealing with MRI volumes, so as example I added my resulting slice from the volume.在我的情况下,我正在处理 MRI 体积,因此作为示例,我从体积中添加了我的结果切片。

图1

图 2

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

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