푸리에 (Fourier) 방법에 의한 단층 재구성을위한이 코드의 문제점은 무엇입니까?


19

최근에 단층 복원 알고리즘을 가지고 놀았습니다. 나는 이미 FBP, ART, SIRT / SART와 같은 반복 체계를 구현하고 직선 대수를 사용하여 느리게 작동합니다 (느린!). 이 질문은 그러한 기술에 관한 것이 아닙니다 . "누구도 그렇게했을까요? 여기에 FBP 코드가 있습니다"라는 대답은 내가 찾는 것이 아닙니다.

이 프로그램으로 다음에하고 싶었던 것은 " 세트를 완성 "하고 소위 " 푸리에 재구성 방법 "을 구현하는 것이 었습니다 . 이것에 대한 나의 이해는 기본적으로 1D FFT를 사이 노 그램 "노출"에 적용하고 2D 푸리에 공간에서 방사형 "바퀴 스포크"로 정렬한다는 것입니다 (중앙 슬라이스 정리에서 직접 수행하는 것이 유용한 것입니다) , 해당 점에서 해당 2D 공간의 일반 그리드로 보간 한 다음 역 푸리에 변환을 수행하여 원래 스캔 대상을 복구 할 수 있어야합니다.

간단하게 들리지만 원래 목표와 비슷한 재구성을 얻는 데 많은 운이 없었습니다.

아래의 Python (numpy / SciPy / Matplotlib) 코드는 내가하려고하는 일에서 얻을 수있는 가장 간결한 표현에 관한 것입니다. 실행되면 다음이 표시됩니다.

그림 1 : 목표 무화과 1

그림 2 : 대상의 사이 노 그램 무화과 2

그림 3 : FFT-ed 사이 노 그램 행 fig3

그림 4 : 상단 행은 푸리에 도메인 사이 노 그램 행에서 보간 된 2D FFT 공간입니다. 맨 아래 행은 (비교 목적으로) 대상의 직접 2D FFT입니다. 이것이 내가 의심스러워지기 시작하는 시점입니다. 사이 노 그램 FFT에서 보간 된 플롯은 대상을 직접 2D-FFT하여 만든 플롯과 비슷하지만 다릅니다. 무화과 4

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


... 여기에 대한 코드가 있기 때문에 중앙에 있어야하는 물건은 가장자리에 있고 가장자리에 있어야하는 물건은 중앙에 있어야합니다.
endolith

1
연결 한 코드는 필터링 된 역 투영 (FBP) 방법입니다. 이는 동일한 중앙 슬라이스 수학을 기반으로하지만 2D 푸리에 도메인 이미지를 명시 적으로 만들려고 시도하지 않습니다. FBP 필터의 저주파 억제 기능은 중앙 슬라이스 "스포크"의 높은 밀도를 보상하는 것으로 볼 수 있습니다. 내가 구현하려고하는 푸리에 재구성 방법에서 이것은 보간 할 더 높은 밀도의 점으로 나타납니다. 나는 자유롭게 내가 조금 사용되는 기술을 구현하기 위해 노력하고있어 인정하고 그것의 제한 범위는 문헌에있다
timday

네, 맞습니다. 다음 은 C 버전입니다 . 나는 조금 살펴보고 몇 가지를 게시했습니다. 나중에 더 볼게요.
endolith

답변:


15

좋아, 나는 이것을 마침내 깨뜨렸다.

이 트릭은 기본적으로 일부 fftshift/ ifftshifts를 올바른 위치에두기 때문에 2D 푸리에 공간 표현이 크게 진동하지 않고 정확하게 보간하기가 불가능한 운명이었습니다. 적어도 그것이 내가 고쳤다 고 생각 하는 것입니다. 푸리에 이론에 대한 제한된 이해의 대부분은 연속 적분 공식을 기반으로하며, 항상 불연속 영역과 FFT를 조금씩 ...

MATLAB 코드가 다소 암호로 보이지만, 이러한 재구성 알고리즘이 이런 종류의 환경에서 합리적으로 컴팩트하게 표현 될 수 있다는 확신을 적어도 제공하기 위해이 구현 을 인정해야 합니다.

먼저 결과를 보여준 다음 코드를 보여 드리겠습니다 :

그림 1 : 새롭고 더 복잡한 목표. 무화과 1

그림 2 : 대상의 사이 노 그램 (OK OK, Radon 변환) 그림 2

그림 3 : 사이 노 그램의 FFT-ed 행 (중앙에 DC로 표시). 그림 3

그림 4 : 2D FFT 공간 (중심에서 DC)으로 변환 된 FFT-ed 사이 노 그램. 색상은 절대 값의 함수입니다. 그림 4

그림 4a : 2D FFT 공간의 중심을 확대하여 사이 노 그램 데이터의 방사형 특성을 더 잘 보여줍니다. 무화과

그림 5 : 상단 행 : 방사형으로 배열 된 FFT-ed 사이 노 그램 행에서 보간 된 2D FFT 공간. 맨 아래 줄 : 단순히 대상을 2D FFT 할 때의 예상 모양입니다.
그림 5

그림 5a : 그림 5에서 서브 플로트의 중앙 영역을 확대하여 이러한 모양이 질적으로 일치 함을 보여줍니다. 무화과

그림 6 : 산 테스트 : 보간 된 FFT 공간의 역 2D FFT는 목표를 복구합니다. Lena는 우리가 방금 옮긴 모든 일에도 불구하고 여전히 꽤 좋아 보입니다 (아마도 2D FFT 평면을 상당히 조밀하게 덮을 수있는 충분한 사이 노 그램 "스포크"가 있기 때문에 노출 각도의 수를 줄이면 더 재미있을 것입니다. ). 여기에 이미지 설명을 입력하십시오

코드는 다음과 같습니다. i7에서 Debian / Wheezy의 64 비트 SciPy에서 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 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]])

생성중인 2D FFT가 원래 각도에서 90도 회전합니까?

실제와 상상이 아닌 규모와 위상을 다루는 것이 좋습니다.

여기에 이미지 설명을 입력하십시오

(흰색 모서리는 -inf로 log(abs(0))인해 문제가되지 않습니다.)


2

나는 최초의 솔루션이 작동하지 않는 이유는 실제 이론적 이유는 회전이의 오프셋을 유도, 이미지의 중심에 대하여 수행하고 있다는 사실에서 온다 믿고 [S/2, S/2]당신의 각 행이 그 어떤 수단 sinogram에서하지 0S오히려에서 -S/2S/2. 귀하의 예에서 오프셋은 실제로 offset = np.floor(S/2.)입니다. 참고이를위한 작품 것을 S짝수 또는 홀수, 당신은 당신의 코드에서했던 것과 동일하다 S/2(더 명시을 피 문제가되고 있지만,이 때 SA는 float예를 들어).

제 생각에는 푸리에 변환 (FT)에서이 변화가 도입하는 위상 지연이 두 번째 메시지에서 말하는 것의 근원에 있다는 것입니다. 위상이 엉망이되어서 그 변화를 보상해야합니다. 라돈 변환의 반전을 적용합니다. 역수가 예상대로 작동하기 위해 정확히 필요한 것이 무엇인지 확인하기 위해이 이론을 더 깊이 파고 들어야합니다.

오프셋을 보상하기 위해 fftshift를 사용하여 (각 행의 중심을 처음에 배치하고, DFT를 사용하면 실제로 S-주기 신호의 푸리에 변환 계산에 해당하므로 올바른 결과를 얻습니다. 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. 그러나이 경우, 당신은 얻을 수있는 보간 전에 이미지의 중심에서 FT의 중심을 둘 필요가 fft2(또는 설정하는 경우 적어도주의 r- 당신이 그것을 완전히 만들 수 있도록 fftshift! - 무료)하고, fftshift정말 편리 온다 그곳에. 그러나 계산 "핵심"이 아닌 시각화 목적으로 해당 기능을 사용하는 것을 선호합니다. :-)

친애하는,

장 루이스

추신 : 원을 자르지 않고 이미지를 재구성하려고 했습니까? 그것은 코너에 꽤 멋진 흐림 효과를 제공하며 Instagram과 같은 프로그램에서 그러한 기능을 갖는 것이 좋을까요?

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.