Hi Mark,
Unfortunately we still have no reference documentation for the PixInsight JavaScript Runtime (PJSR). We'll start fixing this problem this Fall/Winter. The new version 1.8 of PixInsight includes an integrated PJSR documentation system (Object Explorer), similar to the Process Explorer window.
In the meanwhile, the only references for PJSR programming are the existing scripts and, above all, this forum. So please feel free to ask any doubts here and we'll try to do our best to help you.
I wish to take an fft of a sourceImage and then manipulate the magnitude Image with a filter of my own construction.
Take a look at this code snippet:
/*
* Returns the discrete Fourier transform (DFT) of an image as a new complex image.
*/
function DFT( image )
{
var C = Image.newComplexImage();
C.assign( image );
C.FFT( true/*centered*/ ); // DFT centered on the transformation matrix
return C;
}
This code defines a function that takes an image as argument and returns a complex image with its discrete Fourier transform. In PixInsight, each pixel of a complex image is a complex number with a real and an imaginary part. The real and imaginary components allow you to compute the magnitude and the phase of the DFT with the Complex.mag() and Complex.arg() methods, respectively.
A few important remarks about FFTs in PixInsight (this applies to both PJSR and PCL):
- PixInsight implements FFT routines able to work with vectors and matrices of arbitrary dimensions. You don't need to care about dimensions being powers of two, etc.
- The FFT routines can increase the original dimensions of the image, for optimization purposes. This means that the width and height of the C image in the function above can be slightly larger than the dimensions of the original image (usually the difference is less than 10 pixels).
- The FFT and inverse FFT routines can work with centered or non-centered DFTs. In a centered DFT, the 'DC' component of the transform (that is, the 'zero frequency' component) is located at the geometric center of the transform matrix (in the example above, at the central pixel of C). In a non-centered transform, the DC component is the top left pixel of the DFT. This can be controlled with a Boolean parameter of the Image.FFT() and Image.inverseFFT() methods.
- The inverse one-dimensional DFT is always scaled by the length of the transformed vector. This means that the inverse DFT of an image must be divided by the area of the transform matrix to yield the original range of pixel values. For example:
var R = new Image;
// ...
var I = Image.newComplexImage();
I.assign( R );
I.FFT();
I.inverseFFT();
R.assign( I ); // R = magnitude of I multiplied by I.width*I.height
R.apply( I.width*I.height, ImageOp_Div ); // return to the original rangeOnce you have the DFT of the image, you can modify it to implement your filter. For example, you can alter the magnitude component of the DFT pixel by pixel as follows:
function MyMagnitudeFilterFunction( dft )
{
for ( var y = 0; y < dft.height; ++y ) // for each row
for ( var x = 0; x < dft.width; ++x ) // for each column
{
var c = dft.sample( x, y );
/*
* c is a Complex object. c.real and c.imag are the real and imaginary
* parts of the DFT for the current x,y coordinates.
*/
var mag = c.mag();
var phase = c.arg();
mag = .... // Apply your function here
c = Complex.polar( mag, phase );
dft.setSample( c, x, y );
}
}
Or you can modify the complex image as a whole, depending on what you want to achieve. Finally, you can perform the inverse Fourier transform to get your transformed image. This is a little working example:
#include <pjsr/ImageOp.jsh>
#include <pjsr/UndoFlag.jsh>
function DFT( image )
{
var C = Image.newComplexImage();
C.assign( image );
C.FFT( true/*centered*/ ); // DFT centered on the transformation matrix
return C;
}
function IdealHighPassFilter( dft, radius )
{
var x0 = dft.width/2;
var y0 = dft.height/2;
var r = Math.round( Math.range( radius, 0, 1 ) * Math.max( dft.width, dft.height ) );
var r2 = r*r;
for ( var y = y0-r; y <= y0+r; ++y )
{
var dy = y - y0;
for ( var x = x0-r; x <= x0+r; ++x )
{
var dx = x - x0;
if ( dx*dx + dy*dy < r2 )
dft.setSample( 0, x, y );
}
}
}
var dft = DFT( ImageWindow.activeWindow.mainView.image );
IdealHighPassFilter( dft, 0.1 ); // remove a 10% of the low frequencies
dft.inverseFFT( true/*centered*/ );
var outputWindow = new ImageWindow( 1, 1, 1, 32/*bitsPerSample*/, true/*floatSample*/ );
var outputView = outputWindow.mainView;
outputView.beginProcess( UndoFlag_NoSwapFile ); // don't save an undo state
outputView.image.assign( dft ); // get the magnitude of DFT
outputView.image.apply( dft.width*dft.height, ImageOp_Div ); // rescale
outputView.endProcess();
outputWindow.show();
outputWindow.zoomToFit();
This example applies an ideal filter in the frequency domain. The ideal high-pass filter simply removes a range of low-frequency components of the DFT by setting their magnitudes to zero. As you can see, the results of an ideal filter are anything but 'ideal' (terrible ringing...), but this is just an example to show you the mechanics.
Let me know if this is the kind of information that you need.
(2012/08/26 07:54 UTC - Edited to fix a few mistakes in the examples)