插值/调整3D数组大小

19
我有一个包含来自MRI数据集的体素的3D数组。该模型可以沿一个或多个方向拉伸。例如,体素大小(x,y,z)可以为0.5x0.5x2毫米。现在我想将3D数组重新采样为持有1,1,1毫米体素的数组。为此,我需要使x / y维度更小,z维度更大,并插值体素值。 我的问题是:在numpy或scipy中是否有一个简单的函数可以对简单的3D数组进行这样的重采样?
从*.nii文件加载模型:
img = nib.load(sFileName)
array = np.array(img.dataobj).astype("uint8") # 3d array with e.g. 0.5,0.5,2 mm
# TODO resample

scipy.interpolate.RegularGridInterpolator 可能适用于您的情况。 - Paul Brodersen
我会试一下。 - dgrat
“clinched”是什么意思? - Monica Heddneck
使用PyTorch,您可以轻松地使用torch.nn中的Upsample函数来对1、2和3维图像进行放大,并使用各种方法(包括三线性插值)进行上采样。 - Shayan Daneshvar
5个回答

37

ndimage.zoom

这可能是最好的方法,缩放方法专门为这种任务设计。

from scipy.ndimage import zoom
new_array = zoom(array, (0.5, 0.5, 2))

按指定比例在每个维度上更改大小。如果数组的原始形状是,例如,(40, 50, 60),则新形状将为(20, 25, 120)

signal.resample_poly

SciPy具有信号处理的大量方法。 最相关的是抽取(decimate)多项式重采样(resample_poly)。 我在下面使用后者。

from scipy.signal import resample_poly
factors = [(1, 2), (1, 2), (2, 1)]
for k in range(3):
    array = resample_poly(array, factors[k][0], factors[k][1], axis=k)

这些因子(必须是整数)用于上采样和下采样。具体如下:

  • (1, 2) 表示尺寸缩小一半
  • (2, 1) 表示尺寸扩大一倍
  • (2, 3) 表示先放大两倍再缩小三倍,所以尺寸变为原来的2/3

可能的缺点是:此过程在每个维度上独立进行,因此空间结构可能不如ndimage方法考虑得那么充分。

RegularGridInterpolator

这种方法更实用,但也更费力,并且没有滤波的好处:直接进行下采样。我们需要为插值器创建一个网格,使用每个方向上的原始步长。创建插值器后,需要在新的网格上进行评估;其调用方法使用了一种不同的网格格式,需使用 mgrid 准备。

from scipy.interpolate import RegularGridInterpolator

values = np.random.randint(0, 256, size=(40, 50, 60)).astype(np.uint8)  # example

steps = [0.5, 0.5, 2.0]    # original step sizes
x, y, z = [steps[k] * np.arange(values.shape[k]) for k in range(3)]  # original grid
f = RegularGridInterpolator((x, y, z), values)    # interpolator

dx, dy, dz = 1.0, 1.0, 1.0    # new step sizes
new_grid = np.mgrid[0:x[-1]:dx, 0:y[-1]:dy, 0:z[-1]:dz]   # new grid
new_grid = np.moveaxis(new_grid, (0, 1, 2, 3), (3, 0, 1, 2))  # reorder axes for evaluation
new_values = f(new_grid)

缺点:例如,当一个维度减少2时,实际上会丢弃每个其他值,这是简单的下采样。在这种情况下,理想情况下应该平均相邻的值。在信号处理方面,在decimation之前进行低通滤波。


3
你可以使用TorchIO来实现这个目的:
import torchio as tio
image = tio.ScalarImage(sFileName)
resample = tio.Resample(1)
resampled = resample(image)

你对我的答案有何看法?我问这个是因为我正在寻找一个三次插值方法来完成这个任务。 - Can H. Tartanoglu
你也可以使用TorchIO!我相信ITK中的B样条阶数为3,所以你可以这样做:resample = tio.Resample(1, image_interpolation='bspline') - fepegar

1

最佳现代解决方案必须包括使用Python的ITK库。

假设

我假设您的数据已针对每个对象进行二值化处理!如果您正在处理颜色甚至灰度图像,则应该使用深度神经网络,但这超出了范围。

方法论

我会使用ITK的morphological interpolation extension。您需要手动添加每个图像平面之间的间距。因此,假设您的XYZ比例为1:1:7,请在每个图像平面之间添加7个空白平面,并将新矩阵用作函数的主要参数。

确保遵守库支持的dtype。我使用np.uint8作为我的数组dtype

实施

from typing import Union

import itk
import numpy as np
from yaspin import yaspin
from yaspin.spinners import Spinners

INTERPOLATION_TEXT = "Interpolating for the first time"


@yaspin(Spinners.bouncingBall, text=INTERPOLATION_TEXT)
def morphological_interpolation(
    labeled_stack: np.ndarray, stack_depth_spacing: Union[float, int]
):
    spacing_array = np.zeros(
        (round(stack_depth_spacing), *labeled_stack.shape[1:]),
        dtype=labeled_stack.dtype,
    )

    spaced_stack = []
    for xy_plane in labeled_stack:
        spaced_stack.append(np.expand_dims(xy_plane, axis=0))
        spaced_stack.append(spacing_array)
    spaced_stack.pop(-1)
    spaced_stack = np.concatenate(spaced_stack)
    spaced_stack.astype(labeled_stack.dtype, copy=False)

    return itk.GetArrayFromImage(
        itk.morphological_contour_interpolator(itk.GetImageFromArray(spaced_stack))
    )

@dgrat,你的xyz比例为0.5x0.5x2毫米 -> 1:1:4。 - Can H. Tartanoglu

0
如果您想直接在MR图像上执行重采样操作,可以使用antspy,它将对数据进行重采样,并更改体素间距信息。
    spacing = (1,1,1)
    import ants
    img = ants.image_read(file)
    filin = ants.resample_image(img,spacing,False,1)
    ants.image_write(filin,output)

-1

我在torch.nn.functional中使用grid_sample。当您想要在非均匀分布点上重新采样3D数组时,它非常方便。

以下是示例代码。您可以通过更改代码中的grid来指定重新采样点。

import numpy as np
import torch
import torch.nn.functional as F

def resize_by_grid_sample(x):

    dx = torch.linspace(-1, 1, 8)
    dy = torch.linspace(-1, 1, 32)
    dz = torch.linspace(-1, 1, 32)
    meshx, meshy, meshz = torch.meshgrid((dx, dy, dz))
    grid = torch.stack((meshx, meshy, meshz), 3)
    grid = grid.unsqueeze(0)

    x = x[np.newaxis, np.newaxis, :, :, :]
    x = torch.tensor(x, requires_grad=False)

    out = F.grid_sample(x, grid, align_corners=True)
    out = out.data.numpy()
    out = np.squeeze(out)

    return out


data = np.random.normal(size=(32,32,32)).astype(np.float32)

# grid_sample
resized_data = resize_by_grid_sample(data)

我参考了这里的代码herealign_corners=Truealign_corners=False之间的区别在于调整大小之前和之后是否对齐角像素,这是一个很好的示意图 nice figure here

当我寻找一种方法时,我也偶然发现了这些函数;然而,OP想要插值。zoom和grid_sample都没有这个功能。你可以说zoom确实有插值;此外,可用的方法在体素插值方面大多是无用的。 - Can H. Tartanoglu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接