简体   繁体   English

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

[英]Convolve array with kernel of variable standard deviation

Good day to you fellow programmer !祝你程序员朋友们美好的一天!
Today I would like to do something that I believe is tricky.今天我想做一些我认为很棘手的事情。 I have a very large 2D array called tac that basically contains time curve values and a file containing a tuple of coordinates called coor which contains information on where to place these curves in a 3D array.我有一个非常大的二维数组,称为tac ,它基本上包含时间曲线值和一个包含坐标元组的文件,称为coor ,其中包含有关在 3D 数组中放置这些曲线的位置的信息。 What this set of variables represents is actually a 4D array: the first 3 dimensions represent space dimensions and the fourth is time.这组变量所代表的实际上是一个4D数组:前3个维度代表空间维度,第四个维度是时间。 The whole thing is stored as is to avoid storing an immense amount of zeros.整个内容按原样存储,以避免存储大量零。

I would like to apply, for each time (in other words, each values in the 4th dimension), a gaussian kernel to this set of data.我想每次(换句话说,第 4 维中的每个值)都将高斯 kernel 应用于这组数据。 I was able to generate this kernel and to perform the convolution quite easily for a fixed standard deviation for the whole array using scipy.ndimage.convolve .我能够生成这个 kernel 并使用scipy.ndimage.convolve很容易地执行卷积以获得整个阵列的固定标准偏差。 The kernel was created using scipy.signal.gaussian . kernel 是使用scipy.signal.gaussian创建的。 Here is a brief example of the principle where tac_4d contains the 4D array (stores a lot of data I know... but one problem at the time):以下是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)

The trick is now to convolve the array with a kernel which standard deviation is different for each SPACE coordinate.现在的诀窍是将数组与 kernel 卷积,每个空间坐标的标准偏差都不同。 In other words, I would have a 3D array std containing standard deviations for each coordinate of the array.换句话说,我将有一个 3D 数组std ,其中包含数组每个坐标的标准偏差。

It seems https://github.com/sheliak/varconvolve is the code needed to take care of this problem.似乎https://github.com/sheliak/varconvolve是解决此问题所需的代码。 However I don't really understand how to use it and quite frankly, I would prefer to come up with a genuine solution.但是我不太了解如何使用它,坦率地说,我更愿意提出一个真正的解决方案。 Do you guys see a way to solve this problem?你们看到解决这个问题的方法了吗?

Thanks in advance !提前致谢 !

EDIT编辑

Here is what I hope can be considered MCVE这是我希望可以考虑的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

Essentially, you have a 4D dataset, shape (nx, ny, nz, nt) that is sparse in (nx, ny, nz) and dense in the nt axis.本质上,您有一个 4D 数据集,形状(nx, ny, nz, nt)(nx, ny, nz)上是稀疏的,在nt轴上是密集的。 If (i, j, k) are coordinates of nonzero points in the sparse dimensions, you want to convolve with a Gaussian 3D kernel that has a sigma that depends on (i, j, k) .如果(i, j, k)是稀疏维度中非零点的坐标,您希望与具有取决于(i, j, k)的 sigma 的高斯 3D kernel 进行卷积。

For example, if there are nonzero points at [1, 2, 5] and [1, 4, 5] with corresponding sigmas 0.1 and 1.0 , then the output at coordinates [1, 3, 5] is affected mostly by the [1, 4, 5] point because that one has the largest point spread.例如,如果在[1, 2, 5][1, 4, 5]处有对应的 sigmas 为0.11.0的非零点,则坐标[1, 3, 5]处的 output 主要受[1, 4, 5]点,因为该点具有最大的点差。

Your question is ambiguous;您的问题模棱两可; it could also mean that point [1, 3, 5] has a its own associated sigma, for example 0.5, and pulls data from the two adjacent points with equal weight.这也可能意味着点[1, 3, 5]具有自己的关联 sigma,例如 0.5,并从两个相邻点以相等的权重提取数据。 I will assume the first definition (sigma values associated with input points, not with output points).我将假设第一个定义(与输入点相关的 sigma 值,而不是与 output 点相关的)。

Because the operation is not a true convolution, there is no fast FFT-based method to do the entire operation in one operation.由于该操作不是真正的卷积,因此没有基于 FFT 的快速方法可以在一次操作中完成整个操作。 Instead, you have to loop over the sigma values.相反,您必须遍历 sigma 值。 Fortunately, your example has only 150 nonzero points, so the loop is not too expensive.幸运的是,您的示例只有 150 个非零点,因此循环不会太昂贵。

Here is an implementation.这是一个实现。 I keep the data in sparse representation as long as possible.我尽可能长时间地以稀疏表示形式保存数据。

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