How to use Image.forEachPixel( Function f, ...) or Image.forEachSample(Function f, ...)

jmurphy

Well-known member
I am currently making some improvements to my PhotometricMosaic script, including improving its efficiency.
I need to apply a simple formula to a rectangular area within an image. Currently my code looks something like:

1587814088186.png


Is there a better way to do this? I see that Image has forEachPixel and forEachSample methods, but I cannot figure out how to use them. I would be grateful for any help or suggestions.
 
I never do it it this way however, correct me if I am wrong, but the code above gives the pixels for a complete row of the image not a rectangular area within the image
 
Hi Dave, thanks for your reply.

The 'row' variable is of type 'Rect'.
In this particular example, I am indeed passing a rectangle of height 1 (a row), but in the general case it would be a normal rectangle.
I have assumed that using an array of samples to read and write to the image is more efficient than reading each pixel individually.
I am hoping that it is possible to go one step further and give the PixInsight Core a function to apply to each pixel.
 
The 'row' variable is of type 'Rect'.
OK gotcha

I must admit I have only processed all pixels in an image not just a portion of them. I have had a quick look and nowhere is either forEachPixel or forEachSample methods used, I will keep looking, but I think we need Juan to gives us the parameters for these.

EDIT
Have a look at this thread

void forEachSample( float* samples, int width, int height );

and of course if you hover over the method in the Object Explorer it gives you parameters......forgot about that LOL
 
Last edited:
There are basically four ways to access individual pixel samples in the current JavaScript runtime:

1. The Image.sample() and Image.setSample() methods.
2. Image sample and pixel iterators.
3. The Image.forEachSample() and Image.forEachMutableSample() methods.
4. The Image.getSamples() and Image.setSamples() methods.

For the last method we can use either Array objects or typed array objects (Float32Array, Uint16Array, etc).

Here is a little script that shows the existing performance differences between these methods and techniques.

JavaScript:
/*
 * Pixel access benchmarks
 */

#include <pjsr/UndoFlag.jsh>

function test_setSample( image )
{
   for ( let y = 0; y < image.height; ++y )
      for ( let x = 0; x < image.width; ++x )
         image.setSample( 1 - image.sample( x, y ), x, y );
}

function test_sampleIterator( image )
{
   image.initSampleIterator();
   do
      image.setSampleValue( 1 - image.sampleValue() );
   while ( image.nextSample() );
}

function test_forEachSample( image )
{
   image.forEachSample( (x, y, c, v) => { image.setSample( 1 - v, x, y, c ); } );
}

function test_forEachMutableSample( image )
{
   image.forEachMutableSample( (x, y, c, v) => 1 - v );
}

function test_sampleArray( image )
{
   let A = [];
   image.getSamples( A );
   for ( let i = 0; i < A.length; ++i )
      A[i] = 1 - A[i];
   image.setSamples( A );
}

function test_sampleTypedArray( image )
{
   let A = new Float32Array( image.numberOfPixels );
   image.getSamples( A );
   for ( let i = 0; i < A.length; ++i )
      A[i] = 1 - A[i];
   image.setSamples( A );
}

function main()
{
   let window = new ImageWindow( 2048,        /*width*/
                                 2048,        /*height*/
                                 1,           /*numberOfChannels*/
                                 32,          /*bitsPerSample*/
                                 true,        /*floatSample*/
                                 false,       /*color*/
                                 "test_image" /*id*/ );

   window.mainView.beginProcess( UndoFlag_NoSwapFile );
   let image = window.mainView.image;

   let T = new ElapsedTime;

   T.reset();
   test_setSample( image );
   let t1 = T.value;

   T.reset();
   test_sampleIterator( image );
   let t2 = T.value;

   T.reset();
   test_forEachSample( image );
   let t3 = T.value;

   T.reset();
   test_forEachMutableSample( image );
   let t4 = T.value;

   T.reset();
   test_sampleArray( image );
   let t5 = T.value;

   T.reset();
   test_sampleTypedArray( image );
   let t6 = T.value;

   window.mainView.endProcess();
   window.forceClose();

   let t0 = Math.min( t1, t2, t3, t4, t5, t6 );

   console.writeln( "<end><cbr><br>test                 | time       | rel.t" );
   console.writeln(               "---------------------+------------+------" );
   console.writeln( format( "setSample            | %10s | %5.2f", ElapsedTime.toString( t1 ), t1/t0 ) );
   console.writeln( format( "sampleIterator       | %10s | %5.2f", ElapsedTime.toString( t2 ), t2/t0 ) );
   console.writeln( format( "forEachSample        | %10s | %5.2f", ElapsedTime.toString( t3 ), t3/t0 ) );
   console.writeln( format( "forEachMutableSample | %10s | %5.2f", ElapsedTime.toString( t4 ), t4/t0 ) );
   console.writeln( format( "sampleArray          | %10s | %5.2f", ElapsedTime.toString( t5 ), t5/t0 ) );
   console.writeln( format( "sampleTypedArray     | %10s | %5.2f", ElapsedTime.toString( t6 ), t6/t0 ) );
}

main();

Here is a sample output from this script on the machine where I'm writing this:

Code:
test                 | time       | rel.t
---------------------+------------+------
setSample            | 781.405 ms | 10.68
sampleIterator       | 121.109 ms |  1.65
forEachSample        | 506.530 ms |  6.92
forEachMutableSample | 423.575 ms |  5.79
sampleArray          | 473.596 ms |  6.47
sampleTypedArray     |  73.191 ms |  1.00

The script inverts all the pixels of a monochrome, 32-bit floating point 4 Mpx image, using the four methods enumerated above, plus the Float32Array variant of the fourth method. Then it writes a report with absolute and relative execution times (relative to the fastest routine).

As you can see, the least efficient way to read/write pixels is using the sample() and setSample() methods. Although these methods offer an intuitive way to process pixels by varying coordinates and reading/writing individual pixel samples separately, their execution times are one order of magnitude longer than the fastest techniques available. The reason for this bad performance is the amount of required interactions between JavaScript code and the underlying native C++ implementation, which prevent an optimal JIT compilation from JavaScript to machine code.

The most efficient technique is using the getSamples() and setSamples() methods with typed arrays (Float32Array in this case). This happens because typed arrays are extremely efficient, both for JIT optimization on the JavaScript side, and for direct manipulation in the native C++ implementation. Of course, the drawback of these methods is their additional memory space requirements. Basically, you need to work on a temporary duplicate of the image region that you want to process. This usually poses no practical problems, unless your script has to work with huge images under strict memory space constraints.

Using getSamples()/setSamples() with Array objects is about six times slower than the same methods used with typed arrays. This happens because Array objects are dynamic and potentially non-homogeneous containers, as seen from the native C++ implementation, requiring a higher degree of interaction with the JavaScript engine. This prevents linear access to array data, which in turn disables almost all code optimizations that make typed arrays so fast.

An excellent alternative is using sample and pixel iterators. This technique is only about a 65% less efficient than using typed arrays, but has no additional space requirements.

Finally, execution times using forEachSample() are about the same as those of getSamples()/setSamples() with Array objects, requiring no additional space.

So the bottom line is:

- For the fastest possible implementation, use getSamples()/setSamples() with typed arrays.

- For relatively good performance without additional space requirements, use image iterators.

- Avoid using the sample() and setSample() methods in production code, except for very simple cases or small images. They are great for demonstration purposes and can facilitate debugging in some cases, though.
 
Last edited:
Hi Juan Conejero
Thanks for your very clear and detailed answer.

I am currently making many improvements to my PhotometricMosaic script, and I will now be able to make even greater performance improvements.

Thanks
John Murphy
 
Hi John,

Glad to know that my answer is useful. I've just updated it, along with the benchmark script, to include the Image.forEachMutableSample() method, which I forgot before. It doesn't change any conclusion though.
 
Back
Top