voxel 转换为 patient coordinate(python实现)

dcm

其实自己感觉还未完全理解(博客内容若有错误请指出),先记下来,等答辩、课题等事情弄好再重新学习并补充。

一些基础概念别人博客已经写的很好了,我理解的关键点为:
1、病人坐标系的xyz定义方向为LPS(并非所有的,一些集成3D slice的软件用的是RAS)
2、图像坐标xy定义方向:(0,0)代表图像左上角,(max(x),max(y))代表图像右下角(这儿x的延伸方向也是i的延伸方向,y的延伸方向等同于j的延伸方向)
3、仿射变换矩阵定义为
在这里插入图片描述
4、最需要注意的是,在下面_merge_slice_pixel_arrays函数里也有注释说明,python中数组默认order=C,即变换最快的轴向反而靠后,也就是我们一般读序列进来会是(Z,Y,X),而matlab数组的默认order=F,读进来是正常的(X,Y,Z)。
对于dcm数据中的pixel_data而言,其为(height,width),用数组的index取值时
pixel_data[i][j]代表的是第i行,第j列,这儿与第三点仿射变换矩阵中的i,j含义对调了,因此下面的代码里原作者将order改为了F,且将pixel_data进行了转置,但由于这种操作会不利于后续的操作。我认为只要清楚了这点,在后续操作需要用到变换矩阵时,手动进行矩阵的transpose即可。

import pydicom
import numpy as np
import os

"""
DICOM voxel to patient coordinate system mapping

https://github.com/innolitics/dicom-numpy/tree/master/dicom_numpy
"""
path='C:\\Users\\dell7920\\Desktop\\object\\3Dircadb1.1\\PATIENT_DICOM\\'
list_of_dicom_files = os.listdir(path)
datasets = [pydicom.dcmread(path + f) for f in list_of_dicom_files]

def _requires_rescaling(dataset):
    return hasattr(dataset, 'RescaleSlope') or hasattr(dataset, 'RescaleIntercept')

def combine_slices(slice_datasets, rescale=None):
    voxels = _merge_slice_pixel_arrays(slice_datasets, rescale)
    transform = _ijk_to_patient_xyz_transform_matrix(slice_datasets)

    return voxels, transform

def _merge_slice_pixel_arrays(slice_datasets, rescale=None):
    first_dataset = slice_datasets[0]
    num_rows = first_dataset.Rows
    num_columns = first_dataset.Columns
    num_slices = len(slice_datasets)

    sorted_slice_datasets = _sort_by_slice_spacing(slice_datasets)
    # print(sorted_slice_datasets)

    if rescale is None:
        rescale = any(_requires_rescaling(d) for d in sorted_slice_datasets)

    if rescale:
        # 注意,numpy默认的order是C,matlab才是fortan,Fortan最先读入增长最快的那个轴向,ct中是(X,Y,Z)
        voxels = np.empty((num_rows, num_columns, num_slices), dtype=np.float32)
        for k, dataset in enumerate(sorted_slice_datasets):
            slope = float(getattr(dataset, 'RescaleSlope', 1))
            intercept = float(getattr(dataset, 'RescaleIntercept', 0))
            # 原githubvoxel用的order='F', dataset.pixel_array.T
            voxels[:, :, k] = dataset.pixel_array.astype(np.float32) * slope + intercept
    else:
        dtype = first_dataset.pixel_array.dtype
        voxels = np.empty((num_rows, num_columns, num_slices), dtype=dtype)
        for k, dataset in enumerate(sorted_slice_datasets):
        # 原githubvoxel用的order='F', dataset.pixel_array.T
            voxels[:, :, k] = dataset.pixel_array

    return voxels

def _sort_by_slice_spacing(slice_datasets):
    slice_spacing = _slice_positions(slice_datasets)
    # print("slice_spacing ",sorted(slice_spacing))
    return [d for (s, d) in sorted(zip(slice_spacing, slice_datasets))]

def _extract_cosines(image_orientation):
    row_cosine = np.array(image_orientation[:3])
    column_cosine = np.array(image_orientation[3:])
    slice_cosine = np.cross(row_cosine, column_cosine)
    # print("slice cosine:",slice_cosine)
    return row_cosine, column_cosine, slice_cosine

def _slice_positions(slice_datasets):
    """
    获取切片在patient coordinate下的z轴坐标
    """
    image_orientation = slice_datasets[0].ImageOrientationPatient
    row_cosine, column_cosine, slice_cosine = _extract_cosines(image_orientation)
    return [np.dot(slice_cosine, d.ImagePositionPatient) for d in slice_datasets]


def _slice_spacing(slice_datasets):
    """
    获取切片在patient coordinate下的平均间隔
    """
    if len(slice_datasets) > 1:
        slice_positions = _slice_positions(slice_datasets)
        slice_positions_diffs = np.diff(sorted(slice_positions))
        return np.mean(slice_positions_diffs)
    else:
        return 0.0

def _ijk_to_patient_xyz_transform_matrix(slice_datasets):
    first_dataset = _sort_by_slice_spacing(slice_datasets)[0]
    image_orientation = first_dataset.ImageOrientationPatient
    row_cosine, column_cosine, slice_cosine = _extract_cosines(image_orientation)

    row_spacing, column_spacing = first_dataset.PixelSpacing
    slice_spacing = _slice_spacing(slice_datasets)

    transform = np.identity(4, dtype=np.float32)

    transform[:3, 0] = row_cosine * column_spacing
    transform[:3, 1] = column_cosine * row_spacing
    transform[:3, 2] = slice_cosine * slice_spacing

    transform[:3, 3] = first_dataset.ImagePositionPatient

    return transform

voxels, transform = combine_slices(datasets)

print(transform)

参考:
http://nipy.org/nipy/devel/code_discussions/understanding_affines.html
https://nben.net/MRI-Geometry/#mri-geometry
https://brainder.org/2012/09/23/the-nifti-file-format/
https://blog.csdn.net/zssureqh/article/details/61636150

nii (还未实现,待补充)

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
点云的滤波可以通过一些算法来实现,比如下采样(Downsampling)、统计滤波(Statistical Outlier Removal)、半径滤波(Radius Outlier Removal)等等。下面我将分别介绍这几种滤波方法的实现。 首先需要安装依赖库`pyrealsense2`和`open3d`,可以通过pip命令安装: ``` pip install pyrealsense2 open3d ``` 下采样(Downsampling): 下采样是将点云中的点进行降采样,从而减少点云的点数。这种方法常用于大规模点云的处理,可以有效提高处理效率。 ```python import open3d as o3d # 读取点云文件 pcd = o3d.io.read_point_cloud('input.pcd') # 下采样 downsampled = pcd.voxel_down_sample(voxel_size=0.05) # 保存下采样后的点云 o3d.io.write_point_cloud('downsampled.pcd', downsampled) ``` 统计滤波(Statistical Outlier Removal): 统计滤波是一种通过计算点周围点的统计学信息来识别和删除离群点的方法。 ```python import open3d as o3d # 读取点云文件 pcd = o3d.io.read_point_cloud('input.pcd') # 统计滤波 cl, ind = pcd.remove_statistical_outlier(nb_neighbors=20, std_ratio=2.0) # 保存滤波后的点云 o3d.io.write_point_cloud('filtered.pcd', pcd.select_by_index(ind)) ``` 半径滤波(Radius Outlier Removal): 半径滤波是一种通过计算每个点周围邻居点之间的距离来删除离群点的方法。 ```python import open3d as o3d # 读取点云文件 pcd = o3d.io.read_point_cloud('input.pcd') # 半径滤波 cl, ind = pcd.remove_radius_outlier(nb_points=16, radius=0.05) # 保存滤波后的点云 o3d.io.write_point_cloud('filtered.pcd', pcd.select_by_index(ind)) ``` 以上是三种常用的点云滤波方法的Python实现。需要注意的是,这些算法在不同的场景下可能会有不同的效果,可以根据实际情况选择合适的算法。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值