医療画像レジストレーションにおける変形フィールドの可視化(描画変形フィールド)

この記事では、ツールを介して直接表示する1つの方法と、手動で描画する2つの方法を含め、医療画像レジストレーションにおける変形フィールドの視覚化について説明します。

まず、変形場を紹介します。[W、H]のサイズの2次元画像の変形場は[W、H、2]であり、3次元のサイズは2であり、 x軸とy軸方向の変位。同様に、[D、W、H]のサイズの3次元画像に対応する変形フィールドのサイズは[D、W、H、3]であり、3次元のサイズは3です。これらはそれぞれx軸とy軸で表されます。軸とz軸の方向の変位。下の図は、2次元の脳画像の登録後に得られた変形フィールドです。

ここに画像の説明を挿入

冒頭で述べたように、以下では変形フィールドを視覚化する3つの方法を紹介します。

1.ITK-Snapを使用して変形フィールドを視覚化します

この方法は最も便利で、私の意見では最高の効果ですが、3次元の変形フィールドにのみ適しています。まず、変形フィールドを「.nii」画像として保存し、ITK-Snapツールで開きます。まず、下図の赤い枠の位置にしばらくカーソルを合わせ、下図のアイコンが表示されたらクリックします。 「マルチコンポーネントディスプレイの下」グリッドを選択すると、次の図に示す効果が表示されます。
ここに画像の説明を挿入

ここに画像の説明を挿入

2.マオマオの方法

具体的なアイデアについては、Maomao Great Godの記事plt.contourDrawing Image Deformation Field(Deformation Field)を参照してください実際、具体的な考え方がよくわかりませんでした。まずは規則的なグリッドを生成し、変形フィールドを追加して表示します。ただし、グリッドが[W、H]サイズで、変形フィールドが[W、H、2]サイズの場合、追加する次元を1つしか選択できないため、少し奇妙に感じます。コードは次のように表示されます。

import matplotlib.pyplot as plt
import SimpleITK as sitk
import numpy as np


def grid2contour(grid, title):
    '''
    grid--image_grid used to show deform field
    type: numpy ndarray, shape: (h, w, 2), value range:(-1, 1)
    '''
    assert grid.ndim == 3
    x = np.arange(-1, 1, 2.0 / grid.shape[1])
    y = np.arange(-1, 1, 2.0 / grid.shape[0])
    X, Y = np.meshgrid(x, y)
    Z1 = grid[:, :, 0] + 2  # remove the dashed line
    Z1 = Z1[::-1]  # vertical flip
    Z2 = grid[:, :, 1] + 2

    plt.figure()
    plt.contour(X, Y, Z1, 15, levels=50, colors='k') #改变levels的值,可以改变形变场的密集程度
    plt.contour(X, Y, Z2, 15, levels=50, colors='k')
    plt.xticks(()), plt.yticks(())  # remove x, y ticks
    plt.title(title)
    plt.show()


def show_grid():
    img = sitk.ReadImage("./2D1.nii")
    img_arr = sitk.GetArrayFromImage(img)
    img_shape = img_arr.shape

    # 起点、终点、步长(可为小数)
    x = np.arange(-1, 1, 2 / img_shape[1])
    y = np.arange(-1, 1, 2 / img_shape[0])
    X, Y = np.meshgrid(x, y)
    regular_grid = np.stack((X, Y), axis=2)
    grid2contour(regular_grid, "regular_grid")

    rand_field = np.random.rand(*img_shape[:2], 2)  # 参数前加*是以元组形式导入
    rand_field_norm = rand_field.copy()
    rand_field_norm[:, :, 0] = rand_field_norm[:, :, 0] * 2 / img_shape[1]
    rand_field_norm[:, :, 1] = rand_field_norm[:, :, 1] * 2 / img_shape[0]
    sampling_grid = regular_grid + rand_field_norm
    grid2contour(sampling_grid, "sampling_grid")

    img_arr[..., 0] = img_arr[..., 0] * 2 / img_shape[1]
    img_arr[..., 1] = img_arr[..., 1] * 2 / img_shape[0]
    img_grid = regular_grid + img_arr
    grid2contour(img_grid, "img_grid")


if __name__ == "__main__":
    show_grid()
    print("end")

コードの20行目で、レベルの値を変更すると、変形フィールドのグリッドの密度が変更される可能性があります。

3.最初に通常のグリッドを生成し、次に空間変換ネットワークを使用して変形します

この方法は、登録グループの友人によって提案され、自分で実装しました。アイデアは、最初に通常のグリッドを生成してそれを画像として保存し、次に空間変換ネットワークを使用してそれを変形することです。コードは次のように表示されます。

import numpy as np
import SimpleITK as sitk
import matplotlib.pyplot as plt

import torch
import torch.nn as nn
import torch.nn.functional as F


# 空间转换网络
class SpatialTransformer(nn.Module):
    # 1.生成网格grid;2.new_grid=grid+flow,即旧网格加上一个位移;3.将网格规范化到[-1,1];4.根据新网格对原图进行采样
    def __init__(self, size, mode='bilinear'):
        """
        Instiatiate the block
            :param size: size of input to the spatial transformer block
            :param mode: method of interpolation for grid_sampler
        """
        super(SpatialTransformer, self).__init__()

        # Create sampling grid
        vectors = [torch.arange(0, s) for s in size]
        grids = torch.meshgrid(vectors)
        grid = torch.stack(grids)  # y, x, z
        grid = torch.unsqueeze(grid, 0)  # add batch
        grid = grid.type(torch.FloatTensor)
        self.register_buffer('grid', grid)

        self.mode = mode

    def forward(self, src, flow):
        """
        Push the src and flow through the spatial transform block
            :param src: the original moving image
            :param flow: the output from the U-Net
        """
        new_locs = self.grid + flow

        shape = flow.shape[2:]

        # Need to normalize grid values to [-1, 1] for resampler
        for i in range(len(shape)):
            new_locs[:, i, ...] = 2 * (new_locs[:, i, ...] / (shape[i] - 1) - 0.5)

        if len(shape) == 2:
            new_locs = new_locs.permute(0, 2, 3, 1)  # 维度置换,变为0,2,3,1
            new_locs = new_locs[..., [1, 0]]
        elif len(shape) == 3:
            new_locs = new_locs.permute(0, 2, 3, 4, 1)
            new_locs = new_locs[..., [2, 1, 0]]

        return F.grid_sample(src, new_locs, mode=self.mode)


# 生成网格图片
def create_grid(size, path):
    num1, num2 = (size[0] + 10) // 10, (size[1] + 10) // 10  # 改变除数(10),即可改变网格的密度
    x, y = np.meshgrid(np.linspace(-2, 2, num1), np.linspace(-2, 2, num2))

    plt.figure(figsize=((size[0] + 10) / 100.0, (size[1] + 10) / 100.0))  # 指定图像大小
    plt.plot(x, y, color="black")
    plt.plot(x.transpose(), y.transpose(), color="black")
    plt.axis('off')  # 不显示坐标轴
    # 去除白色边框
    plt.gca().xaxis.set_major_locator(plt.NullLocator())
    plt.gca().yaxis.set_major_locator(plt.NullLocator())
    plt.subplots_adjust(top=1, bottom=0, left=0, right=1, hspace=0, wspace=0)
    plt.margins(0, 0)
    plt.savefig(path)  # 保存图像
    # plt.show()


if __name__ == "__main__":
    out_path = r"C:\Users\zuzhiang\Desktop\new_img.jpg"  # 图片保存路径
    # 读入形变场
    phi = sitk.ReadImage("./2D1.nii")  # [324,303,2]
    phi_arr = torch.from_numpy(sitk.GetArrayFromImage(phi)).float()
    phi_shape = phi_arr.shape
    # 产生网格图片
    create_grid(phi_shape, out_path)
    img = sitk.GetArrayFromImage(sitk.ReadImage(out_path))[..., 0]
    img = np.squeeze(img)[np.newaxis, np.newaxis, :phi_shape[0], :phi_shape[1]]
    # 用STN根据形变场对网格图片进行变形
    STN = SpatialTransformer(phi_shape[:2])
    phi_arr = phi_arr.permute(2, 0, 1)[np.newaxis, ...]
    warp = STN(torch.from_numpy(img).float(), phi_arr)
    # 保存图片
    warp_img = sitk.GetImageFromArray(warp[0, 0, ...].numpy().astype(np.uint8))
    sitk.WriteImage(warp_img, out_path)
    print("end")

グリッド画像のサイズは正確に決定できないため、生成時にさらに10ピクセルが生成され、読み取られるときにトリミングされることに注意してください。上記のコードの57行目の除数(10)を変更すると、グリッド線の密度を変更できます。

この方法はもう少しエレガントな感じがしますが、効果は2番目の方法と似ています。2番目と3番目の方法の効果を次の図に示します。

おすすめ

転載: blog.csdn.net/zuzhiang/article/details/107423465