Author Topic: PJSR: Image.FFT(true/false) relationship?  (Read 9618 times)

Offline mschuster

  • PTeam Member
  • PixInsight Jedi
  • *****
  • Posts: 1087
PJSR: Image.FFT(true/false) relationship?
« on: 2015 February 04 23:03:25 »
Lacking documentation that I can find, I have been trying to figure out the relationship between the centered and uncentered Image.FFT's.

I had assumed that they were related by a simple shift of inputs/outputs between corner and center. But this does not seem to be the case. By trial and error I found that, at least with the shift as I envision it, an extra sign flip is needed, where the sign flip is row/col index mod 2 related. This script shows this flip, which is required to make the outputs identical.

Is this expected behavior? Maybe I am not doing the corner/center shift properly? Is there a simpler way to understand the centered/uncentered relationship?

Thanks,
Mike

Code: [Select]
#include <pjsr/ColorSpace.jsh>
#include <pjsr/SampleType.jsh>

function FFT(complexData, centered) {
   var rows = complexData.real.rows;
   var cols = complexData.real.cols;
   var image = new Image(cols, rows, 1, ColorSpace_Gray, 32, SampleType_Complex);

   var c = new Complex;
   for (var row = 0; row != rows; ++row) {
      for (var col = 0; col != cols; ++col) {
         c.real = complexData.real.at(row, col);
         c.imag = complexData.imag.at(row, col);
         image.setSample(c, col, row);
      }
   }

   image.FFT(centered);

   var newComplexData = {real: new Matrix(rows, cols), imag: new Matrix(rows, cols)};
   var c = new Complex;
   for (var row = 0; row != rows; ++row) {
      for (var col = 0; col != cols; ++col) {
         c = image.sample(col, row);
         newComplexData.real.at(row, col, c.real);
         newComplexData.imag.at(row, col, c.imag);
      }
   }

   image.free();

   return newComplexData;
}

function FFTShiftReal(realData) {
   var rows = realData.rows;
   var cols = realData.cols;
   var rows2 = rows >> 1;
   var cols2 = cols >> 1;
   var newRealData = new Matrix(rows, cols);
   for (var row = 0; row != rows; ++row) {
      for (var col = 0; col != cols; ++col) {
         newRealData.at(row, col, realData.at((row + rows2) % rows, (col + cols2) % cols));
      }
   }

   return newRealData;
}

function FFTShiftComplex(complexData) {
   return {real: FFTShiftReal(complexData.real), imag: FFTShiftReal(complexData.imag)};
}

function FFTFlipReal(realData) {
   var rows = realData.rows;
   var cols = realData.cols;
   var rows2 = rows >> 1;
   var cols2 = cols >> 1;
   var newRealData = new Matrix(rows, cols);
   for (var row = 0; row != rows; ++row) {
      for (var col = 0; col != cols; ++col) {
         newRealData.at(row, col, (row % 2) == (col % 2) ? -realData.at(row, col) : realData.at(row, col));
      }
   }

   return newRealData;
}

function FFTFlipComplex(complexData) {
   return {real: FFTFlipReal(complexData.real), imag: FFTFlipReal(complexData.imag)};
}

function maximumAbsoluteDifference(complexData1, complexData2) {
   function square(x) {
      return x * x;
   }

   var rows = complexData1.real.rows;
   var cols = complexData1.real.rows;
   var error = 0;
   for (var row = 0; row != rows; ++row) {
      for (var col = 0; col != cols; ++col) {
         error = Math.max(error, Math.sqrt(
            square(complexData1.real.at(row, col) - complexData2.real.at(row, col)) +
            square(complexData1.imag.at(row, col) - complexData2.imag.at(row, col))
         ));
      }
   }

   return error;
}

function randomRealData(rows, cols) {
   var newRealData = new Matrix(rows, cols);
   for (var row = 0; row != rows; ++row) {
      for (var col = 0; col != cols; ++col) {
         newRealData.at(row, col, Math.random());
      }
   }

   return newRealData;
}
 
function main() {
   for (var size = 1; size <= 1024; size *= 2) {
      var complexData = {real: randomRealData(size, size), imag: randomRealData(size, size)};
      //console.writeln("complexData.real: ", complexData.real.toArray());
      //console.writeln("complexData.imag: ", complexData.imag.toArray());

      var centeredFFT = FFT(complexData, true);
      //console.writeln("centeredFFT.real: ", centeredFFT.real.toArray());
      //console.writeln("centeredFFT.imag: ", centeredFFT.imag.toArray());

      var shiftComplexData = FFTShiftComplex(complexData);
      //console.writeln("shiftComplexData.real: ", shiftComplexData.real.toArray());
      //console.writeln("shiftComplexData.imag: ", shiftComplexData.imag.toArray());
      var shiftUncenteredFFT = FFT(shiftComplexData, false);
      //console.writeln("shiftUncenteredFFT.real: ", shiftUncenteredFFT.real.toArray());
      //console.writeln("shiftUncenteredFFT.imag: ", shiftUncenteredFFT.imag.toArray());
      var uncenteredFFT = FFTShiftComplex(shiftUncenteredFFT);
      //console.writeln("uncenteredFFT.real: ", uncenteredFFT.real.toArray());
      //console.writeln("uncenteredFFT.imag: ", uncenteredFFT.imag.toArray());

      var flipUncenteredFFT = FFTFlipComplex(uncenteredFFT);
      //console.writeln("flipUncenteredFFT.real: ", flipUncenteredFFT.real.toArray());
      //console.writeln("flipUncenteredFFT.imag: ", flipUncenteredFFT.imag.toArray());

      var error = maximumAbsoluteDifference(centeredFFT, flipUncenteredFFT);
      console.writeln("size: ", size, ", error: ", error);
   }
}

main();


Offline Juan Conejero

  • PTeam Member
  • PixInsight Jedi Grand Master
  • ********
  • Posts: 7111
    • http://pixinsight.com/
Re: PJSR: Image.FFT(true/false) relationship?
« Reply #1 on: 2015 February 05 02:40:29 »
Yes, this is the expected behavior, and your assumption about a sign inversion is correct. The following code is from the PJSR engine in the core application:

Code: [Select]
template <class P> static
void CenterFFT( GenericImage<P>& image )
{
   for ( int c = 0; c < image.NumberOfChannels(); ++c )
      for ( int i = 0; i < image.Height(); ++i )
      {
         typename P::sample* f = image.ScanLine( i, c ) + (i & 1);
         for ( int j = i & 1; j < image.Width(); j += 2, f += 2 )
            *f = -*f;
      }
}

This routine is called before performing the FFT of the image, if the "centered" argument is true. The image is of course of a 32-bit or 64-bit complex type.

The function above is equivalent to multiplying the complex matrix by (-1)^(x+y). This effectively shifts the origin of the DFT (the DC or zero-frequency component) to the center of the matrix. This is described for example in pages 237-238 of Gonzalez/Woods Digital Image Processing 3rd Edition.
Juan Conejero
PixInsight Development Team
http://pixinsight.com/

Offline mschuster

  • PTeam Member
  • PixInsight Jedi
  • *****
  • Posts: 1087
Re: PJSR: Image.FFT(true/false) relationship?
« Reply #2 on: 2015 February 05 08:31:36 »
Hi Juan,

I believe your sign flip code must be applied to BOTH input and output to make centered TRULY centered. Flipping only the input leaves the output "centered, but with a sign flip".

The need to flip both input and output may be an oversight in your reference Gonzalez/Woods, I don't have it to check.

Of course it is easy for users to handle and correct this "centered, but with a sign flip" themselves.  I will do so. But doing so bakes in the situation, and this patch will break if PI later changes.

Thanks,
Mike



Offline Juan Conejero

  • PTeam Member
  • PixInsight Jedi Grand Master
  • ********
  • Posts: 7111
    • http://pixinsight.com/
Re: PJSR: Image.FFT(true/false) relationship?
« Reply #3 on: 2015 February 05 08:34:51 »
Yes, of course. The same routine *is* in fact applied after the inverse FFT in core PJSR code.
Juan Conejero
PixInsight Development Team
http://pixinsight.com/

Offline Juan Conejero

  • PTeam Member
  • PixInsight Jedi Grand Master
  • ********
  • Posts: 7111
    • http://pixinsight.com/
Re: PJSR: Image.FFT(true/false) relationship?
« Reply #4 on: 2015 February 05 08:37:32 »
Here are the relevant PJSR routines that implement the Image.FFT() and Image.inverseFFT() JavaScript methods:

Code: [Select]
template <class P> inline static
void DoFFT( GenericImage<P>& image, bool centered )
{
   if ( centered )
      CenterFFT( image );
   InPlaceFourierTransform() >> image;
}

template <class P>
static void DoInverseFFT( GenericImage<P>& image, bool centered )
{
   InPlaceInverseFourierTransform() >> image;
   if ( centered )
      CenterFFT( image );
}
Juan Conejero
PixInsight Development Team
http://pixinsight.com/

Offline mschuster

  • PTeam Member
  • PixInsight Jedi
  • *****
  • Posts: 1087
Re: PJSR: Image.FFT(true/false) relationship?
« Reply #5 on: 2015 February 05 08:41:58 »
This is not correct.

Input and output flipping should be done on BOTH forward and inverse transforms.

Mike

Offline Juan Conejero

  • PTeam Member
  • PixInsight Jedi Grand Master
  • ********
  • Posts: 7111
    • http://pixinsight.com/
Re: PJSR: Image.FFT(true/false) relationship?
« Reply #6 on: 2015 February 05 08:51:09 »
It is done that way. See the code in my previous message.
Juan Conejero
PixInsight Development Team
http://pixinsight.com/

Offline mschuster

  • PTeam Member
  • PixInsight Jedi
  • *****
  • Posts: 1087
Re: PJSR: Image.FFT(true/false) relationship?
« Reply #7 on: 2015 February 05 08:54:46 »
No. Each transform should perform two flips not one. But as is is fine, I will deal with it.

Mike


Code: [Select]
template <class P> inline static
void DoFFT( GenericImage<P>& image, bool centered )
{
   if ( centered )
      CenterFFT( image );
   InPlaceFourierTransform() >> image;
   if ( centered )
      CenterFFT( image );
}

template <class P>
static void DoInverseFFT( GenericImage<P>& image, bool centered )
{
   if ( centered )
      CenterFFT( image );
   InPlaceInverseFourierTransform() >> image;
   if ( centered )
      CenterFFT( image );
}

Offline Juan Conejero

  • PTeam Member
  • PixInsight Jedi Grand Master
  • ********
  • Posts: 7111
    • http://pixinsight.com/
Re: PJSR: Image.FFT(true/false) relationship?
« Reply #8 on: 2015 February 05 09:21:22 »
Well, it depends on what you want to do with the transform, and on how you implement your frequency domain processing. We use FFT shifting (centering) to visualize the Fourier transform with the zero-frequency component in the middle of the spectrum. We also use it as part of the Fourier-Mellin transform (image registration with translation, rotation and scaling). As it is implemented in PJSR the shifted transform is invertible, so it works perfectly for these purposes (see for example the FourierTransform and InverseFourierTransform tools). Your two extra calls to CenterFFT(), after the direct transform and before the inverse transform, are a no-op for these applications, although they can be necessary in your specific application.

In the next version I'll implement an Image.shiftFFT() method, so this operation can be performed at native speed.
Juan Conejero
PixInsight Development Team
http://pixinsight.com/

Offline mschuster

  • PTeam Member
  • PixInsight Jedi
  • *****
  • Posts: 1087
Re: PJSR: Image.FFT(true/false) relationship?
« Reply #9 on: 2015 February 05 09:45:58 »
Thank you Juan. I agree with your comments and your plan sounds good. Thanks for taking the time to comment, I know you are very busy.

Mike

Offline Carlos Milovic

  • PTeam Member
  • PixInsight Jedi Master
  • ******
  • Posts: 2172
  • Join the dark side... we have cookies
    • http://www.astrophoto.cl
Re: PJSR: Image.FFT(true/false) relationship?
« Reply #10 on: 2015 February 07 08:23:43 »
Hi Mike

You don't need those extra multiplications. In fact, you shouldn't use them, as there is no theoretical need for them. Let me explain:

Let's assume that a standard FFT maps the frequencies so that the zero freq component is in the 0,0 coordinate. To visualize it on the center, since the fourier space is periodical, we just need to convolve the fft of the image with a impulse (Dirac delta) in the center of the image.
Due the properties of the fourier transform:
FFT( Image · function ) = FFT(Image) * FFT(function)
so, this convolution is exactly the same as multiplying the original image by the inverse FFT of the impulse, and then take the FFT of the modified image. The FFT-1 of the impulse at half the image size is (-1)^(x+y).

Following the same rationale, by performing the multiplication on the fourier space you are in fact shifting the original image, which is not needed at all (in most cases).
Regards,

Carlos Milovic F.
--------------------------------
PixInsight Project Developer
http://www.pixinsight.com

Offline mschuster

  • PTeam Member
  • PixInsight Jedi
  • *****
  • Posts: 1087
Re: PJSR: Image.FFT(true/false) relationship?
« Reply #11 on: 2015 February 07 16:29:17 »
Hi Carlos,

Let c and u be identical, power of 2 size images, except c is centered, and u is uncentered.

Notationally, u == Shift(c) and c == Shift(u) where Shift(x) = RotateRight(x, Dimensions(x) / 2) in Mathematica, a cyclical right rotation in both dimensions by half the size.

In my testing, u.fft(false) != c.fft(true). I believe they should be equal. Adding the second sign flip makes them equal.

Thanks,
Mike

Offline Carlos Milovic

  • PTeam Member
  • PixInsight Jedi Master
  • ******
  • Posts: 2172
  • Join the dark side... we have cookies
    • http://www.astrophoto.cl
Re: PJSR: Image.FFT(true/false) relationship?
« Reply #12 on: 2015 February 07 20:39:42 »
Are you shifting the signals before the fft?
Regards,

Carlos Milovic F.
--------------------------------
PixInsight Project Developer
http://www.pixinsight.com

Offline mschuster

  • PTeam Member
  • PixInsight Jedi
  • *****
  • Posts: 1087
Re: PJSR: Image.FFT(true/false) relationship?
« Reply #13 on: 2015 February 07 22:11:34 »
I have centered data c and need a centered fft.

c.shift().fft(false).shift() works fine.

c.fft(true) does not. Sign flip problem.

I think my prior post did not express this correctly.

In your convolution notation, I need f(c · d) · d, where f is uncentered fft and d is dirac.

I find that f(c * D) * D provides the correct result, where D is the fft of d, -1^(x+y).

I am pretty sure that two sign flips are needed, but so far I can not prove it.

Mike


« Last Edit: 2015 February 07 22:33:32 by mschuster »

Offline Carlos Milovic

  • PTeam Member
  • PixInsight Jedi Master
  • ******
  • Posts: 2172
  • Join the dark side... we have cookies
    • http://www.astrophoto.cl
Re: PJSR: Image.FFT(true/false) relationship?
« Reply #14 on: 2015 February 08 02:59:02 »
Yes, of course. If you need to shift the data at the begining, this is translated as a phase diference of Pi in the fourier plane, or the same -1^(x+y) multiplication.
What I also was trying to say is that shifting the data in the image domain is almost never needed (only if you want to measure something in the phase, and you need to unwrap it). So, this extra multiplication in the fourier domain is not needed (in most application).

Bottom line, if you want the centered fft of the centered data, and we take the shift function as this multiplication, then you need: shift( u.fft(true) )

PS: in signal processing the * sign is usually used for convolution and . for multiplication (formal mathematics... Not pseudocode).
Regards,

Carlos Milovic F.
--------------------------------
PixInsight Project Developer
http://www.pixinsight.com