逆FFT不应返回负值

时间:2019-01-03 12:33:13

标签: python numpy filtering fft

我在3D盒子中有几个点(x,y,z坐标),并带有相关的质量。我想绘制在给定半径R的球体中发现的质量密度的直方图。

如果没有出现我认为可能有的任何错误,我编写的代码将以下列方式工作:

  • 我的“真实”数据是巨大的,因此我写了一些代码来随机产生任意重叠的非重叠点。

  • 我计算出3D直方图(按质量加权),其装仓比我的球体半径小10倍。

  • 我对直方图进行FFT,计算波模(kxkykz),然后使用它们将傅立叶空间中的直方图乘以傅立叶空间中3D高顶礼帽窗口(球面滤波)函数的解析表达式。

  • 我对新计算的网格进行了逆FFT。

因此在每个bin上绘制值的一维直方图将给我我想要的东西。

我的问题如下:考虑到我所做的事情,我的反向FFT网格中应该没有任何负值(步骤4),但是我得到了一些负值,并且其值比数值误差高得多。

如果我在一个小盒子(300x300x300 cm 3 )上运行我的代码,并且这些点之间的距离至少为1 cm,则不会出现此问题。我确实以600x600x600 cm3的尺寸得到它。

如果将所有质量都设置为0,从而在一个空网格上工作,我的确得到了0,而没有任何明显的问题。

我在这里以完整的块形式提供我的代码,以便于复制。

import numpy as np
import matplotlib.pyplot as plt
import random
from numba import njit

# 1. Generate a bunch of points with masses from 1 to 3 separated by a radius of 1 cm

radius = 1
rangeX = (0, 100)
rangeY = (0, 100)
rangeZ = (0, 100)
rangem = (1,3)
qty = 20000  # or however many points you want

# Generate a set of all points within 1 of the origin, to be used as offsets later
deltas = set()
for x in range(-radius, radius+1):
    for y in range(-radius, radius+1):
        for z in range(-radius, radius+1):
            if x*x + y*y + z*z<= radius*radius:
                deltas.add((x,y,z))

X = []
Y = []
Z = []
M = []
excluded = set()
for i in range(qty):
    x = random.randrange(*rangeX)
    y = random.randrange(*rangeY)
    z = random.randrange(*rangeZ)
    m = random.uniform(*rangem)
    if (x,y,z) in excluded: continue
    X.append(x)
    Y.append(y)
    Z.append(z)
    M.append(m)
    excluded.update((x+dx, y+dy, z+dz) for (dx,dy,dz) in deltas)

print("There is ",len(X)," points in the box")

# Compute the 3D histogram
a = np.vstack((X, Y, Z)).T
b = 200

H, edges = np.histogramdd(a, weights=M, bins = b)      

# Compute the FFT of the grid
Fh = np.fft.fftn(H, axes=(-3,-2, -1))

# Compute the different wave-modes
kx = 2*np.pi*np.fft.fftfreq(len(edges[0][:-1]))*len(edges[0][:-1])/(np.amax(X)-np.amin(X))
ky = 2*np.pi*np.fft.fftfreq(len(edges[1][:-1]))*len(edges[1][:-1])/(np.amax(Y)-np.amin(Y))
kz = 2*np.pi*np.fft.fftfreq(len(edges[2][:-1]))*len(edges[2][:-1])/(np.amax(Z)-np.amin(Z))

# I create a matrix containing the values of the filter in each point of the grid in Fourier space

R = 5                                                                                               
Kh = np.empty((len(kx),len(ky),len(kz)))

@njit(parallel=True)
def func_njit(kx, ky, kz, Kh):
    for i in range(len(kx)):
        for j in range(len(ky)):
            for k in range(len(kz)):
                if np.sqrt(kx[i]**2+ky[j]**2+kz[k]**2) != 0:
                    Kh[i][j][k] = (np.sin((np.sqrt(kx[i]**2+ky[j]**2+kz[k]**2))*R)-(np.sqrt(kx[i]**2+ky[j]**2+kz[k]**2))*R*np.cos((np.sqrt(kx[i]**2+ky[j]**2+kz[k]**2))*R))*3/((np.sqrt(kx[i]**2+ky[j]**2+kz[k]**2))*R)**3
                else:
                    Kh[i][j][k] = 1
    return Kh

Kh = func_njit(kx, ky, kz, Kh)

# I multiply each point of my grid by the associated value of the filter (multiplication in Fourier space = convolution in real space)

Gh = np.multiply(Fh, Kh)

# I take the inverse FFT of my filtered grid. I take the real part to get back floats but there should only be zeros for the imaginary part.

Density = np.real(np.fft.ifftn(Gh,axes=(-3,-2, -1)))

# Here it shows if there are negative values the magnitude of the error

print(np.min(Density))

D = Density.flatten()
N = np.mean(D)

# I then compute the histogram I want

hist, bins = np.histogram(D/N, bins='auto', density=True)
bin_centers = (bins[1:]+bins[:-1])*0.5
plt.plot(bin_centers, hist)
plt.xlabel('rho/rhom')
plt.ylabel('P(rho)')

plt.show()

您知道为什么我会得到这些负值吗?您认为有一种更简单的方法吗?

很抱歉,如果这是一篇很长的文章,我试图使它很清楚,并将用您的评论进行编辑,非常感谢!

-编辑-

可以在[此处]找到有关此问题的后续问题。1

2 个答案:

答案 0 :(得分:2)

您在频域中创建的滤波器只是您要创建的滤波器的近似值。问题在于我们在这里处理DFT,而不是连续域FT(具有无限频率)。球的傅立叶变换确实是您所描述的函数,但是此函数无限大-它不受频带限制!

通过仅在窗口中对该函数进行采样,可以有效地将其与理想的低通滤波器(域的矩形)相乘。在空间域中,此低通滤波器具有负值。因此,您创建的过滤器在空间域中也具有负值。

这是Kh逆变换的原点的一个切片(在我应用fftshift之后将原点移动到图像的中间,以便更好地显示):

display of a slice through the origin of the spatial-domain filter

正如您在这里可以看到的那样,有些振铃会导致负值。

克服振铃的一种方法是在频域中应用开窗功能。另一种选择是在空间域中生成一个球,然后计算其傅里叶变换。第二种选择将是最简单的实现。请记住,在空间域中的内核还必须在左上像素处具有原点,以获取正确的FFT。

通常在空间域中应用开窗功能,以避免在计算FFT时出现图像边界问题。在此,我建议在频域中应用这种窗口,以避免在计算IFFT时出现类似问题。但是请注意,这将始终进一步减少内核的带宽(毕竟,窗口函数将充当低通滤波器),因此在空间域(即空间域)中产生从前景到背景的平滑过渡内核将没有您想要的那样尖锐的过渡)。最为人熟知的开窗功能是Hamming and Hann windows,但还有many others值得尝试。


主动提供的建议:

我简化了将Kh计算为以下代码的代码:

kr = np.sqrt(kx[:,None,None]**2 + ky[None,:,None]**2 + kz[None,None,:]**2)
kr *= R
Kh = (np.sin(kr)-kr*np.cos(kr))*3/(kr)**3
Kh[0,0,0] = 1

我发现这比嵌套循环更易于阅读。它还应该明显更快,并避免使用njit。请注意,您正在计算相同的距离(这里我称为kr)5次。排除这种计算不仅速度更快,而且产生了更具可读性的代码。

答案 1 :(得分:0)

一个猜测:

您从何处得知虚部必须为零?您是否曾经尝试过采用绝对值(sqrt(re ^ 2 + im ^ 2))并忘记了相位,而不是仅仅考虑真实部分?只是我想到的东西。