VTK 实现对vtkpolyata 膨胀放缩

VTK 实现对vtkpolyata 膨胀放缩

实现VTKpolydata 向外扩大, 目前了解到的有两种方法

  • 方法1

使用vtkWarpVector 通过设置放缩因子和normals,使得vtkpolyata向外膨胀的效果

但是该种方式更多的是成比例的膨胀,即膨胀多少倍等,很难做到具体的控制膨胀多少距离

另外,注意放缩后可能没那么平滑了, 或许需要进行stripper去除表面多边形
在这里插入图片描述

def update_edge_size(input_polydata, scaleFactor, iteration=50):
    """vtkWarpVector 放缩 vtkpolyata"""
    
    
    # 先将polydata表面进行一定平滑处理
    smooth_filter = vtk.vtkSmoothPolyDataFilter()
    smooth_filter.SetInputData(input_polydata)
    smooth_filter.SetNumberOfIterations(iteration)
    smooth_filter.SetRelaxationFactor(0.2)
    smooth_filter.Update()
    
	# 清理polydata 数据中的冗余信息,包括重复的点等,简化数据集
    clean = vtk.vtkCleanPolyData()
    clean.SetInputData(smooth_filter.GetOutput())
    
    normals = vtk.vtkPolyDataNormals()
    normals.SetInputConnection(clean.GetOutputPort())
    normals.SplittingOff()
    # 使用vtkWarpVector 进行放缩
    warp = vtk.vtkWarpVector()
    warp.SetInputConnection(normals.GetOutputPort())
    warp.SetInputArrayToProcess(
        0, 0, 0,
        vtk.vtkDataObject().FIELD_ASSOCIATION_POINTS,
        vtk.vtkDataSetAttributes().NORMALS
    )
    warp.SetScaleFactor(scaleFactor)
    warp.Update()
    warped_polydata = warp.GetOutput()
    return warped_polydata
    

  • 方法2

使用 vtkImplicitPolyDataDistance 计算一个有向距离场(signed distance field), 距离场的数据表示距离polydata表面的距离,在polydata外部值为正数, 内部值为负数,表面值为0

具体官方例子可见 这里

官方例子中还是以点的形式展示了有向距离场

但是我的目的是通过SDF 实现对polydata按照指定长度的膨胀,就是说膨胀3mm的时候,希望膨胀后的polydata表面距离原先polydata表面的平均距离也是3mm

考虑了一下大致思路是, 通过vtkImplicitPolyDataDistance 得到一个有向距离场的矩阵,然后根据希望放大的长度删选除膨胀后的点数据,得到一个mask,再将mask转换为polydata,得到膨胀了指定距离的polydata

在这里插入图片描述

def sdf_polydata(polydata:vtk.vtkPolyData, volume_sitk_img: sitk.Image,target_size=20):
    """
		通过SDF 膨胀 polydata
		params:
			polydata: 输入待膨胀的polydata
			volume_sitk_img: sitk.Image 相应的体数据,主要是为了得到矩阵大小以及进行RAS坐标和IJK世界坐标的转换,如果有指定的矩阵大小和RAS坐标和IJK坐标转换则不需要
			target_size: 希望膨胀的大小
        """
    if not target_size or target_size <= 1:
        return None

    volume_arr = sitk.GetArrayFromImage(volume_sitk_img)
    sdf_arr = np.zeros_like(volume_arr, np.float16)
    origin = volume_sitk_img.GetOrigin()
    spacing = volume_sitk_img.GetSpacing()

    volume_shape = volume_arr.shape

    # 膨胀后 mask的包围盒
    polydata_bounds = [0] * 6
    polydata.GetBounds(polydata_bounds)
    ras_x_min, ras_x_max, ras_y_min, ras_y_max, ras_z_min, ras_z_max = polydata_bounds

    ras_x_min, ras_y_min, ras_z_min = (
        ras_x_min - target_size - 1,
        ras_y_min - target_size - 1,
        ras_z_min - target_size - 1
    )
    ras_x_max, ras_y_max, ras_z_max = (
        ras_x_max + target_size + 1,
        ras_y_max + target_size + 1,
        ras_z_max + target_size + 1
    )

    impl_distance = vtk.vtkImplicitPolyDataDistance()
    impl_distance.SetInput(polydata)

    min_ijk_point = volume_sitk_img.TransformPhysicalPointToIndex([ras_x_min, ras_y_min, ras_z_min])
    max_ijk_point = volume_sitk_img.TransformPhysicalPointToIndex([ras_x_max, ras_y_max, ras_z_max])

    # 这部分比较耗时
    # 遍历包围盒,计算包围盒中每个值保存的是当前点距离polydata表面的距离
    for x in range(min_ijk_point[0], max_ijk_point[0]):
        for y in range(min_ijk_point[1], max_ijk_point[1]):
            for z in range(min_ijk_point[2], max_ijk_point[2]):
                tmp_ijk_point = [x, y, z]
                tmp_ras_point = volume_sitk_img.TransformIndexToPhysicalPoint(tmp_ijk_point)

                if not 0 <= tmp_ijk_point[0] <= volume_shape[2] or not 0 <= tmp_ijk_point[1] <= volume_shape[
                    1] or not 0 <= tmp_ijk_point[2] <= volume_shape[0]:
                    print(f"point {tmp_ijk_point} is over rand {volume_shape}")
                    continue
				# 计算当前点到polydata表面的距离, 由于计算需要ras坐标,所以把索引下标再转回了ras
                distance = impl_distance.EvaluateFunction(tmp_ras_point)
                sdf_arr[tmp_ijk_point[2], tmp_ijk_point[1], tmp_ijk_point[0]] = round(distance, 2)


    # 得到膨胀后mask
    new_mask = np.zeros_like(sdf_arr, np.uint8)
    logic = np.logical_and(sdf_arr <= target_size, sdf_arr != 0)
    new_mask[logic] = 1
	
    # 进行开运算,去掉膨胀后mask中间的一些空洞
    for i in range(0, new_mask.shape[0]):
        new_mask[i] = open_compute(new_mask[i], 3)

    # 得到膨胀后的polydatamask之后需要将其转为polydata
    # 分两步走。 1,mask转为imagedata; 2, imagedata转为polydata

    # 将膨胀后mask转为imagedata
    tmp_imagedata = vtk.vtkImageData()
    tmp_imagedata.SetDimensions(*volume_sitk_img.GetSize())
    tmp_imagedata.SetOrigin(origin)
    tmp_imagedata.SetSpacing(spacing)

    vtk_array = numpy_support.numpy_to_vtk(new_mask.flatten(), array_type=vtk.VTK_INT)
    tmp_imagedata.GetPointData().SetScalars(vtk_array)
	
    # imagedata转为polydata
    marching_cubes = vtk.vtkDiscreteMarchingCubes()
    marching_cubes.SetInputData(tmp_imagedata)
    marching_cubes.SetValue(0, 1)
    # 设置法向量计算开启
    marching_cubes.ComputeNormalsOn()
    marching_cubes.ComputeScalarsOn()
    marching_cubes.Update()
    res_polydata = marching_cubes.GetOutput()
    return res_polydata


def open_compute(img_arr, kernel_size=3, iterations=1):
	# 开运算
    kernel = np.ones((kernel_size, kernel_size), np.int8)
    dilated_arr = cv2.dilate(img_arr, kernel, iterations=iterations)
    erode_arr = cv2.erode(dilated_arr, kernel, iterations=iterations)
    return erode_arr

补充:

使用vtkImplicitPolyDataDistance 计算时候可能会受到 polydata的normals的影响,当法线指向内部时候, 外部的距离变为负数,内部变为正数。 所以需要法线指向外部(更方便计算处理)

修正polydata 法线:

def updatepolydata_normals(polydata):
	normals = vtk.vtkPolyDataNormals()
    normal_filter.SetInputData(polydata)
    normal_filter.SetComputePointNormals(0)
    normal_filter.SetComputeCellNormals(1)
    normal_filter.SetAutoOrientNormals(1)
    normal_filter.Update()
    polydata = normal_filter.GetOutput()
    return polydata
  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吃肉夹馍不要夹馍

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值