医学图像处理 利用pytorch实现的可用于反传的Radon变换和逆变换

医学图像处理 利用pytorch实现的可用于反传的Radon变换和逆变换

前言

Computed Tomography(CT,计算机断层成像)技术作为如今医学中重要的辅助诊断手段,也是医学图像研究的重要主题。如今,随着深度学习对CT重建、CT分割的研究逐渐深入,论文开始越来越多的利用CT的每一个环节,来扩充Feature或构造损失函数。

但是每到这个时候,一个问题就出现了,如果我要构造损失函数,我势必要保证这个运算中梯度不会断掉,否则起不到优化效果。而Radon变换目前好像没有人直接用其当作损失函数的一部分,很奇怪,故在此实现pytorch版本的Radon变换,已经验证可以反传(但是反传的对不对不敢保证,只保证能计算出反传的值)。希望能帮到需要的同学。

参考了这两篇博文,在此十分感谢这位前辈。
Python实现离散Radon变换
Python实现逆Radon变换------直接反投影和滤波反投影

代码

python 复制代码
from typing import Optional
import numpy as np
import matplotlib.pyplot as plt
import math
import torch as th
import torch.nn as nn
import torch.nn.functional as F
import SimpleITK as sitk


#两种滤波器的实现
def RLFilter(N, d):
    filterRL = np.zeros((N,))
    for i in range(N):
        filterRL[i] = - 1.0 / np.power((i - N / 2) * np.pi * d, 2.0)
        if np.mod(i - N / 2, 2) == 0:
            filterRL[i] = 0
    filterRL[int(N/2)] = 1 / (4 * np.power(d, 2.0))
    return filterRL

def SLFilter(N, d):
    filterSL = np.zeros((N,))
    for i in range(N):
        filterSL[i] = - 2 / (np.pi**2.0 * d**2.0 * (4 * (i - N / 2)**2.0 - 1))
    return filterSL

def IRandonTransform(image:'th.Tensor|np.array', steps:Optional[int]=None):
    '''
    Inverse Radon Transform(with Filter, FBP)

    Parameters:
        image: (B, C, W, H)
    '''
    # 定义用于存储重建后的图像的数组
    channels = image.shape[-1]
    B, C, W, H = image.shape
    if steps == None:
        steps = channels
    origin = th.zeros((B, C, steps, channels, channels), dtype=th.float32)
    filter_kernal = th.tensor(SLFilter(channels, 1)).unsqueeze(0).unsqueeze(0).float()
    Filter = nn.Conv1d(C, C, (channels), padding='same',bias=False)
    Filter.weight = nn.Parameter(filter_kernal) 

    for i in range(steps):
    	# 传入的图像中的每一列都对应于一个角度的投影值
    	# 这里用的图像是上篇博文里得到的Radon变换后的图像裁剪后得到的
        projectionValue = image[:, :, :, i]
        projectionValue = Filter(projectionValue)
        # 这里利用维度扩展和重复投影值数组来模拟反向均匀回抹过程
        projectionValueExpandDim = projectionValue.unsqueeze(2)
        projectionValueRepeat = projectionValueExpandDim.repeat((1, 1, channels, 1))
        origin[:,:, i] = rotate(projectionValueRepeat, (i / steps) * math.pi)
    #各个投影角度的投影值已经都保存在origin数组中,只需要将它们相加即可
    iradon = th.sum(origin, axis=2)
    return iradon


def rotate(image:th.Tensor, angle):
    '''
    Rotate the image in any angles(include negtive).
    angle should be pi = 180
    '''
    B= image.shape[0]
    transform_matrix = th.tensor([
            [math.cos(angle),math.sin(-angle),0],
            [math.sin(angle),math.cos(angle),0]], dtype=th.float32).unsqueeze(0).repeat(B,1,1)
    grid = F.affine_grid(transform_matrix, # 旋转变换矩阵
                            image.shape).float()	# 变换后的tensor的shape(与输入tensor相同)
    rotation = F.grid_sample(image, # 输入tensor,shape为[B,C,W,H]
                            grid, # 上一步输出的gird,shape为[B,C,W,H]
                            mode='nearest') # 一些图像填充方法,这里我用的是最近邻
    return rotation


def DiscreteRadonTransform(image:'th.Tensor|np.array', steps:Optional[int]=None):
    '''
    Radon Transform

    Parameters:
        image : (B, C, W, H)
    '''
    channels = image.shape[-1] # img_size
    B, C, W, H = image.shape
    res = th.zeros((B, channels, channels), dtype=th.float32)
    if steps == None:
        steps = channels
    for s in range(steps):
        angle = (s / steps) * math.pi
        rotation = rotate(image, -angle)
        res[:, :,s] = th.sum(rotation, dim=2)
    return res.unsqueeze(1)
    
if __name__ == '__main__':

    origin = sitk.ReadImage('/hy-tmp/data/LIDC/CT/0001.nii.gz')
    t_origin = sitk.GetArrayFromImage(origin)
    t_origin = th.tensor(t_origin)
    t_origin = t_origin[40].unsqueeze(0).unsqueeze(0)
    a = nn.Parameter(th.ones_like(t_origin))
    t_origin = t_origin * a
    ret = DiscreteRadonTransform(t_origin) # (B, 1, H, W)
    b = th.ones_like(ret)
    lf = nn.MSELoss()
    loss = lf(b, ret)
    loss.backward() # pytorch不会报错,并能返回grad
    rec = IRandonTransform(ret)
    ret = ret.squeeze(0)
    rec = rec.squeeze(0)
    plt.imsave('test.png', (t_origin.squeeze(0).squeeze(0)).data.numpy(), cmap='gray')
    plt.imsave('test2.png', (ret.squeeze(0)).data.numpy(), cmap='gray')
    plt.imsave('test3.png', (rec.squeeze(0)).data.numpy(), cmap='gray')

实现思路

这份代码实际上是参考前文提到的前辈的代码修改而来,具体而言就是把各种numpy实现的地方修改为pytorch的对应实现,其中pytorch没有对应的API来实现矩阵的Rotate,因此还参考了网上其它人实现的手写旋转的pytorch版本。并将其写作Rotate函数,在其它任务中也可以调用,这里需要注意,调用时,矩阵需要是方阵,否则会出现旋转后偏移中心的问题。

实验结果

原始图像:

Radon变换的结果:

重建结果:

这些图像可以下载下来,自己试试。

相关推荐
东胜物联3 分钟前
探寻5G工业网关市场,5G工业网关品牌解析
人工智能·嵌入式硬件·5g
皓74114 分钟前
服饰电商行业知识管理的创新实践与知识中台的重要性
大数据·人工智能·科技·数据分析·零售
985小水博一枚呀1 小时前
【深度学习滑坡制图|论文解读3】基于融合CNN-Transformer网络和深度迁移学习的遥感影像滑坡制图方法
人工智能·深度学习·神经网络·cnn·transformer
AltmanChan1 小时前
大语言模型安全威胁
人工智能·安全·语言模型
985小水博一枚呀1 小时前
【深度学习滑坡制图|论文解读2】基于融合CNN-Transformer网络和深度迁移学习的遥感影像滑坡制图方法
人工智能·深度学习·神经网络·cnn·transformer·迁移学习
数据与后端架构提升之路1 小时前
从神经元到神经网络:深度学习的进化之旅
人工智能·神经网络·学习
爱技术的小伙子1 小时前
【ChatGPT】如何通过逐步提示提高ChatGPT的细节描写
人工智能·chatgpt
深度学习实战训练营2 小时前
基于CNN-RNN的影像报告生成
人工智能·深度学习
昨日之日20064 小时前
Moonshine - 新型开源ASR(语音识别)模型,体积小,速度快,比OpenAI Whisper快五倍 本地一键整合包下载
人工智能·whisper·语音识别
浮生如梦_4 小时前
Halcon基于laws纹理特征的SVM分类
图像处理·人工智能·算法·支持向量机·计算机视觉·分类·视觉检测