เกิดอะไรขึ้นกับรหัสนี้สำหรับการสร้างภาพโทโมกราฟีใหม่โดยวิธีฟูริเยร์


19

ฉันได้เล่นกับอัลกอริทึมการสร้างใหม่ tomographic เมื่อเร็ว ๆ นี้ ฉันมีการใช้งานที่ดีของ FBP, ART, รูปแบบการวนซ้ำแบบ SIRT / SART และแม้กระทั่งการใช้พีชคณิตเชิงเส้นแบบตรง (ช้า!) คำถามนี้ไม่เกี่ยวกับการใด ๆ ของเทคนิคเหล่านั้น ; คำตอบของแบบฟอร์ม "ทำไมทุกคนจะทำอย่างนั้นนี่คือรหัส FBP บางส่วนแทน" ไม่ใช่สิ่งที่ฉันกำลังมองหา

สิ่งต่อไปที่ฉันต้องการจะทำกับโปรแกรมนี้คือ " ทำให้ครบชุด " และใช้ " วิธีการสร้างใหม่ฟูเรียร์ " ความเข้าใจของฉันเกี่ยวกับเรื่องนี้เป็นพื้นฐานที่คุณใช้ 1D FFT กับ "การสัมผัส" แบบไซน์จัดเรียงสิ่งเหล่านั้นในรูปแบบ "ซี่ล้อ" ในรัศมี 2D ในพื้นที่ฟูริเยร์ 2D (ซึ่งนี่เป็นสิ่งที่มีประโยชน์ที่ต้องทำดังนี้โดยตรงจาก สอดแทรกจากจุดเหล่านั้นไปยังตารางปกติในพื้นที่ 2D นั้นและจากนั้นควรจะสามารถแปลงฟูริเยร์เพื่อแปลงเป้าหมายสแกนต้นฉบับได้

ฟังดูง่าย แต่ฉันไม่ได้โชคดีเลยที่จะมีไทคอนซึ่งมีลักษณะเหมือนเป้าหมายดั้งเดิม

รหัส Python (numpy / SciPy / Matplotlib) ด้านล่างนี้เกี่ยวกับการแสดงออกที่กระชับที่สุดที่ฉันสามารถหาได้จากสิ่งที่ฉันพยายามจะทำ เมื่อเรียกใช้จะแสดงสิ่งต่อไปนี้:

รูปที่ 1: เป้าหมาย fig1

รูปที่ 2: รูปสัญลักษณ์ของเป้าหมาย รูปที่ 2

รูปที่ 3: แถว sinogram FFT-ed fig3

รูปที่ 4: แถวบนสุดคือพื้นที่ FFT 2 มิติที่ถูกสอดแทรกจากแถว sinogram ของโดเมนฟูริเยร์ แถวล่างคือ (สำหรับวัตถุประสงค์ในการเปรียบเทียบ) 2D โดยตรง FFT ของเป้าหมาย นี่คือจุดที่ฉันเริ่มสงสัย พล็อตที่ถูกสอดแทรกจาก FFT ของ sinogram นั้นดูคล้ายกับแผนการที่ทำโดย 2D-FFTing โดยตรงกับเป้าหมาย ... และยังแตกต่างกัน fig4

รูปที่ 5: การแปลงผกผัน - ฟูริเยร์ของรูปที่ 4 ฉันหวังว่าสิ่งนี้จะเป็นที่จดจำได้มากกว่าในฐานะที่เป็นเป้าหมายมากกว่าที่เป็นจริง fig5

ความคิดเห็นใดที่ฉันทำผิด ไม่แน่ใจว่าการทำความเข้าใจวิธีการฟื้นฟูฟูริเยร์ของฉันนั้นมีข้อบกพร่องพื้นฐานหรือมีข้อบกพร่องบางอย่างในรหัสของฉัน

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

1
นี่เทียบเท่ากับการใช้ FFT เพื่อคำนวณการแปลงเรดอนแบบผกผันหรือไม่?
endolith

... เพราะมีรหัสสำหรับที่นี่ สิ่งที่ควรอยู่ตรงกลางอยู่ที่ขอบและสิ่งของที่ควรอยู่ตรงกลางอยู่ตรงกลางเหมือนกับมีการเปลี่ยนเฟส 90 องศาที่อื่นไม่ควรมี
endolith

1
รหัสที่คุณเชื่อมโยงนั้นใช้สำหรับวิธีการฉายภาพด้านหลังกรอง (FBP) ซึ่งขึ้นอยู่กับคณิตศาสตร์กลางชิ้นเดียวกัน แต่ไม่เคยพยายามสร้างภาพโดเมน 2D ฟูริเยร์อย่างชัดเจน คุณสามารถดูการปราบปรามตัวกรอง FBP ของความถี่ต่ำว่าเป็นการชดเชยความหนาแน่นที่สูงขึ้นของ "ซี่" ชิ้นกลางที่อยู่ตรงกลาง ในวิธีการสร้างฟูเรียร์ที่ฉันพยายามจะใช้นี่เป็นเพียงความหนาแน่นที่สูงขึ้นของจุดที่จะแก้ไข ฉันยอมรับอย่างอิสระว่าฉันกำลังพยายามใช้เทคนิคที่ใช้เพียงเล็กน้อยและมีการรายงานข่าวที่มีข้อ จำกัด ในช่วงเวลาดังกล่าว
timday

โอ๊ะใช่คุณพูดถูก นี่เป็นรุ่นใน C ฉันดูมันเล็กน้อยและโพสต์บางสิ่ง ฉันจะดูเพิ่มเติมในภายหลัง
endolith

คำตอบ:


15

ตกลงฉันได้ทำมันแตกในที่สุด

เคล็ดลับโดยทั่วไปมาลงเพื่อวางบางfftshift/ ifftshifts ในสถานที่ที่เหมาะสมดังนั้นการแสดงพื้นที่ 2D ฟูริเยร์ก็ไม่ได้สั่นคลอนอย่างรุนแรงและถึงวาระที่จะเป็นไปไม่ได้ที่จะแก้ไขได้อย่างถูกต้อง อย่างน้อยนั่นคือสิ่งที่ฉันคิดว่าคงไว้ ส่วนใหญ่ของความเข้าใจที่ จำกัด ที่ฉันมีเกี่ยวกับทฤษฎีฟูริเยร์นั้นขึ้นอยู่กับสูตรการรวมอย่างต่อเนื่องและฉันมักจะพบว่าโดเมนไม่ต่อเนื่องและ FFT เป็นบิต ... แปลก

ในขณะที่ฉันพบรหัส MATLAB ค่อนข้างเป็นความลับ แต่ฉันต้องให้เครดิตกับการดำเนินการนี้อย่างน้อยทำให้ฉันมั่นใจว่าอัลกอริทึมการสร้างใหม่นี้สามารถแสดงออกอย่างเหมาะสมในสภาพแวดล้อมแบบนี้

ก่อนอื่นฉันจะแสดงผลลัพธ์จากนั้นให้โค้ด:

รูปที่ 1: เป้าหมายใหม่ที่ซับซ้อนกว่า Fig1

รูปที่ 2: ไซน์ (ตกลงตกลงมันคือการแปลงเรดอน) ของเป้าหมาย รูปที่ 2

รูปที่ 3: แถว FFT-ed ของ sinogram (พล็อตกับ DC ตรงกลาง) Fig3

รูปที่ 4: ไซน์ FFT-ed แปลงเป็นพื้นที่ FFT 2D (DC ที่กึ่งกลาง) สีคือฟังก์ชั่นของค่าสัมบูรณ์ Fig4

รูปที่ 4a: ซูมเข้าที่กึ่งกลางของพื้นที่ FFT 2D เพียงเพื่อแสดงลักษณะรัศมีของข้อมูล sinogram ได้ดีขึ้น Fig4a

รูปที่ 5: แถวบนสุด: พื้นที่ FFT 2D ที่ถูกสอดแทรกจากแถว FFT-ed ที่จัดเรียงแบบเรดิคัล แถวล่าง: รูปลักษณ์ที่คาดหวังจากเพียงแค่ 2D FFT-ing เป้าหมาย
Fig5

รูปที่ 5a: ซูมเข้าบริเวณภาคกลางของแผนย่อยในรูปที่ 5 เพื่อแสดงให้เห็นว่าสิ่งเหล่านี้เป็นข้อตกลงที่ดีงามในเชิงคุณภาพ Fig5a

รูปที่ 6: การทดสอบกรด: ผกผัน 2D FFT ของพื้นที่ FFT ที่ถูกแก้ไขจะกู้คืนเป้าหมาย Lena ยังคงดูดีอยู่แม้จะมีทุกอย่างที่เราเพิ่งทำให้เธอผ่านไปได้ (อาจเป็นเพราะมี "ซี่" รูปกรวยเพียงพอที่จะครอบคลุมระนาบ FFT 2 มิติได้ค่อนข้างหนาแน่นสิ่งต่าง ๆ น่าสนใจถ้าคุณลดจำนวนการเปิดรับแสง ) ป้อนคำอธิบายรูปภาพที่นี่

นี่คือรหัส; นำแปลงในน้อยกว่า 15s บน 64 บิตของ Debian / Wheezy SciPy บน 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()

อัปเดต 2013-02-17:หากคุณสนใจที่จะลุยผ่านล็อตนั้นผลลัพธ์เพิ่มเติมจากโปรแกรมการศึกษาด้วยตนเองซึ่งเป็นส่วนหนึ่งที่สามารถพบได้ในรูปแบบของโปสเตอร์นี้ เนื้อความของรหัสในพื้นที่เก็บข้อมูลนี้อาจเป็นที่สนใจ (แม้ว่าจะสังเกตได้ว่าโค้ดนั้นไม่ได้มีความคล่องตัวเหมือนกับที่กล่าวมาข้างต้น) ฉันอาจลองและจัดทำแพ็กเกจใหม่เป็น "โน๊ตบุ๊ค" IPython ในบางจุด


3

ฉันไม่รู้ว่าปัญหาอยู่ที่ใด แต่ทฤษฎีบทเชือดหมายความว่ากรณีพิเศษทั้งสองนี้ควรเป็นจริง:

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

ดังนั้นติดตามรหัสของคุณและพยายามหาจุดที่สิ่งเหล่านี้หยุดเสมอกันทำงานไปข้างหน้าจากไซน์และย้อนกลับจาก 2D FFT ที่สร้างขึ้น

สิ่งนี้ดูไม่ถูกต้อง:

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

FFT 2D ที่คุณสร้างขึ้นนั้นหมุน 90 องศาจากที่ควรจะเป็น

ฉันขอแนะนำให้ทำงานกับขนาดและเฟสไม่ใช่ของจริงและจินตภาพดังนั้นคุณสามารถเห็นสิ่งที่เกิดขึ้นได้ง่ายขึ้น:

ป้อนคำอธิบายรูปภาพที่นี่

(มุมสีขาวเป็น -inf จากการทำlog(abs(0))พวกเขาไม่ใช่ปัญหา)


2

ฉันเชื่อว่าเหตุผลทางทฤษฎีที่แท้จริงว่าทำไมการแก้ปัญหาครั้งแรกไม่ได้มาจากการหมุนที่เกิดขึ้นจากศูนย์กลางของภาพทำให้เกิดการชดเชย[S/2, S/2]ซึ่งหมายความว่าแต่ละแถวของคุณsinogramไม่ได้มาจาก0ถึงSแต่จากการ-S/2 ในตัวอย่างของการชดเชยเป็นจริงS/2 offset = np.floor(S/2.)โปรดทราบว่าสิ่งนี้ใช้ได้กับSคู่หรือคี่และเทียบเท่ากับสิ่งที่คุณทำในรหัสของคุณS/2(แม้ว่าจะเป็นปัญหาที่ชัดเจนมากขึ้นเพื่อหลีกเลี่ยงปัญหาเมื่อSเป็นfloatเช่น

ฉันเดาว่าเฟสเลื่อนการเปลี่ยนแปลงนี้ในการแปลงฟูริเยร์ (FT) เป็นจุดเริ่มต้นของสิ่งที่คุณพูดถึงในข้อความที่สองของคุณ: เฟสถูกทำให้ยุ่งเหยิงและเราต้องชดเชยการเปลี่ยนแปลงนั้นเพื่อให้สามารถ ใช้การผกผันของการแปลงเรดอน เราต้องขุดลึกลงไปในทฤษฎีเพื่อให้แน่ใจว่าสิ่งที่จำเป็นสำหรับผกผันในการทำงานตามที่คาดไว้

เพื่อชดเชยออฟเซ็ตคุณสามารถใช้ fftshift ได้เช่นเดียวกับที่คุณทำ (ซึ่งทำให้จุดศูนย์กลางของแต่ละแถวอยู่ที่จุดเริ่มต้นและเนื่องจากการใช้ DFT สอดคล้องกับการคำนวณการแปลงฟูริเยร์ของสัญญาณ S-periodic จริง ๆ ) หรือชดเชยผลกระทบนี้อย่างชัดเจนในการแปลงฟูริเยร์ที่ซับซ้อนเมื่อคำนวณsinogramFT ในทางปฏิบัติแทนที่จะเป็น:

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

คุณสามารถลบifftshiftและคูณแต่ละแถวด้วยเวกเตอร์ที่ถูกต้อง:

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)

สิ่งนี้มาจากคุณสมบัติการแปลงฟูริเยร์เมื่อพิจารณาการเปลี่ยนแปลงเวลา (ตรวจสอบหน้าวิกิพีเดีย FTสำหรับ "ทฤษฎีบทการเปลี่ยนแปลง" และใช้การเปลี่ยนแปลงเท่ากับ- offset- เพราะเราใส่ภาพกลับไปที่กึ่งกลาง)

ในทำนองเดียวกันคุณสามารถใช้กลยุทธ์เดียวกันกับการสร้างใหม่และแทนที่fftshiftด้วยการแก้ไขเฟสในทั้งสองมิติ แต่ไปในทิศทางอื่น (ชดเชยกลับ):

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

สิ่งนี้ไม่ได้ปรับปรุงการแก้ปัญหาของคุณ แต่ให้ความกระจ่างในแง่มุมทางทฤษฎีของคำถามของคุณ หวังว่าจะช่วย!

นอกจากนี้ฉันไม่ชอบใช้fftshiftเพราะมันมีแนวโน้มที่จะยุ่งกับวิธีการfftคำนวณ ในกรณีนี้คุณต้องใส่จุดกึ่งกลางของภาพตรงกลางภาพก่อนการแก้ไขเพื่อให้ได้fft2(หรืออย่างน้อยต้องระวังเมื่อตั้งค่าr- เพื่อให้คุณสามารถทำให้มันfftshiftฟรีอย่างสมบูรณ์!) และfftshiftมีประโยชน์จริงๆ ที่นั่น ฉันชอบที่จะใช้ฟังก์ชั่นนั้นต่อไปเพื่อจุดประสงค์ในการสร้างภาพและไม่ได้อยู่ในการคำนวณ "แกน" :-)

ขอแสดงความนับถืออย่างสูง,

Jean-Louis

PS: คุณพยายามที่จะสร้างภาพโดยไม่ต้องตัดวงกลมรอบ ๆ ? ที่ทำให้เอฟเฟ็กต์เบลอสวย ๆ ในมุมต่างๆน่าจะมีคุณสมบัติเช่นนี้ในโปรแกรมอย่าง Instagram ใช่ไหม?

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.