簡體   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