插值/调整3D数组的大小

时间:2017-12-12 15:00:29

标签: python numpy scipy

我有一个3D阵列,用于保存mri数据集中的体素。该模型可以沿着一个或多个方向获得。例如。体素尺寸(x,y,z)可以是0.5,0.5,2mm。现在我想将3D阵列重新采样为一个容纳1,1,1 mm体素的阵列。为此,我需要使x / y尺寸更小,z尺寸更大并插入体素值。 我的问题是:在numpy或scipy中有一个简单的函数可以对一个简单的3d数组进行重新采样吗?

从* .nii文件加载模型:

private const string WebServiceUrl = "http://localhost:50397/api/books/";

    public async Task<List<T>> GetAsync()
    {
        try
        {
            var httpClient = new HttpClient();
            var json = await httpClient.GetStringAsync(WebServiceUrl);

            var taskModels = JsonConvert.DeserializeObject<List<T>>(json);
            return taskModels;
        }
        catch (HttpRequestException e)
        {
            String blad = e.InnerException.Message;
            blad += "asdasd";
        }
        return null;
    }

5 个答案:

答案 0 :(得分:14)

ndimage.zoom

这可能是最好的方法,zoom method正是为这类任务而设计的。

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

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

signal.resample_poly

SciPy有一个large set of methods用于信号处理。这里最相关的是decimateresample_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
  • (2,1)表示大小乘以2
  • (2,3)意味着增加2然后减少3,所以尺寸乘以2/3

可能的缺点:该过程在每个维度中独立发生,因此可能不考虑空间结构以及ndimage方法。

RegularGridInterpolator

这是更多实践,但也更费力,没有过滤的好处:直接下采样。我们必须使用每个方向的原始步长为内插器制作网格。创建插补器后,需要在新网格上进行评估;其调用方法采用不同类型的网格格式,使用mgrid准备。

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(array.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中进行下采样。

答案 1 :(得分:0)

您还可以使用PyTorch + TorchIO。以下是使用高斯插值对3D数组进行的重采样(缩放功能改编自我的主管为我编写的代码):

def zoom(
    label_stack: Sequence[Sequence[SupportsFloat]],
    spacing: Sequence[Union[SupportsInt, SupportsFloat]],
) -> np.ndarray:
    spacing = np.asanyarray(spacing)
    target = tuple(1 / spacing)

    # Resample the array with respect to spacing parameters
    arr4d = np.expand_dims(label_stack, 0)
    transform = tio.Resample(target, image_interpolation="gaussian")
    transformed = transform(arr4d)
    result = np.squeeze(transformed)

    # Perform an arbitrary gaussian to smoothen the surface.
    # Achieves a more smooth surface
    sigma = (round(spacing[0] * 0.25), 7, 7)
    gaussian_3d = gaussian_filter(result, sigma=sigma)

    # Binary thresholding
    highpass_threshold = np.median(gaussian_3d[gaussian_3d > 0.09])
    thresh = threshold_array(gaussian_3d, highpass=highpass_threshold)

    return thresh


def threshold_array(
    array: np.ndarray,
    lowpass: Union[Union[SupportsInt, SupportsFloat], None] = None,
    highpass: Union[Union[SupportsInt, SupportsFloat], None] = None,
) -> np.ndarray:
    if lowpass and highpass:
        msg = "Defition of both lowpass and highpass is illogical"
        raise ValueError(msg)

    if not lowpass and not highpass:
        msg = "Either lowpass or highpass has to be defined"
        raise ValueError(msg)

    array = np.asanyarray(array)
    filtered = np.zeros_like(array, dtype=np.int8)

    if lowpass:
        filtered[array < lowpass] = 1
    if highpass:
        filtered[array > highpass] = 1

    return filtered

图像数组为z-x-y,并已相应设计缩放。

间距由用户定义。间距必须是与图像numpy数组的形状具有相同长度的序列。插值后的图像将具有间隔*“旧形状”的形状。

我希望在Resample function中使用三次方插值法。我尝试使用高斯+阈值化,但没有得到任何出色的结果。比我可以与SciPy召集的要好。

使用时请记入信用额。

答案 2 :(得分:0)

您可以使用TorchIO

import torchio as tio
image = tio.ScalarImage(sFileName)
resample = tio.Resample(1)
resampled = resample(image)

答案 3 :(得分: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)

答案 4 :(得分:-1)

我在 grid_sample 中使用 torch.nn.functional。它相对较快,适用于多次调整 3d 数组的大小(例如进行数据扩充时)。

在我的本地计算机上进行的一项实验表明,grid_sample 的处理速度为 2241.25 个样本/秒,zoom 的处理速度为 233.91 个样本/秒。

比较进程速度的示例代码:

import numpy as np
import torch
import torch.nn.functional as F
from scipy.ndimage import zoom
import time

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

def resize_by_zoom(x):
    out = zoom(x,(1/4, 1, 1))
    return out


NUM = 5000

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

# grid_sample
for i in range(NUM):
    if i==int(0.1*NUM):
        start_time, start_i = time.time(), i
    resized_data = resize_by_grid_sample(data)

samples_per_sec = (NUM-start_i)/(time.time()-start_time)
print("grid_sample: {:.2f} samples/sec".format(samples_per_sec)) 
# 2241.25 samples/sec

# zoom
for i in range(NUM):
    if i==int(0.1*NUM):
        start_time, start_i = time.time(), i
    resized_data = resize_by_zoom(data)

samples_per_sec = (NUM-start_i)/(time.time()-start_time)
print("zoom: {:.2f} samples/sec".format(samples_per_sec))  
# 233.91 samples/sec

(为了使评估稳定,我放弃了拳头 10% 的迭代。)

我引用了代码 here

align_corners=True 的使用将比 align_corners=Falseintuitive。 它们之间的区别实际上是我们是否在调整大小之前和之后对齐角像素。 nice figure here