Fourier yöntemiyle tomografik rekonstrüksiyon için bu kodda yanlış olan ne?


19

Son zamanlarda tomografik rekonstrüksiyon algoritmalarıyla oynuyorum. FBP, ART, SIRT / SART benzeri yinelemeli bir şema ve hatta düz doğrusal cebir (yavaş!) Kullanıyorum. Bu soru bu tekniklerin hiçbiriyle ilgili değildir ; "neden herkes bunu böyle yapsın, yerine bazı FBP kodu" formunun cevapları ben aradığım şey değil.

Bu programla bir sonraki yapmak istediğim şey " seti tamamlamak " ve " Fourier rekonstrüksiyon metodu " nu uygulamaktı . Bunu anladığım temelde sinogram "pozlamalarına" 1D FFT uygulamanız, bunları 2D Fourier uzayında radyal "tekerlek teli" olarak düzenlemenizdir (bunun doğrudan merkezi dilim teoreminden sonra yapılması yararlı bir şeydir) , bu noktalardan 2B alanda normal bir ızgaraya enterpolasyon uygulayın ve ardından orijinal tarama hedefini kurtarmak için Fourier dönüşümünü tersine çevirmek mümkün olmalıdır.

Kulağa basit geliyor, ama orijinal hedefe benzeyen herhangi bir rekonstrüksiyon elde etme şansım olmadı.

Aşağıdaki Python (numpy / SciPy / Matplotlib) kodu, yapmaya çalıştığım şeyle karşılaşabileceğim en özlü ifade hakkında. Çalıştırıldığında aşağıdakileri görüntüler:

Şekil 1: Hedef şekil1

Şekil 2: Hedefin bir sinogramı incir. 2

Şekil 3: FFT-ed sinogram satırları Şekil 3

Şekil 4: üst sıra, Fourier-domain sinogram satırlarından enterpolasyonlu 2D FFT alanıdır; alt sıra (karşılaştırma amacıyla) hedefin doğrudan 2D FFT'sidir. Şüphelendiğim nokta bu; sinogram FFT'lerinden enterpole edilen araziler, doğrudan 2D-FFT hedefi ile yapılan çizimlere benziyor ... ama yine de farklı. fig4

Şekil 5: Şekil 4'teki ters-Fourier dönüşümü, bunun hedef olarak gerçekte olduğundan biraz daha tanınabilir olacağını umuyordum. fig5

Yanlış yaptığım hakkında bir fikrin var mı? Fourier yönteminin yeniden yapılandırılması konusundaki anlayışımın temelde kusurlu olup olmadığından emin değil miyim, yoksa kodumda sadece bir hata var.

import math
import matplotlib
import matplotlib.pyplot as plt
import numpy as np

import scipy.interpolate
import scipy.fftpack
import scipy.ndimage.interpolation

S=256  # Size of target, and resolution of Fourier space
A=359  # Number of sinogram exposures

# Construct a simple test target
target=np.zeros((S,S))
target[S/3:2*S/3,S/3:2*S/3]=0.5
target[120:136,100:116]=1.0

plt.figure()
plt.title("Target")
plt.imshow(target)

# Project the sinogram
sinogram=np.array([
        np.sum(
            scipy.ndimage.interpolation.rotate(
                target,a,order=1,reshape=False,mode='constant',cval=0.0
                )
            ,axis=1
            ) for a in xrange(A)
        ])

plt.figure()
plt.title("Sinogram")
plt.imshow(sinogram)

# Fourier transform the rows of the sinogram
sinogram_fft_rows=scipy.fftpack.fftshift(
    scipy.fftpack.fft(sinogram),
    axes=1
    )

plt.figure()
plt.subplot(121)
plt.title("Sinogram rows FFT (real)")
plt.imshow(np.real(np.real(sinogram_fft_rows)),vmin=-50,vmax=50)
plt.subplot(122)
plt.title("Sinogram rows FFT (imag)")
plt.imshow(np.real(np.imag(sinogram_fft_rows)),vmin=-50,vmax=50)

# Coordinates of sinogram FFT-ed rows' samples in 2D FFT space
a=(2.0*math.pi/A)*np.arange(A)
r=np.arange(S)-S/2
r,a=np.meshgrid(r,a)
r=r.flatten()
a=a.flatten()
srcx=(S/2)+r*np.cos(a)
srcy=(S/2)+r*np.sin(a)

# Coordinates of regular grid in 2D FFT space
dstx,dsty=np.meshgrid(np.arange(S),np.arange(S))
dstx=dstx.flatten()
dsty=dsty.flatten()

# Let the central slice theorem work its magic!
# Interpolate the 2D Fourier space grid from the transformed sinogram rows
fft2_real=scipy.interpolate.griddata(
    (srcy,srcx),
    np.real(sinogram_fft_rows).flatten(),
    (dsty,dstx),
    method='cubic',
    fill_value=0.0
    ).reshape((S,S))
fft2_imag=scipy.interpolate.griddata(
    (srcy,srcx),
    np.imag(sinogram_fft_rows).flatten(),
    (dsty,dstx),
    method='cubic',
    fill_value=0.0
    ).reshape((S,S))

plt.figure()
plt.suptitle("FFT2 space")
plt.subplot(221)
plt.title("Recon (real)")
plt.imshow(fft2_real,vmin=-10,vmax=10)
plt.subplot(222)
plt.title("Recon (imag)")
plt.imshow(fft2_imag,vmin=-10,vmax=10)

# Show 2D FFT of target, just for comparison
expected_fft2=scipy.fftpack.fftshift(scipy.fftpack.fft2(target))

plt.subplot(223)
plt.title("Expected (real)")
plt.imshow(np.real(expected_fft2),vmin=-10,vmax=10)
plt.subplot(224)
plt.title("Expected (imag)")
plt.imshow(np.imag(expected_fft2),vmin=-10,vmax=10)

# Transform from 2D Fourier space back to a reconstruction of the target
fft2=scipy.fftpack.ifftshift(fft2_real+1.0j*fft2_imag)
recon=np.real(scipy.fftpack.ifft2(fft2))

plt.figure()
plt.title("Reconstruction")
plt.imshow(recon,vmin=0.0,vmax=1.0)

plt.show()


... çünkü burada kod var Merkezde olması gereken şeyler kenarlarda ve kenarlarda olması gereken şeyler ortada, 90 derecelik bir faz kayması olması gerektiği gibi değil mi?
endolith

1
Bağladığınız kod, filtrelenmiş arkadan projeksiyon (FBP) yöntemi içindir. Hangi aynı merkezi dilim matematik dayalı, ama asla açıkça 2D Fourier etki alanı görüntü oluşturmak için çalışır. FBP filtresinin düşük frekansları bastırmasına, ortadaki "dilim" orta dilimin daha yüksek yoğunluğunun telafisi olarak bakabilirsiniz. Uygulamaya çalıştığım Fourier rekonstrüksiyon yönteminde bu sadece enterpolasyon yapılacak noktaların daha yüksek bir yoğunluğu olarak kendini gösteriyor. Serbestçe ben biraz kullanılmış tekniği uygulamak çalışıyorum itiraf ve onunla sınırlı kapsama literatürde var
timday

Hata! Evet, haklısın. İşte C sürümü . Biraz içine baktım ve bazı şeyler gönderdim. Daha sonra bakacağım.
endolith

Yanıtlar:


15

Tamam sonunda bunu kırdım.

Hüner temelde bazı fftshift/ ifftshifts'leri doğru yere koymaya geldi , böylece 2D Fourier uzay gösterimi çılgınca salınımlı değildi ve doğru bir şekilde enterpolasyon yapmak imkansızdı. En azından ben bunu düzelttim. Fourier teorisindeki sınırlı anlayışımın çoğu sürekli integral formülasyona dayanıyor ve her zaman ayrık alanı ve FFT'leri biraz ... ilginç buluyorum.

Matlab kodunu oldukça şifreli bulurken, en azından bana bu rekonstrüksiyon algoritmasının bu tür bir ortamda makul derecede kompakt bir şekilde ifade edilebileceğine dair güven vermek için bu uygulamayı kredilendirmeliyim .

Önce sonuçları göstereceğim, sonra kod yazacağım:

Şekil 1: yeni, daha karmaşık bir hedef. Şek1

Şekil 2: Hedefin sinogramı (OK OK, Radon dönüşümü). İncir. 2

Şekil 3: Sinogramın FFT-ed satırları (merkezde DC ile işaretlenmiştir). Şekil 3

Şekil 4: 2D FFT boşluğuna (merkezdeki DC) dönüştürülen FFT-ed sinogram. Renk mutlak değerin bir fonksiyonudur. Fig4

Şekil 4a: Sinogram verilerinin radyal doğasını daha iyi göstermek için 2D FFT alanının ortasını yakınlaştırın. Fig4a

Şekil 5: Üst sıra: Radyal olarak düzenlenmiş FFT-ed sinogram sıralarından enterpolasyonlu 2D FFT alanı. Alt satır: hedefi 2B FFT'den beklenen görünüm.
Fig5

Şekil 5a: Bunların niteliksel olarak oldukça iyi bir uyum içinde olduklarını göstermek için Şekil 5'te alt alanların orta bölgesini yakınlaştırın. Fig5a

Şekil 6: Asit testi: enterpolasyonlu FFT uzayının ters 2D FFT'si hedefi kurtarır. Lena, sadece onu koyduğumuz her şeye rağmen hala iyi görünüyor (muhtemelen 2D FFT uçağını oldukça yoğun bir şekilde kapsayacak kadar sinogram "konuşmacı" olduğu için; pozlama açılarının sayısını azaltırsanız işler ilginçleşir, bu artık doğru değil ). resim açıklamasını buraya girin

İşte kod; Debian / Wheezy'nin 64 bitlik SciPy i7'de 15 saniyeden daha az bir sürede parseller getiriyor.

import math
import matplotlib
import matplotlib.pyplot as plt
import numpy as np

import scipy.interpolate
import scipy.fftpack
import scipy.misc
import scipy.ndimage.interpolation

S=256 # Size of target, and resolution of Fourier space
N=259 # Number of sinogram exposures (odd number avoids redundant direct opposites)

V=100 # Range on fft plots

# Convenience function
def sqr(x): return x*x

# Return the angle of the i-th (of 0-to-N-1) sinogram exposure in radians.
def angle(i): return (math.pi*i)/N

# Prepare a target image
x,y=np.meshgrid(np.arange(S)-S/2,np.arange(S)-S/2)
mask=(sqr(x)+sqr(y)<=sqr(S/2-10))
target=np.where(
    mask,
    scipy.misc.imresize(
        scipy.misc.lena(),
        (S,S),
        interp='cubic'
        ),
    np.zeros((S,S))
    )/255.0

plt.figure()
plt.title("Target")
plt.imshow(target)
plt.gray()

# Project the sinogram (ie calculate Radon transform)
sinogram=np.array([
        np.sum(
            scipy.ndimage.interpolation.rotate(
                target,
                np.rad2deg(angle(i)), # NB rotate takes degrees argument
                order=3,
                reshape=False,
                mode='constant',
                cval=0.0
                )
            ,axis=0
            ) for i in xrange(N)
        ])

plt.figure()
plt.title("Sinogram")
plt.imshow(sinogram)
plt.jet()

# Fourier transform the rows of the sinogram, move the DC component to the row's centre
sinogram_fft_rows=scipy.fftpack.fftshift(
    scipy.fftpack.fft(
        scipy.fftpack.ifftshift(
            sinogram,
            axes=1
            )
        ),
    axes=1
    )

plt.figure()
plt.subplot(121)
plt.title("Sinogram rows FFT (real)")
plt.imshow(np.real(sinogram_fft_rows),vmin=-V,vmax=V)
plt.subplot(122)
plt.title("Sinogram rows FFT (imag)")
plt.imshow(np.imag(sinogram_fft_rows),vmin=-V,vmax=V)

# Coordinates of sinogram FFT-ed rows' samples in 2D FFT space
a=np.array([angle(i) for i in xrange(N)])
r=np.arange(S)-S/2
r,a=np.meshgrid(r,a)
r=r.flatten()
a=a.flatten()
srcx=(S/2)+r*np.cos(a)
srcy=(S/2)+r*np.sin(a)

# Coordinates of regular grid in 2D FFT space
dstx,dsty=np.meshgrid(np.arange(S),np.arange(S))
dstx=dstx.flatten()
dsty=dsty.flatten()

plt.figure()
plt.title("Sinogram samples in 2D FFT (abs)")
plt.scatter(
    srcx,
    srcy,
    c=np.absolute(sinogram_fft_rows.flatten()),
    marker='.',
    edgecolor='none',
    vmin=-V,
    vmax=V
    )

# Let the central slice theorem work its magic!
# Interpolate the 2D Fourier space grid from the transformed sinogram rows
fft2=scipy.interpolate.griddata(
    (srcy,srcx),
    sinogram_fft_rows.flatten(),
    (dsty,dstx),
    method='cubic',
    fill_value=0.0
    ).reshape((S,S))

plt.figure()
plt.suptitle("FFT2 space")
plt.subplot(221)
plt.title("Recon (real)")
plt.imshow(np.real(fft2),vmin=-V,vmax=V)
plt.subplot(222)
plt.title("Recon (imag)")
plt.imshow(np.imag(fft2),vmin=-V,vmax=V)

# Show 2D FFT of target, just for comparison
expected_fft2=scipy.fftpack.fftshift(
    scipy.fftpack.fft2(
        scipy.fftpack.ifftshift(
            target
            )
        )
    )

plt.subplot(223)
plt.title("Expected (real)")
plt.imshow(np.real(expected_fft2),vmin=-V,vmax=V)
plt.subplot(224)
plt.title("Expected (imag)")
plt.imshow(np.imag(expected_fft2),vmin=-V,vmax=V)

# Transform from 2D Fourier space back to a reconstruction of the target
recon=np.real(
    scipy.fftpack.fftshift(
        scipy.fftpack.ifft2(
            scipy.fftpack.ifftshift(fft2)
            )
        )
    )

plt.figure()
plt.title("Reconstruction")
plt.imshow(recon,vmin=0.0,vmax=1.0)
plt.gray()

plt.show()

Güncelleme 2013-02-17: Eğer o partiyi geçecek kadar ilgiliyseniz, parçası olduğu kendi kendine çalışma programından daha fazla çıktı bu poster şeklinde bulunabilir . Bu depodaki kod gövdesi de ilgi çekici olabilir (ancak kodun neredeyse yukarıdaki gibi düzenlenmiş olmasına dikkat edin). Bir noktada bir IPython "notebook" olarak yeniden paketlemeyi deneyebilirim.


3

Sorunun tam olarak nerede olduğunu bilmiyorum, ancak dilim teoremi bu iki özel vakanın doğru olması gerektiği anlamına geliyor:

fft2(target)[0] = fft(sinogram[270])
fft2(target)[:,0] = fft(sinogram[0])

Bu yüzden kodunuzu takip edin ve bunların sinogramdan ileriye ve oluşturulan 2D FFT'den geriye doğru çalışarak eşdeğer durdukları noktayı bulmaya çalışın.

Bu doğru görünmüyor:

In [47]: angle(expected_fft2[127:130,127:130])
Out[47]: 
array([[-0.07101021,  3.11754929,  0.02299738],
       [ 3.09818784,  0.        , -3.09818784],
       [-0.02299738, -3.11754929,  0.07101021]])

In [48]: fft2_ = fft2_real+1.0j*fft2_imag

In [49]: angle(fft2_[127:130,127:130])
Out[49]: 
array([[ 3.13164353, -3.11056554,  3.11906449],
       [ 3.11754929,  0.        , -3.11754929],
       [ 3.11519503,  3.11056604, -2.61816765]])

Oluşturduğunuz 2D FFT olması gerekenden 90 derece döndürülüyor mu?

Gerçek ve hayali değil, büyüklük ve faz ile çalışmanızı öneririm, böylece neler olduğunu daha kolay görebilirsiniz:

resim açıklamasını buraya girin

(Beyaz köşeler yapılıyor - log(abs(0))sorun değil)


2

Ben ilk çözüm işe yaramadı neden gerçek teorik nedeni rotasyonlar bir ofset indükleyerek, görüntülerin merkezlerine göre yapılır olmasından kaynaklanır inanıyoruz [S/2, S/2], aramalarınızdan satırların her yani hangi yollarla sinogramuzak değildir 0için Sziyade gelen -S/2etmek S/2. Örneğinizde, mahsup aslında offset = np.floor(S/2.). Bunun Sçift ​​veya tek olarak çalıştığını ve kodunuzda yaptıklarınıza eşdeğer olduğunu unutmayın S/2(daha açık olmak , örneğin Sa olduğunda sorunları önler float).

Benim tahminim, bu kaymanın Fourier dönüşümünde (FT) ortaya koyduğu faz gecikmeleri, ikinci mesajınızda konuştuğunuz şeyin kökenidir: fazlar dağınıktır ve yapabilmek için bu kaymayı telafi etmesi gerekir. Radon dönüşümünün tersini uygular. Tersin beklendiği gibi çalışması için tam olarak neye ihtiyaç duyulduğundan emin olmak için bu teoriyi daha fazla araştırmak gerekir.

Bu ofseti telafi etmek için, ya ffthift tuşunu kullandığınız gibi kullanabilirsiniz (her satırın ortasını başlangıçta koyar ve DFT kullanmak aslında bir S-periyodik sinyalin Fourier Dönüşümünü hesaplamaya karşılık geldiğinden, doğru şeylerle sonuçlanırsınız ) veya FT'yi hesaplarken karmaşık Fourier dönüşümünde bu efekti açıkça telafi edin sinogram. Uygulamada, yerine:

sinogram_fft_rows=scipy.fftpack.fftshift(
    scipy.fftpack.fft(
        scipy.fftpack.ifftshift(
            sinogram,
            axes=1
            )
        ),
    axes=1
    )

ifftshifther satırı bir düzeltici vektörle kaldırabilir ve çarpabilirsiniz:

offset = np.floor(S/2.)
sinogram_fft_rows = scipy.fftpack.fftshift(
    scipy.fftpack.fft(sinogram, axis=1)
    * (np.exp(1j * 2.* np.pi * np.arange(S) * offset / S)),
    axes=1)

Bu, bir zaman kayması göz önüne alındığında Fourier dönüşüm özelliklerinden gelir ( FT wikipedia sayfasını "shift teoremi" için kontrol edin ve bu kaymaya eşit uygulayın - offset- çünkü görüntüyü tekrar merkeze koyduk).

Aynı şekilde, yeniden yapılandırmaya aynı stratejiyi uygulayabilir ve fftshifther iki boyutta, ancak diğer yönde (geri dengeleme) aşamaların düzeltilmesi ile değiştirebilirsiniz :

recon=np.real(
    scipy.fftpack.ifft2(
        scipy.fftpack.ifftshift(fft2)
        *  np.outer(np.exp(- 1j * 2.* np.pi * np.arange(S) * offset / S),
                    np.exp(- 1j * 2.* np.pi * np.arange(S) * offset / S))
        )
    )

Bu, çözümünüzü iyileştirmez, daha çok sorunuzun teorik yönlerine başka bir ışık tutar. Umarım yardımcı olur!

Buna ek olarak, bu kadar hesaplamak gibi bir fftshiftkarmaşa eğilimi çünkü kullanmaktan fftçok hoşlanmıyorum. Bununla birlikte, bu durumda, interpolasyondan önce FT'nin merkezini görüntünün merkezine koymanız gerekir fft2(veya en azından ayar yaparken dikkatli olun r- böylece tamamen fftshiftücretsiz hale getirebilirsiniz !) Ve fftshiftgerçekten kullanışlı olur Orada. Ancak ben bu fonksiyonun görselleştirme amacıyla kullanılmasını tercih ediyorum, "çekirdek" hesaplamasında değil. :-)

Saygılarımla,

Jean-Louis

Not: Çevreyi kırpmadan görüntüyü yeniden oluşturmaya çalıştınız mı? köşelerde oldukça havalı bir bulanıklık etkisi yaratıyor, Instagram gibi programlarda böyle bir özelliğe sahip olmak güzel olurdu, değil mi?

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.