Apa yang salah dengan kode ini untuk rekonstruksi tomografi dengan metode Fourier?

19

Saya telah bermain-main dengan algoritma rekonstruksi tomografi baru-baru ini. Saya sudah memiliki implementasi kerja yang bagus dari FBP, ART, skema iterasi seperti SIRT / SART dan bahkan menggunakan aljabar linear lurus (lambat!). Pertanyaan ini bukan tentang salah satu dari teknik itu ; jawaban dari bentuk "mengapa ada orang yang melakukannya seperti itu, inilah beberapa kode FBP" bukan yang saya cari.

Hal berikutnya yang ingin saya lakukan dengan program ini adalah " melengkapi perangkat " dan mengimplementasikan apa yang disebut " metode rekonstruksi Fourier ". Pemahaman saya tentang ini pada dasarnya adalah bahwa Anda menerapkan 1D FFT ke sinogram "eksposur", mengatur mereka sebagai "jari-jari roda" radial dalam ruang 2D Fourier (bahwa ini adalah hal yang berguna untuk dilakukan mengikuti langsung dari teorema slice pusat) , interpolasi dari titik-titik tersebut ke kisi-kisi biasa dalam ruang 2D itu, dan kemudian dimungkinkan untuk membalik Fourier-transform untuk memulihkan target pemindaian asli.

Kedengarannya sederhana, tetapi saya belum beruntung mendapatkan rekonstruksi yang terlihat seperti target asli.

Kode Python (numpy / SciPy / Matplotlib) di bawah ini adalah tentang ekspresi paling ringkas yang bisa saya dapatkan dari apa yang saya coba lakukan. Saat dijalankan, ini menampilkan yang berikut:

Gambar 1: target gbr1

Gambar 2: sinogram dari target gbr2

Gambar 3: baris sinogram FFT-ed fig3

Gambar 4: baris atas adalah ruang FFT 2D yang diinterpolasi dari baris sinogram Fourier-domain; baris bawah adalah (untuk tujuan perbandingan) 2D FFT langsung dari target. Ini adalah titik di mana saya mulai curiga; plot yang diinterpolasi dari sinogram FFT terlihat mirip dengan plot yang dibuat oleh 2D-FFT langsung target ... namun berbeda. gbr4

Gambar 5: transformasi invers-Fourier dari Gambar 4. Saya berharap ini akan sedikit lebih dikenali sebagai target daripada yang sebenarnya. gbr5

Ada ide yang saya lakukan salah? Tidak yakin apakah pemahaman saya tentang rekonstruksi metode Fourier secara mendasar cacat, atau hanya ada beberapa bug dalam kode saya.

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()
timday
sumber
1
Apakah ini setara dengan menggunakan FFT untuk menghitung transformasi Radon terbalik ?
endolith
... karena ada kode untuk itu di sini Barang-barang yang seharusnya berada di tengah ada di tepinya dan barang-barang yang seharusnya ada di tepian ada di tengah, seperti ada pergeseran fase 90 derajat di suatu tempat yang seharusnya tidak ada?
endolith
1
Kode yang Anda tautkan adalah untuk metode proyeksi kembali yang difilter (FBP). Yang didasarkan pada matematika iris-pusat yang sama, tetapi tidak pernah secara eksplisit mencoba membangun citra domain Fourier 2D. Anda dapat melihat penekanan filter FBP untuk frekuensi rendah sebagai kompensasi untuk kepadatan lebih tinggi dari "jari-jari" slice tengah di bagian tengah. Dalam metode rekonstruksi Fourier yang saya coba implementasikan, ini hanya bermanifestasi sebagai kepadatan poin yang lebih tinggi untuk diinterpolasi. Saya dengan bebas mengakui bahwa saya sedang mencoba menerapkan teknik yang sedikit digunakan dan ada cakupan yang terbatas dalam literatur,
timday
Ups, ya Anda benar. Berikut adalah versi di C . Saya melihat sedikit dan memposting beberapa hal. Saya akan melihat lagi nanti.
endolith

Jawaban:

15

OK akhirnya saya sudah memecahkan ini.

Triknya pada dasarnya adalah meletakkan beberapa fftshift/ ifftshifts di tempat yang tepat sehingga representasi ruang Fourier 2D tidak berosilasi liar dan ditakdirkan mustahil untuk diinterpolasi secara akurat. Setidaknya itulah yang saya pikir memperbaikinya. Sebagian besar pemahaman terbatas yang saya miliki tentang teori Fourier didasarkan pada perumusan integral yang berkesinambungan, dan saya selalu menemukan domain diskrit dan FFT sedikit ... aneh.

Sementara saya menemukan kode matlab agak samar, saya harus menghargai implementasi ini untuk setidaknya memberi saya kepercayaan algoritma rekonstruksi ini dapat dinyatakan cukup kompak dalam lingkungan semacam ini.

Pertama saya akan menunjukkan hasil, lalu kode:

Gambar 1: target baru yang lebih kompleks. Fig1

Gambar 2: sinogram (OK OK, itu adalah transformasi Radon) dari target. Fig2

Gambar 3: baris FFT-ed dari sinogram (diplot dengan DC di tengah). Fig3

Gambar 4: sinogram FFT-ed diubah menjadi ruang FFT 2D (DC di tengah). Warna adalah fungsi dari nilai absolut. Fig4

Gambar 4a: Memperbesar bagian tengah ruang FFT 2D hanya untuk menunjukkan sifat radial dari data sinogram dengan lebih baik. Fig4a

Gambar 5: Baris atas: ruang FFT 2D diinterpolasi dari baris sinogram FFT-ed yang disusun secara radial. Baris bawah: penampilan yang diharapkan dari 2D FFT-ing target.
Fig5

Gambar 5a: Memperbesar bagian tengah dari subplot pada Gambar 5 untuk menunjukkan tampilan ini dalam persetujuan yang cukup bagus secara kualitatif. Fig5a

Gambar 6: Tes asam: FFT 2D terbalik dari ruang FFT yang diinterpolasi memulihkan target. Lena masih terlihat cukup baik meskipun kami baru saja melewatinya (mungkin karena ada "jari-jari" sinogram yang cukup untuk menutupi pesawat FFT 2D cukup padat; hal-hal menjadi menarik jika Anda mengurangi jumlah sudut paparan sehingga ini tidak lagi benar ). masukkan deskripsi gambar di sini

Ini kodenya; menampilkan plot dalam waktu kurang dari 15 detik pada SciPy 64bit Debian / Wheezy pada i7.

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()

Pembaruan 2013-02-17: Jika Anda cukup tertarik untuk mengarungi lot itu, beberapa output lagi dari program belajar mandiri yang merupakan bagiannya dapat ditemukan dalam bentuk poster ini . Isi kode dalam repositori ini mungkin juga menarik (walaupun perhatikan kode ini tidak seefisien yang di atas). Saya mungkin mencoba dan mengemasnya kembali sebagai "notebook" IPython di beberapa titik.

timday
sumber
3

Saya tidak tahu persis di mana masalahnya, tetapi teorema irisan berarti bahwa dua kasus khusus ini harus benar:

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

Jadi ikuti kode Anda dan cobalah untuk menemukan titik di mana ini berhenti menjadi setara, bekerja maju dari sinogram dan mundur dari FFT 2D yang dihasilkan.

Ini tidak beres:

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]])

2D FFT yang Anda hasilkan diputar 90 derajat dari yang seharusnya?

Saya sarankan bekerja dengan magnitudo dan fase daripada nyata dan imajiner, sehingga Anda dapat melihat lebih mudah apa yang terjadi:

masukkan deskripsi gambar di sini

(Sudut putih -inf melakukan log(abs(0)), mereka tidak masalah)

endolit
sumber
2

Saya percaya bahwa alasan teoretis yang sebenarnya mengapa solusi pertama tidak berhasil berasal dari kenyataan bahwa rotasi dilakukan sehubungan dengan pusat gambar, menginduksi offset [S/2, S/2], yang berarti bahwa setiap baris Anda sinogrambukan dari 0ke S, melainkan dari -S/2ke S/2. Dalam contoh Anda, offset sebenarnya offset = np.floor(S/2.). Perhatikan bahwa ini berfungsi untuk Sgenap atau ganjil, dan setara dengan apa yang Anda lakukan dalam kode Anda S/2(walaupun lebih eksplisit menghindari masalah, kapan Sa float, misalnya).

Dugaan saya adalah bahwa fase penundaan pergeseran ini memperkenalkan dalam transformasi Fourier (FT) adalah pada asal dari apa yang Anda bicarakan dalam pesan kedua Anda: fase-fase itu kacau, dan orang perlu mengkompensasi pergeseran itu agar dapat menerapkan inversi dari transformasi Radon. Kita perlu menggali lebih dalam teori itu untuk memastikan apa yang sebenarnya dibutuhkan agar invers bekerja seperti yang diharapkan.

Untuk mengkompensasi offset itu, Anda dapat menggunakan fftshift seperti yang Anda lakukan (yang menempatkan pusat setiap baris di awal, dan karena menggunakan DFT sebenarnya sesuai dengan menghitung Transformasi Fourier dari sinyal S-periodik, Anda berakhir dengan hal yang benar. ), atau secara eksplisit mengkompensasi efek ini dalam transformasi Fourier kompleks, ketika menghitung sinogramFT. Dalam praktik, alih-alih:

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

Anda dapat menghapus ifftshiftdan mengalikan setiap baris dengan vektor korektif:

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)

Ini berasal dari properti transformasi Fourier, ketika mempertimbangkan waktu-shift (periksa halaman wikipedia FT untuk "shift theorem", dan terapkan untuk shift yang sama dengan - offset- karena kami meletakkan gambar kembali di sekitar tengah).

Demikian juga, Anda dapat menerapkan strategi yang sama untuk rekonstruksi, dan menggantikannya fftshiftdengan koreksi fase, di kedua dimensi, tetapi di arah lain (kompensasi kembali):

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))
        )
    )

Nah, ini tidak meningkatkan solusi Anda, tetapi lebih memberi cahaya pada aspek teoritis dari pertanyaan Anda. Semoga itu bisa membantu!

Selain itu, saya tidak begitu suka menggunakan fftshiftkarena cenderung mengacaukan cara fftpenghitungannya. Namun, dalam hal ini, Anda harus meletakkan pusat FT di tengah gambar sebelum interpolasi untuk mendapatkan fft2(atau setidaknya berhati-hati saat mengatur r- sehingga Anda bisa membuatnya benar-benar fftshiftgratis!), Dan fftshiftsangat berguna sana. Namun saya lebih suka untuk tetap menggunakan fungsi itu untuk tujuan visualisasi, dan tidak dalam "inti" perhitungan. :-)

Salam Hormat,

Jean-Louis

PS: sudah mencoba merekonstruksi gambar tanpa memotong lingkaran? yang memberikan efek kabur yang cukup keren di sudut-sudut, akan menyenangkan jika memiliki fitur seperti itu di program-program seperti Instagram, bukan?

Jean-louis Durrieu
sumber