फूरियर विधि द्वारा टोमोग्राफिक पुनर्निर्माण के लिए इस कोड के साथ क्या गलत है?


19

मैं हाल ही में टोमोग्राफिक पुनर्निर्माण एल्गोरिदम के साथ खेल रहा हूं। मेरे पास पहले से ही FBP, ART, SIRT / SART- जैसी पुनरावृत्ति योजना का अच्छा कार्यान्‍वयन है और यहां तक ​​कि सीधे रेखीय बीजगणित (धीमे!) का उपयोग करना है। यह प्रश्न उन तकनीकों में से किसी के बारे में नहीं है ; फॉर्म का उत्तर "कोई भी इस तरह से क्यों करेगा, यहां कुछ FBP कोड के बजाय" मैं वह नहीं हूं जो मैं देख रहा हूं।

अगली बात जो मैं इस कार्यक्रम के साथ करना चाहता था वह था " सेट पूरा करें " और तथाकथित " फूरियर पुनर्निर्माण विधि " को लागू करना। इस बारे में मेरी समझ मूल रूप से है कि आप 1D FFT को साइनोग्राम "एक्सपोज़र" पर लागू करते हैं, 2 डी फूरियर अंतरिक्ष में रेडियल "एक पहिया के प्रवक्ता" के रूप में व्यवस्थित करते हैं (कि यह केंद्रीय मसाला प्रमेय से सीधे अनुसरण करने के लिए एक उपयोगी चीज है) , उन बिंदुओं से उस 2 डी अंतरिक्ष में एक नियमित ग्रिड के लिए प्रक्षेपित करें, और फिर मूल स्कैन लक्ष्य को पुनर्प्राप्त करने के लिए फूरियर-परिवर्तन को उलटा करना संभव होना चाहिए।

सरल लगता है, लेकिन मुझे कोई भी पुनर्निर्माण प्राप्त करने का सौभाग्य नहीं मिला जो मूल लक्ष्य की तरह कुछ भी देखते हैं।

नीचे पायथन (numpy / SciPy / Matplotlib) कोड सबसे संक्षिप्त अभिव्यक्ति के बारे में है जो मैं करने की कोशिश कर रहा हूं। जब चलता है, तो यह निम्नलिखित प्रदर्शित करता है:

चित्र 1: लक्ष्य चित्र .1

चित्र 2: लक्ष्य का एक साइनोग्राम रेखा चित्र नम्बर 2

चित्र 3: एफएफटी-एड साइनोग्राम पंक्तियाँ fig3

चित्रा 4: शीर्ष पंक्ति फूरियर-डोमेन साइनोग्राम पंक्तियों से प्रक्षेपित 2 डी एफएफटी स्थान है; नीचे की पंक्ति लक्ष्य के प्रत्यक्ष 2 डी एफएफटी (तुलना उद्देश्यों के लिए) है। यह वह बिंदु है जिस पर मुझे संदेह होने लगा है; साइनोग्राम एफएफटी से प्रक्षेपित प्लॉट सीधे 2 डी-एफएफटी लक्ष्य द्वारा बनाए गए प्लॉट के समान दिखते हैं ... और फिर भी अलग। 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()


... क्योंकि यहाँ उस के लिए कोड है कि सामग्री जो केंद्र में होनी चाहिए वह किनारों पर है और सामान जो किनारों पर होना चाहिए वह केंद्र में है, जैसे कि कहीं 90degree चरण बदलाव नहीं होना चाहिए?
एंडोलिथ

1
आपके द्वारा जोड़ा गया कोड फ़िल्टर किए गए बैक प्रोजेक्शन (FBP) विधि के लिए है। जो एक ही केंद्रीय-स्लाइस गणित पर आधारित है, लेकिन 2 डी फूरियर डोमेन छवि बनाने के लिए कभी भी स्पष्ट रूप से प्रयास नहीं करता है। आप मध्य स्तर पर केंद्रीय स्लाइस "स्पोक्स" के उच्च घनत्व के लिए मुआवजे के रूप में कम आवृत्तियों के एफबीपी फ़िल्टर को देख सकते हैं। फूरियर पुनर्निर्माण पद्धति में मैं लागू करने का प्रयास कर रहा हूं, यह सिर्फ प्रक्षेप करने के लिए बिंदुओं के उच्च घनत्व के रूप में प्रकट होता है। मैं स्वतंत्र रूप से मानता हूँ कि मैं एक छोटी सी इस्तेमाल की हुई तकनीक को लागू करने की कोशिश कर रहा हूँ और साहित्य में इसका सीमित दायरा है,
टाइमडे

उफ़, हाँ तुम सही हो। यहाँ C में एक संस्करण है । मैंने इसे थोड़ा देखा और कुछ चीजें पोस्ट कीं। मैं बाद में और देखूंगा।
एंडोलिथ

जवाबों:


15

ठीक है, मैं अंत में यह टूट गया।

ट्रिक मूल रूप से सही जगह पर कुछ fftshift/ ifftshiftएस लगाने के लिए नीचे आई थी, इसलिए 2 डी फूरियर अंतरिक्ष प्रतिनिधित्व बेतहाशा दोलन नहीं था और सटीक रूप से प्रक्षेप करने के लिए असंभव होने के लिए बर्बाद हुआ था। कम से कम मुझे लगता है कि यह तय है। फूरियर सिद्धांत के बारे में मेरे पास जो सीमित समझ है, उसमें से अधिकांश निरंतर अभिन्न सूत्रीकरण पर आधारित है, और मैं हमेशा असतत डोमेन और एफएफटी को थोड़ा सा खोजता हूं ... विचित्र।

जबकि मुझे मैटलैब कोड मिल जाता है बल्कि गूढ़ है, मुझे इस कार्यान्वयन का श्रेय कम से कम मुझे यह विश्वास दिलाने के लिए है कि इस पुनर्निर्माण एल्गोरिथम को इस तरह के वातावरण में यथोचित रूप से व्यक्त किया जा सकता है।

पहले मैं परिणाम दिखाऊंगा, फिर कोड:

चित्र 1: एक नया, अधिक जटिल लक्ष्य। चित्र .1

चित्र 2: लक्ष्य का साइनोग्राम (ओके ओके, यह रेडॉन ट्रांसफॉर्म है)। रेखा चित्र नम्बर 2

चित्र 3: सिनोग्राम की एफएफटी-एड पंक्तियाँ (केंद्र में डीसी के साथ प्लॉट की गई)। fig3

चित्रा 4: एफएफटी-एड साइनोग्राम 2 डी एफएफटी स्पेस (डीसी एट सेंटर) में तब्दील हो गया। रंग निरपेक्ष मूल्य का एक कार्य है। Fig4

चित्रा 4 ए: केवल सिनोग्राम डेटा की रेडियल प्रकृति को बेहतर दिखाने के लिए 2 डी एफएफटी अंतरिक्ष के केंद्र में ज़ूम करें। Fig4a

चित्र 5: शीर्ष पंक्ति: 2 डी एफएफटी स्थान रेडियल रूप से व्यवस्थित एफएफटी-एड साइनोग्राम पंक्तियों से प्रक्षेपित होता है। नीचे पंक्ति: लक्ष्य से केवल 2D एफएफटी-इंग से अपेक्षित उपस्थिति।
Fig5

चित्रा 5 ए: इन लुक को गुणात्मक रूप से बहुत अच्छे समझौते में दिखाने के लिए चित्र 5 में सबप्लेट्स के मध्य क्षेत्र पर ज़ूम करें। Fig5a

चित्रा 6: एसिड परीक्षण: उल्टे एफएफटी अंतरिक्ष के 2 डी एफएफटी लक्ष्य को ठीक करता है। Lena अभी भी बहुत अच्छी लग रही है सब कुछ के बावजूद हम उसे बस के माध्यम से डाल दिया है (शायद इसलिए कि वहाँ काफी सिनोग्राम "प्रवक्ता" हैं 2 डी एफएफटी विमान को काफी घने रूप से कवर करने के लिए; चीजें दिलचस्प हो जाती हैं यदि आप एक्सपोजर कोणों की संख्या कम कर देते हैं तो यह अब सच नहीं है; )। यहाँ छवि विवरण दर्ज करें

यहाँ कोड है; एक i7 पर डेबियन / व्हीजी के 64 बिट साइंपी पर 15 से कम समय में भूखंडों को लाता है।

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 एफएफटी से पीछे।

यह सही नहीं लगता:

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

आप जो 2 डी एफएफटी पैदा कर रहे हैं, वह 90 डिग्री से घुमाया गया है जो यह होना चाहिए?

मैं आपको वास्तविक और काल्पनिक के बजाय परिमाण और चरण के साथ काम करने का सुझाव देता हूं, ताकि आप अधिक आसानी से देख सकें कि क्या हो रहा है:

यहाँ छवि विवरण दर्ज करें

(सफेद कोनों से कर रहे हैं 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उदाहरण के लिए है)।

मेरा अनुमान है कि फूरियर ट्रांसफॉर्म (एफटी) में इस बदलाव की शुरुआत में जो चरण विलंब होता है, उसके मूल में आप अपने दूसरे संदेश में जो बात करते हैं, उसके मूल में हैं: चरण गड़बड़ हैं, और किसी को उस पारी की भरपाई करने में सक्षम होना चाहिए। रैडॉन परिवर्तन के उलटा लागू करें। उस सिद्धांत में अधिक खुदाई करने की आवश्यकता है ताकि यह सुनिश्चित किया जा सके कि अपेक्षित रूप से काम करने के लिए व्युत्क्रम की क्या आवश्यकता है।

उस ऑफसेट की भरपाई करने के लिए, आप या तो fftshift का उपयोग कर सकते हैं जैसा कि आपने किया था (जो शुरुआत में प्रत्येक पंक्ति का केंद्र रखता है, और चूंकि DFT का उपयोग वास्तव में S- आवधिक संकेत के फूरियर ट्रांसफॉर्म की गणना करने से मेल खाता है, आप सही सामान के साथ समाप्त होते हैं ), या sinogramएफटी की गणना करते समय, स्पष्ट रूप से जटिल फूरियर रूपांतरण में इस प्रभाव की भरपाई करते हैं । व्यवहार में, इसके बजाय:

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)

यह फूरियर ट्रांसफॉर्म प्रॉपर्टीज से आता है, जब एक टाइम-शिफ्ट पर विचार करते हैं ( "शिफ्ट प्रमेय" के लिए एफटी विकिपीडिया पेज की जांच करें , और शिफ्ट के बराबर आवेदन करें - 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वास्तव में काम आता है। वहाँ। हालांकि मैं विज़ुअलाइज़ेशन उद्देश्यों के लिए उस फ़ंक्शन का उपयोग रखना पसंद करता हूं, और गणना "कोर" के भीतर नहीं। :-)

सादर,

जीन लुइस

पुनश्च: क्या आपने सर्कल को क्रॉप किए बिना छवि को फिर से बनाने की कोशिश की है? यह कोनों पर एक बहुत अच्छा धुंधला प्रभाव देता है, इंस्टाग्राम जैसे कार्यक्रमों में इस तरह की सुविधा होना अच्छा होगा, है ना?

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.