繁体   English   中英

使用可变标准差的 kernel 对数组进行卷积

[英]Convolve array with kernel of variable standard deviation

祝你程序员朋友们美好的一天!
今天我想做一些我认为很棘手的事情。 我有一个非常大的二维数组,称为tac ,它基本上包含时间曲线值和一个包含坐标元组的文件,称为coor ,其中包含有关在 3D 数组中放置这些曲线的位置的信息。 这组变量所代表的实际上是一个4D数组:前3个维度代表空间维度,第四个维度是时间。 整个内容按原样存储,以避免存储大量零。

我想每次(换句话说,第 4 维中的每个值)都将高斯 kernel 应用于这组数据。 我能够生成这个 kernel 并使用scipy.ndimage.convolve很容易地执行卷积以获得整个阵列的固定标准偏差。 kernel 是使用scipy.signal.gaussian创建的。 以下是tac_4d包含 4D 数组的原理的简要示例(存储了我知道的大量数据……但当时有一个问题):

def gaussian_kernel_3d(radius, sigma):
    num = 2 * radius + 1
    kernel_1d = signal.gaussian(num, std=sigma).reshape(num, 1)
    kernel_2d = np.outer(kernel_1d, kernel_1d)
    kernel_3d = np.outer(kernel_1d, kernel_2d).reshape(num, num, num)
    kernel_3d = np.expand_dims(kernel_3d, -1)
    return kernel_3d

g = gaussian_kernel_3d(1, .5)
cag = nd.convolve(tac_4d, g, mode='constant', cval=0.0)

现在的诀窍是将数组与 kernel 卷积,每个空间坐标的标准偏差都不同。 换句话说,我将有一个 3D 数组std ,其中包含数组每个坐标的标准偏差。

似乎https://github.com/sheliak/varconvolve是解决此问题所需的代码。 但是我不太了解如何使用它,坦率地说,我更愿意提出一个真正的解决方案。 你们看到解决这个问题的方法了吗?

提前致谢 !

编辑

这是我希望可以考虑的MCVE

import numpy as np
from scipy import signal
from scipy import ndimage as nd


def gaussian_kernel_2d(radius, sigma):
    num = 2 * radius + 1
    kernel_1d = signal.gaussian(num, std=sigma).reshape(num, 1)
    kernel_2d = np.outer(kernel_1d, kernel_1d)
    return kernel_2d


def gaussian_kernel_3d(radius, sigma):
    num = 2 * radius + 1
    kernel_1d = signal.gaussian(num, std=sigma).reshape(num, 1)
    kernel_2d = np.outer(kernel_1d, kernel_1d)
    kernel_3d = np.outer(kernel_1d, kernel_2d).reshape(num, num, num)
    kernel_3d = np.expand_dims(kernel_3d, -1)
    return kernel_3d


np.random.seed(0)

number_of_tac = 150
time_samples = 915
z, y, x = 100, 150, 100
voxel_number = x * y * z

# TACs in the right order
tac = np.random.uniform(0, 4, time_samples * number_of_tac).reshape(number_of_tac, time_samples)

arr = np.array([0] * (voxel_number - number_of_tac) + [1] * number_of_tac)
np.random.shuffle(arr)
arr = arr.reshape(z, y, x)
coor = np.where(arr != 0)  # non-empty voxel

# Algorithm to replace TAC in 3D space
nnz = np.zeros(arr.shape)
nnz[coor] = 1
tac_4d = np.zeros((x, y, z, time_samples))
tac_4d[np.where(nnz == 1)] = tac

# 3D convolution for all time
# TODO: find a way to make standard deviation change for each voxel
g = gaussian_kernel_3d(1, 1)  # 3D kernel of std = 1
v = np.random.uniform(0, 1, x * y * z).reshape(z, y, x)  # 3D array of std
cag = nd.convolve(tac_4d, g, mode='constant', cval=0.0)  # convolution

本质上,您有一个 4D 数据集,形状(nx, ny, nz, nt)(nx, ny, nz)上是稀疏的,在nt轴上是密集的。 如果(i, j, k)是稀疏维度中非零点的坐标,您希望与具有取决于(i, j, k)的 sigma 的高斯 3D kernel 进行卷积。

例如,如果在[1, 2, 5][1, 4, 5]处有对应的 sigmas 为0.11.0的非零点,则坐标[1, 3, 5]处的 output 主要受[1, 4, 5]点,因为该点具有最大的点差。

您的问题模棱两可; 这也可能意味着点[1, 3, 5]具有自己的关联 sigma,例如 0.5,并从两个相邻点以相等的权重提取数据。 我将假设第一个定义(与输入点相关的 sigma 值,而不是与 output 点相关的)。

由于该操作不是真正的卷积,因此没有基于 FFT 的快速方法可以在一次操作中完成整个操作。 相反,您必须遍历 sigma 值。 幸运的是,您的示例只有 150 个非零点,因此循环不会太昂贵。

这是一个实现。 我尽可能长时间地以稀疏表示形式保存数据。

import scipy.signal
import numpy as np

def kernel3d(mm, sigma):
    """Return (mm, mm, mm) shaped, normalized kernel."""
    g1 = scipy.signal.gaussian(mm, std=sigma)
    g3 = g1.reshape(mm, 1, 1) * g1.reshape(1, mm, 1) * g1.reshape(1, 1, mm)    
    return g3 * (1/g3.sum())

np.random.seed(1)
s = 2 # scaling factor (original problem: s=10)
nx, ny, nz, nt, nnz = 10*s, 11*s, 12*s, 91*s, 15*s
# select nnz random voxels to fill with time series data
randint = np.random.randint
tseries = {} # key: (i, j, k) tuple; value: time series data, shape (nt,)
for _ in range(nnz):
    while True:
        ijk = (randint(nx), randint(ny), randint(nz))
        if ijk not in tseries:
            tseries[ijk] = np.random.uniform(0, 1, size=nt)
            break
        
ijks = np.array(list(tseries.keys())) # shape (nnz, 3)

# sigmas: key: (i, j, k) tuple; value: standard deviation
sigmas = { k: np.random.uniform(0, 2) for k in tseries.keys() }

# output will be stored as dense array, padded to avoid edge issues 
# with convolution.
m = 5 # padding size
cag_4dp = np.zeros((nx+2*m, ny+2*m, nz+2*m, nt))

mm = 2*m + 1 # kernel width
for (i, j, k), tdata in tseries.items():
    kernel = kernel3d(mm, sigmas[(i, j, k)]).reshape(mm, mm, mm, 1)
    # convolution of one voxel by kernel is trivial.
    # slice4d_c has shape (mm, mm, mm, nt).
    slice4d_c = kernel * tdata
    cag_4dp[i:i+mm, j:j+mm, k:k+mm, :] += slice4d_c

cag_4d = cag_4dp[m:-m, m:-m, m:-m, :]

#%%

import matplotlib.pyplot as plt
fig, axs = plt.subplots(2, 2, tight_layout=True)
plt.close('all')
# find a few planes
#ks = np.where(np.any(cag_4d != 0, axis=(0, 1,3)))[0]
ks = ijks[:4, 2]
for ax, k in zip(axs.ravel(), ks):
    ax.imshow(cag_4d[:, :, k, nt//2].T)
    ax.set_title(f'Voxel [:, :, {k}] at time {nt//2}')

fig.show()

for ijk, sigma in sigmas.items():
    print(f'{ijk}: sigma={sigma:.2f}')

来自卷积 4D 数据集的切片

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM