Có gì sai với mã này để tái cấu trúc chụp cắt lớp bằng phương pháp Fourier?


19

Gần đây tôi đã chơi xung quanh với các thuật toán tái cấu trúc chụp cắt lớp. Tôi đã có các triển khai FBP, ART, sơ đồ lặp giống như SIRT / SART và thậm chí sử dụng đại số tuyến tính thẳng (chậm!). Câu hỏi này không phải là về bất kỳ kỹ thuật nào ; câu trả lời của mẫu "tại sao mọi người sẽ làm theo cách đó, đây là một số mã FBP" không phải là những gì tôi đang tìm kiếm.

Điều tiếp theo tôi muốn làm với chương trình này là " hoàn thành bộ " và thực hiện cái gọi là " phương pháp tái tạo Fourier ". Hiểu biết của tôi về điều này về cơ bản là bạn áp dụng FFT 1D cho "phơi sáng" hình sin, sắp xếp chúng thành "nan hoa của bánh xe" trong không gian Fourier 2D (đây là một điều hữu ích để thực hiện trực tiếp từ định lý lát trung tâm) , nội suy từ các điểm đó đến một lưới thông thường trong không gian 2D đó, và sau đó có thể đảo ngược biến đổi Fourier để khôi phục mục tiêu quét ban đầu.

Nghe có vẻ đơn giản, nhưng tôi đã không gặp nhiều may mắn khi nhận được bất kỳ sự tái tạo nào trông giống như mục tiêu ban đầu.

Mã Python (numpy / SciPy / Matplotlib) dưới đây là về biểu thức ngắn gọn nhất mà tôi có thể nghĩ ra về những gì tôi đang cố gắng thực hiện. Khi chạy, nó sẽ hiển thị như sau:

Hình 1: mục tiêu hình 1

Hình 2: hình sin của mục tiêu hình 2

Hình 3: các hàng hình sin của FFT-ed Hình 3

Hình 4: hàng trên cùng là không gian FFT 2D được nội suy từ các hàng hình sin của miền Fourier; hàng dưới cùng là (để so sánh) FFT 2D trực tiếp của mục tiêu. Đây là điểm mà tôi bắt đầu nghi ngờ; các ô được nội suy từ các FFT hình sin trông tương tự như các ô được tạo bằng cách trực tiếp 2D-FFTing mục tiêu ... và khác nhau. hình 4

Hình 5: biến đổi nghịch đảo Fourier của Hình 4. Tôi đã hy vọng điều này sẽ dễ nhận biết hơn một chút so với mục tiêu so với thực tế. hình 5

Có ai biết tôi đang làm gì sai không? Không chắc chắn nếu sự hiểu biết của tôi về việc xây dựng lại phương pháp Fourier về cơ bản là thiếu sót, hoặc chỉ có một số lỗi trong mã của tôi.

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


... bởi vì có mã cho điều đó ở đây Thứ nên ở trung tâm nằm ở rìa và thứ nên ở rìa nằm ở trung tâm, giống như có sự thay đổi pha 90 độ ở đâu đó không nên có?
endolith

1
Mã bạn liên kết là dành cho phương thức chiếu ngược (FBP) được lọc. Dựa trên cùng một toán học trung tâm, nhưng không bao giờ cố gắng xây dựng hình ảnh miền 2D Fourier một cách rõ ràng. Bạn có thể xem việc loại bỏ tần số thấp của bộ lọc FBP như là sự bù cho mật độ cao hơn của "nan hoa" ở giữa. Trong phương pháp tái tạo Fourier mà tôi đang cố gắng thực hiện, điều này chỉ biểu hiện dưới dạng mật độ điểm cao hơn để nội suy từ đó. Tôi tự do thừa nhận tôi đang cố gắng để thực hiện một chút kỹ thuật được sử dụng và có vùng phủ sóng hạn chế của nó trong văn học,
timday

Rất tiếc, vâng, bạn đúng. Dưới đây là một phiên bản trong C . Tôi nhìn qua nó một chút và đăng một số điều. Tôi sẽ xem xét sau.
endolith

Câu trả lời:


15

OK tôi đã phá vỡ điều này cuối cùng.

Thủ thuật về cơ bản được đưa ra để đặt một số fftshift/ ifftshifts vào đúng vị trí để biểu diễn không gian 2D Fourier không dao động mạnh mẽ và cam chịu không thể nội suy chính xác. Ít nhất đó là những gì tôi nghĩ đã sửa nó. Hầu hết những hiểu biết hạn chế mà tôi có về lý thuyết Fourier đều dựa trên công thức tích phân liên tục và tôi luôn thấy miền rời rạc và FFT hơi ... kỳ quặc.

Mặc dù tôi thấy mã MATLAB khá khó hiểu, tôi phải tin tưởng vào việc triển khai này ít nhất là cho tôi sự tự tin rằng thuật toán tái tạo này có thể được thể hiện một cách hợp lý trong môi trường này.

Đầu tiên tôi sẽ hiển thị kết quả, sau đó mã:

Hình 1: một mục tiêu mới, phức tạp hơn. Hình 1

Hình 2: hình sin (OK OK, đó là biến đổi Radon) của mục tiêu. Hình 2

Hình 3: các hàng FFT-ed của hình sin (được vẽ với DC ở giữa). Hình 3

Hình 4: hình sin FFT-ed được chuyển thành không gian FFT 2D (DC ở giữa). Màu sắc là một chức năng của giá trị tuyệt đối. Hình 4

Hình 4a: Phóng to trung tâm của không gian FFT 2D chỉ để hiển thị tính chất xuyên tâm của dữ liệu hình sin tốt hơn. Hình 4a

Hình 5: Hàng trên cùng: không gian FFT 2D được nội suy từ các hàng hình sin FFT được sắp xếp triệt để. Hàng dưới cùng: sự xuất hiện dự kiến ​​từ 2D FFT đơn giản vào mục tiêu.
Hình 5

Hình 5a: Phóng to khu vực trung tâm của các ô con trong Hình 5 để cho thấy các giao diện này có vẻ là một thỏa thuận khá tốt về mặt chất lượng. Hình 5a

Hình 6: Kiểm tra axit: FFT 2D nghịch đảo của không gian FFT nội suy phục hồi mục tiêu. Lena trông vẫn khá ổn dù mọi thứ chúng tôi vừa đưa cô ấy qua (có lẽ vì có đủ "nan hoa" hình sin để che phủ mặt phẳng 2D FFT khá dày đặc; mọi thứ trở nên thú vị nếu bạn giảm số lượng góc phơi sáng nên điều này không còn đúng nữa ). nhập mô tả hình ảnh ở đây

Đây là mã; đưa ra các âm mưu trong chưa đầy 15 giây trên SciPy 64bit của Debian / Wheezy trên 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()

Cập nhật 2013/02/17: Nếu bạn đã đủ quan tâm để lội qua số tiền đó, một số đầu ra khác từ chương trình tự học mà nó là một phần có thể được tìm thấy dưới dạng poster này . Phần thân của mã trong kho lưu trữ này cũng có thể được quan tâm (mặc dù lưu ý rằng mã không gần như được sắp xếp hợp lý như trên). Tôi có thể thử và đóng gói lại dưới dạng "sổ ghi chép" IPython tại một số điểm.


3

Tôi không biết chính xác vấn đề ở đâu, nhưng định lý lát có nghĩa là hai trường hợp đặc biệt này phải đúng:

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

Vì vậy, hãy theo dõi mã của bạn và cố gắng tìm điểm mà các điểm dừng này tương đương nhau, hoạt động chuyển tiếp từ hình sin và ngược lại từ FFT 2D được tạo.

Điều này có vẻ không đúng:

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 mà bạn đang tạo được xoay 90 độ so với mức cần thiết?

Tôi khuyên bạn nên làm việc với cường độ và pha chứ không phải thực và tưởng tượng, để bạn có thể thấy dễ dàng hơn những gì đang xảy ra:

nhập mô tả hình ảnh ở đây

(Các góc trắng không được thực hiện log(abs(0)), chúng không phải là vấn đề)


2

Tôi tin rằng lý do lý thuyết thực tế tại sao giải pháp đầu tiên đã không làm việc xuất phát từ thực tế là các phép quay được thực hiện liên quan đến các trung tâm của hình ảnh, gây một bù đắp của [S/2, S/2], có nghĩa là mỗi người trong số các hàng của bạn sinogramkhông phải là từ 0để S, mà đúng hơn là từ -S/2để S/2. Trong ví dụ của bạn, phần bù là thực sự offset = np.floor(S/2.). Lưu ý rằng điều này hoạt động cho Schẵn hoặc lẻ và tương đương với những gì bạn đã làm trong mã của mình S/2(mặc dù rõ ràng hơn sẽ tránh được các vấn đề, ví dụ như khi nào Slà một floatví dụ).

Tôi đoán là giai đoạn trì hoãn sự thay đổi này giới thiệu trong biến đổi Fourier (FT) là nguồn gốc của những gì bạn nói trong thông điệp thứ hai của bạn: các giai đoạn bị rối và người ta cần phải bù dịch chuyển đó để có thể áp dụng nghịch đảo của biến đổi Radon. Người ta cần đào sâu hơn vào lý thuyết đó để chắc chắn về những gì chính xác là cần thiết để nghịch đảo hoạt động như mong đợi.

Để bù phần bù đó, bạn có thể sử dụng frafthift như bạn đã làm (đặt trung tâm của mỗi hàng ở đầu và vì sử dụng DFT thực sự tương ứng với việc tính toán Fourier Transform của tín hiệu định kỳ S, bạn kết thúc với đúng thứ ) hoặc bù một cách rõ ràng hiệu ứng này trong biến đổi Fourier phức tạp, khi tính toán sinogramFT. Trong thực tế, thay vì:

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

bạn có thể xóa ifftshiftvà nhân mỗi hàng bằng một vectơ hiệu chỉnh:

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)

Điều này xuất phát từ các thuộc tính biến đổi Fourier, khi xem xét dịch chuyển thời gian (kiểm tra trang FT wikipedia để biết "định lý dịch chuyển" và áp dụng cho dịch chuyển bằng - offset- vì chúng tôi đặt hình ảnh trở lại trung tâm).

Tương tự như vậy, bạn có thể áp dụng chiến lược tương tự cho việc tái cấu trúc và thay thế fftshiftbằng cách hiệu chỉnh các pha, theo cả hai chiều, nhưng theo hướng khác (bù lại):

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

Chà, điều này không cải thiện giải pháp của bạn, mà là làm sáng tỏ một khía cạnh lý thuyết khác trong câu hỏi của bạn. Mong rằng sẽ giúp!

Ngoài ra, tôi không thích sử dụng fftshiftvì nó có xu hướng lộn xộn với cách ffttính toán. Tuy nhiên, trong trường hợp này, bạn cần đặt trung tâm FT ở giữa hình ảnh trước khi nội suy để có được fft2(hoặc ít nhất là cẩn thận khi cài đặt r- để bạn có thể làm cho nó hoàn toàn fftshiftmiễn phí!), Và fftshiftthực sự tiện dụng ở đó Tuy nhiên, tôi thích duy trì việc sử dụng chức năng đó cho mục đích trực quan hóa, và không nằm trong "lõi" tính toán. :-)

Trân trọng,

Jean-Louis

PS: bạn đã cố gắng xây dựng lại hình ảnh mà không cắt xén vòng tròn xung quanh chưa? mang lại hiệu ứng làm mờ khá thú vị ở các góc, sẽ rất tuyệt nếu có một tính năng như vậy trong các chương trình như Instagram, phải không?

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.