Small questions for PJSR programming

roryt

Well-known member
Vacations starting tomorrow, but without the ability to carry telescopes/equipment with me (lack of space in the car  :sad: )
Nevertheless, time for learning PJSR  ;) Never coded Javascript, but what the heck how difficult can be, 15-10 years before I used to made my leaving coding in C / C++ , Delphi, Perl, TCL/TK , assembly, even Cobol, and more....

Already read older scripts, sticky threads, a few older threads, but (due to lack of documentation) a have a couple of quick questions that I need a few quick answers (or hints where on other scripts I can find the answers), because I do not have the time before I leave to search the forum and most probably I won't have Internet access.

1) Currently I write my script inside script editor (or on my favorite programmer's editor)  and then I press F9 on an image. How do I include it in my PI menus ? I saved mine in the same directory as all the others, used #feature-id and #feature-info and tried to "Regenerate" "Feature Scripts", but still I can not see it in the menu.  

2) How do I put a "New Instance" triangle and a "Apply (F5)" rectangle on my dialog? I see that many scripts specify with drop-down lists on which image(s) they should apply, but is there a way a PJSR script to follow the same elegant triangle/rectangle schema ?

3) Let say that I modify an image using beginProcess(); / endProcess(); . How do I specify a more descriptive name so that the history browser shows this name and not just "Script" ?

4) Let me see if I got this right, if I want to use an external object on an image (eg HistogramTransformation) , I create a new instance of it and then I use the executeOn ( view ) method, right ? not the other way round (ie apply the external object on the view via any view's method) ?
eg

var p = new HistogramTransformation;
------ manipulation of H etc etc ----
var window = ImageWindow.activeWindow;
if ( window.isNull )
     throw new Error( "No active image" );
var view = window.currentView;
p.executeOn( view);

5) and in the above case I DO NOT use beginProcess(); / endProcess();  right ?

6) How do I use Image.FFT, or as the matter of fact, any other FFT method, to produce the FFT (or the reverse) of an image ? It replies that it requires a complex number   ??? ? I lack the theoretical background on this, but I thought that FFT is something that you can just pop from any image (based on ImageJ's equivelant function)

thanks in advance

 
I don't have any experience with JS, sooo, I'll go directly to 6)

Yes, the FFT of a image (or any function) is a complex image. What is commonly displayed is the "Power Spectrum", or the magnitude of the number (not the phase angle). Also is commonly displayed the real and imaginary parts as two separate images.
The PCL handles internally 32 and 64bits floating point complex images. I guess that this is handled in a similar way in scripts. The output of the FFT must point to a complex image. Take a look at the code of FFTRegistration.
 
Ioannis Ioannou said:
1) Currently I write my script inside script editor (or on my favorite programmer's editor)  and then I press F9 on an image. How do I include it in my PI menus ? I saved mine in the same directory as all the others, used #feature-id and #feature-info and tried to "Regenerate" "Feature Scripts", but still I can not see it in the menu.  

2) How do I put a "New Instance" triangle and a "Apply (F5)" rectangle on my dialog? I see that many scripts specify with drop-down lists on which image(s) they should apply, but is there a way a PJSR script to follow the same elegant triangle/rectangle schema ?

...
Add 1) For me, Feature Scripts/Add always worked.
Add 2) The CanonBanding Script  has an example for the triangle.

Georg
 
Hi John,

Welcome to PixInsight development. Phew! quite a bunch of questions! :) I'll try to answer them as well as I can.

1) Currently I write my script inside script editor (or on my favorite programmer's editor)  and then I press F9 on an image. How do I include it in my PI menus ? I saved mine in the same directory as all the others, used #feature-id and #feature-info and tried to "Regenerate" "Feature Scripts", but still I can not see it in the menu.

Regenerate is not sufficient. You must click the Add button to effectively add your script to the list of featured scripts.

2) How do I put a "New Instance" triangle and a "Apply (F5)" rectangle on my dialog? I see that many scripts specify with drop-down lists on which image(s) they should apply, but is there a way a PJSR script to follow the same elegant triangle/rectangle schema ?

Take a look at the DrawSignature script and study the DrawSignatureEngine object carefully. In the DrawSignatureEngine.initialize() method you can see how the Parameters PJSR object is used to retrieve a set of parameters that the script uses (text, fontFace, fontSize, etc.). The following method of Parameters:

Code:
Boolean Parameters.has( String id )

returns true when the specified parameter id is currently defined as a script parameter. The family of methods:

Code:
Boolean Parameters.getBoolean( String id )
Number Parameters.getInteger( String id[, int radix=0] )
Number Parameters.getReal( String id )
String Parameters.getString( String id )
Number Parameters.getUInt( String id[, int radix=0] )

Return the value of a parameter id of the specified data types. Note that type conversions are performed automatically by the PJSR. For security reasons, all script parameters are stored as objects of type String. This prevents injections of malicious JavaScript code through script parameters. The automatic conversion implies that a script parameter whose value is "1.234" can be obtained as both String and Number; however, "1+2" can only be  String, never Number.

Code:
Boolean Parameters.isViewTarget

is true when the script is being executed in the view context. For example, this happens when a Script icon is dropped on an image. In such case:

Code:
View Parameters.targetView

is a reference to the view where the script is being executed. Complementarily:

Code:
Boolean Parameters.isGlobalTarget

is true when the script is being executed in the global context. For example, this happens when a script is executed directly from the Script menu, or from the Process Explorer window.

Finally, take a look at the DrawSignatureEngine.exportParameters() method and how it is used to export a set of script parameters. Exported parameters will be available (via has()/get()) in a subsequent execution. To export a parameter from a script, the following method is used:

Code:
void Parameters.set( String id, Object value )

where id is the parameter identifier and value is the parameter value.

As for the blue triangle icon (thanks for the elegant adjective ;) ), it is quite easy, too. In DrawSignature.js, go to line # 619 and see how the newInstance_Button is created and managed. The newInstance_Button.onMousePress() event handler exports the script parameters (by calling exportParameters()) and initiates an interactive dragging procedure by calling Dialog.newInstance(). Everything else (the drag & drop procedure, icon creation, etc.) happens in an automatic fashion.

3) Let say that I modify an image using beginProcess(); / endProcess(); . How do I specify a more descriptive name so that the history browser shows this name and not just "Script" ?

You can't. Scripts are always encapsulated as Script instances. A Script instance knows where's the script source code and keeps a cryptographic digest of the executed source code, which is part of PJSR's security scheme. In a future version, Script instances will borrow their icons from the #feature-icon directive, if it exists, but this has not been implemented yet.

4) Let me see if I got this right, if I want to use an external object on an image (eg HistogramTransformation) , I create a new instance of it and then I use the executeOn ( view ) method, right ?

Correct. In PixInsight's object oriented model, process instances are executed on views, not the opposite.

5) and in the above case I DO NOT use beginProcess(); / endProcess();  right ?

Right. The beginProcess()/endProcess() protocol isn't necessary when you execute a process instance, because it is already part of the instance execution procedure.

6) How do I use Image.FFT, or as the matter of fact, any other FFT method, to produce the FFT (or the reverse) of an image ? It replies that it requires a complex number  Huh?  ? I lack the theoretical background on this, but I thought that FFT is something that you can just pop from any image (based on ImageJ's equivelant function)

The Fourier transform operates on complex images. In a complex image, each pixel is a complex number with a real and an imaginary part. Complex images cannot be manipulated directly in the GUI, but only through the development frameworks (PJSR and PCL). To obtain the FT of a real image, you first have to convert it to a complex image. The following routine can be helpful in this case:

Code:
//
// Returns the discrete Fourier transform (DFT) of the current rectangular
// selection of the specified image.
//
// If the centered argument is specified and is true, then the computed DFT
// will be symmetric with respect to the center of the transformation matrix:
// The central pixel of the DFT will correspond to the DC component of the
// transform (zero frequency), and the frequencies in the transform will grow
// radially from the central point.
//
// The returned DFT is a complex image with optimized dimensions. Note that the
// DFT can be larger than the original image selection.
//
this.fft = function ( image, centered )
{
   var C = Image.newComplexImage();
   C.allocate( image.selectedRect.width, image.selectedRect.height );
   C.apply( image );
   C.FFT( centered );
   return C;
};

Good luck and enjoy!
 
Juan Conejero said:
Good luck and enjoy!

Thank you, all of you, for the prompt answers !
I'll enjoy it for sure, lets hope it will not be fruitless :)

Cheers
 
Well, I did some programming during holidays, but considering when I reply to this topic you understand that my time was very limited :(
At least I enjoyed it :)


My first script was to create a FFT representation of an image, more or less the way ImageJ is doing it, in order to have a quick way to evaluate my bias and my camera (btw anyone else interested on this ? )

I have something working (without a -working- GUI for the moment) but I have some more questions:

Why ImageJ uses crop of powers of two (eg 1024X1024 2048x2048 and so on, the one that is most close to the image) for FFT ? I have included such a routine in my script but I can not see a real difference using it or not with FFT from PI. Is this "crop" internal part of the FFT algorithm (so I need it also) or it is related to the specific implementation (so I do not need it) ?

Reversing FFT back to an image I guess means keeping a hole complex instance with information or what I'm missing ? I did not even realized that ImageJ does this by trick, it you just save the image and then load it into it and ask for a reverse FFT it complains that you are not in the complex domain (or something similar). Reasonable if you thing of it in retrospect. Is there an easy way to keep the information needed or I'll have to create a new class inheriting image's properties ? And, what the heck a reverse FFT image is needed anyway at the first place ?

The need for a reverse FFT is related to if I'll have to add a GUI or not. With just "create FFT" it is not needed.


Why there is such a difference in the visual representation between the two tools ? The pattern is the same, but PI's looks more....rich ?
Is it all about using 8bit vs 32bit internally I guess ?
 
I'm interested on seeing your script ;)

FFT is based on an optimization for images with sizes that are powers of two. Let me explain this a little further. A DFT (Discrete Fourier Transform) is an operation that takes O(N^2) calculations. If you have a vector of length N, you have to construct a transformation matrix of N*N to change the space to fourier. Now, the clever idea behind the FFT (Fast Fourier Transform) is to divide the vector in two, taking in one of them the odd elements, and the even in the other. Thus, we have 2* N/2 elements. But, for those smaller vector, we should calculate N/2 * N/2 coefficients for the transformation matrix (once). If we repeat the procedure several times, until we cannot reduce the size of the current vector, we end with N single elements, and the reduction took Log(N) operations. Then, from N^2 we reduced it to N*Log(N)... a huge improvement.

PI uses a more sophisticated FFT algorithm that receives vectors of any length. It is not as fast as power of two only elements, but still is a very good advantage over the naive DFT calculation. Also, I may say that along the "general FFT" algorithm, is one of the fastest.
 
Inverse FFT is needed if you somehow change the image at the Fourier space. This is useful to eliminate some kinds of noise (I mean, manually changing it, by attenuating or suppressing certain coefficients), and is vastly used to optimize convolution calculations (also it is used in the wiener filter, a deconvolution).

To store the FFT result  internally you may use a complex image. I'm almost sure that JS manages them (the PCL does, so it should be inherited). Unluckyly the main GUI does not handle complex images, so you should create 2 floating point images (two windows) to show them. BTW, I suggest you to use the polar representation, i.e. show the magnitude of the complex number, and the phase angle, instead of the real/imaginary components (there should be a function that performs the calculation automatically). One almost never change the phase angle, just the amplitude.
So, a script or tool for inverting the FFT should read two images, and then associate one of them to the magnitude and the other to the phase, and then copy the values to a temporal complex image. Finally, show the real part of the FFT-1.
 
Carlos Milovic said:
I'm interested on seeing your script ;)

Here it is. Not polished, neither cleaned up. Already contains some (unused) code for the reverse FFT


Carlos Milovic said:
FFT is based on an optimization for images with sizes that are powers of two. Let me explain this a little further.

Thank you. A very detailed explanation. So I really need the routine.

Carlos Milovic said:
Inverse FFT is needed if you somehow change the image at the Fourier space. This is useful to eliminate some kinds of noise (I mean, manually changing it, by attenuating or suppressing certain coefficients), and is vastly used to optimize convolution calculations (also it is used in the wiener filter, a deconvolution).

Hmm, not something I really need to bother , not at the moment.

Carlos Milovic said:
To store the FFT result  internally you may use a complex image. I'm almost sure that JS manages them (the PCL does, so it should be inherited). Unluckyly the main GUI does not handle complex images, so you should create 2 floating point images (two windows) to show them. BTW, I suggest you to use the polar representation, i.e. show the magnitude of the complex number, and the phase angle, instead of the real/imaginary components (there should be a function that performs the calculation automatically). One almost never change the phase angle, just the amplitude.
So, a script or tool for inverting the FFT should read two images, and then associate one of them to the magnitude and the other to the phase, and then copy the values to a temporal complex image. Finally, show the real part of the FFT-1.


OK, understood, at least the overview - if I'll find some time to see the reverse in the future I'll have to figure out the details.

Thank you.
 

Attachments

  • ViewImageFFT.js
    2.9 KB · Views: 56
Hi Ioannis,

The PixInsight Class Library, and hence the PixInsight JavaScript Runtime, uses my custom adaptation of the KISS FFT library by Mark Borgerding.

PCL and PJSR support FFT transforms of vectors of any even size. However, the KISS FFT routines have been optimized for any vector length n that can be factorized as follows:

n = 2n2 * 3n3 * 4n4 * 5n5

where n2, n3, n4, and n5 are arbitrary integers >= 0, and n is an even integer. When performing FFT transforms of images, for example with the Image.FFT() method, the resulting image may have its dimensions changed to optimized lengths greater than the original ones, if they were not optimized before the FFT transform. For example, if your image is 11 pixels wide and you call its FFT() method, its width will be 12 pixels after the transform.

In addition, the PJSR only allows FFT transforms for complex images. A complex image has its pixels defined on the complex plane,  so each pixel is a complex number with a real and an imaginary part, represented by the Complex PJSR object.
 
OK, let me get this straight, I really do not need to scale the image before call the FFT function, do I ?
I can just use the original image's dimensions and this will be ok.

 
Yes, there's no need to care about image dimensions when doing Fourier transforms in PCL/PJSR. You only must be careful because after calling FFT, the resulting image may have slightly different sizes. If your code depends on specific image dimensions, you should keep track of possible changes after FFT().

Below is a little example that you and everybody interested in Fourier transforms with PJSR may find interesting. It performs a Fourier transform, then modifies the transform in some way (in this case it applies a simple notch filter in the frequency domain), and finally performs the inverse Fourier transform to obtain the resulting image.

The script generates two additional image windows with the Fourier transform and the "notched" transform. Centered DFTs are used because they greatly simplify applying filters in frequency.

I hope you'll find it useful.

Code:
#include <pjsr/UndoFlag.jsh>
#include <pjsr/ImageOp.jsh>

/*
   Returns the discrete Fourier transform (DFT) of the current rectangular
   selection of the specified image.

   If the inverse argument is specified and is true, the inverse DFT is
   computed instead of the direct DFT.

   If the centered argument is specified and is true, the DFT will be
   symmetric with respect to the center of the transformation matrix: the
   central pixel of the DFT will correspond to the DC component of the
   transform (zero frequency), and the frequencies in the transform will grow
   radially from the central point to the borders. If centered is not specified
   or is false, then the DFT will be stored in wrap around order. 

   Returns the DFT as a newly created complex image.
*/

function fft( image, inverse, centered )
{
   var F = Image.newComplexImage();
   image.clonePixelData( F );

   if ( inverse )
      F.inverseFFT( centered );
   else
      F.FFT( centered );

   return F;
}

function centeredFFT( image )
{
   return fft( image, false, true );
}

function inverseCenteredFFT( image )
{
   return fft( image, true, true );
}

/*
   Applies a high-pass or low-pass notch filter in the frequency domain.

   F is the DFT.
   size is the relative size of the notch filter.
   highPass applies a high pass notch filter instead of a low pass filter.
*/
function applyNotchFilter( F, size, highPass )
{
   size = Math.round( Math.range( size, 0, 1 ) * Math.min( F.width, F.height ) ) | 1;
   var G = Matrix.gaussianFilterBySize( size, 5, 1e-7 ).toImage();
   var dx2 = (F.width>>1) - (size>>1);
   var dy2 = (F.height>>1) - (size>>1);
   G.cropTo( dx2, dy2, dx2, dy2, 0, 0, 0 );
   if ( highPass )
      G.invert();
   F.apply( G, ImageOp_Mul );
}

function applyHighPassNotchFilter( F, size )
{
   applyNotchFilter( F, size, true );
}

function applyLowPassNotchFilter( F, size )
{
   applyNotchFilter( F, size, false );
}

/*
   Creates and shows a new image window with the magnitude of a DFT.
*/
function showMagnitudeOfDFTAsANewImageWindow( F, title )
{
   var w = new ImageWindow( F.width,
                            F.height,
                            F.numberOfChannels,
                            32,   // bitsPerSample
                            true, // floatSample
                            F.isColor,
                            title );
   with ( w.mainView )
   {
      beginProcess( UndoFlag_NoSwapFile );
      image.apply( F );
      image.rescale();
      endProcess();
   }
   
   w.bringToFront();
}

function main()
{
   var activeWindow = ImageWindow.activeWindow;
   if ( activeWindow.isNull )
      throw new Error( "No active image" );

   var activeView = activeWindow.currentView;

   var F = centeredFFT( activeView.image );

   showMagnitudeOfDFTAsANewImageWindow( F, "DFT" );

   applyHighPassNotchFilter( F, .5 );

   showMagnitudeOfDFTAsANewImageWindow( F, "notched_DFT" );

   F = inverseCenteredFFT( F );

   showMagnitudeOfDFTAsANewImageWindow( F, "notched" );
}

main();
 
Thank you Juan, indeed it makes a lot of things clear.
I'll have to find some time to review my script based on the above.
Thanks.
 
Just for fun, here is a nice example with the above notch filtering script:

http://forum-images.pixinsight.com/20101103/notch-filter-1.jpg
 
Back
Top