Author Topic: [PJSR] Window 'no more events'  (Read 4655 times)

Offline bitli

  • PTeam Member
  • PixInsight Guru
  • ****
  • Posts: 513
[PJSR] Window 'no more events'
« on: 2015 February 07 01:55:19 »
Is there a callback in PJSR when there is 'no more pending window events event?. 

In my script. the user can make many changes on the model, which should trigger complex updates to keep the UI synchronized (like re-reading fits files).  Although this works fine when the processing time is not too long, for complex changes this can make the UI unresponsive.  With such an event (or a similar trick with the timer or something, there would be an automatic regulation of the response time in case of many changes done by the user - the complex update will tend to be batched together.
-- bitli

Offline Juan Conejero

  • PTeam Member
  • PixInsight Jedi Grand Master
  • ********
  • Posts: 7111
    • http://pixinsight.com/
Re: [PJSR] Window 'no more events'
« Reply #1 on: 2015 February 07 05:02:50 »
Quote
Is there a callback in PJSR when there is 'no more pending window events event?.

There is no such callback, mainly because it cannot be implemented (in a realistic way).

There are much better solutions for the problem that you are facing. One of them is asynchronous event handling. This has been implemented, for example, in Georg's CanonBandingReduction script.

Consider the following script:

Code: [Select]
#include <pjsr/NumericControl.jsh>
#include <pjsr/FrameStyle.jsh>

/*
 * Defines the rate of calls to processEvents(). This value is somewhat
 * critical:
 *
 * - Too low of an event check rate will slow down the process too much.
 *
 * - Too large of an event rate won't consume GUI events as quickly as needed
 *   to keep the GUI responsive.
 */
#define EVENT_CHECK_RATE   256

function AsynchronousPolarTransformDialog()
{
   this.__base__ = Dialog;
   this.__base__();

   this.startingAngle = 0;

   this.image = new Image;

   this.abort = false;
   this.busy = false;
   this.terminate = false;

   /*
    * Transformation of an image from Cartesian to polar coordinates.
    * Code adapted from the PolarCoordinates example script.
    */
   this.cartesianToPolar = function( image )
   {
      // Gather working parameters
      let n = image.numberOfChannels;
      let w = image.width;
      let h = image.height;
      let w2 = w/2;
      let h2 = h/2;
      let r0 = Math.sqrt( w2*w2 + h2*h2 ); // semi-diagonal

      // Total work
      let N = n*h;

      // Create a working image to store one channel of the target image
      let tmp = new Image( w, h, 1 );

      // Reset the rectangular selection to the whole image boundaries
      image.resetRectSelection();

      // For each channel
      for ( let c = 0, status = 0; c < n; ++c )
      {
         tmp.fill( 0 ); // initialize working data with zeros

         // Use a sample iterator for faster pixel access.
         tmp.initSampleIterator();

         // For each row
         for ( let i = 0; i < h; ++i, ++status )
         {
            // Polar angle for the current row
            let theta =  this.startingAngle + 360*i/h;
            if ( theta >= 360 )
               theta -= 360;
            theta = Math.rad( theta );

            // Sine and cosine of the current polar angle
            let stheta = Math.sin( theta );
            let ctheta = Math.cos( theta );

            // For each column
            for ( let j = 0, e = 0; j < w; ++j, tmp.nextSample() )
            {
               // Radial distance for the current column
               let r = r0*j/w;

               // Horizontal coordinate on the source image
               let x = w2 + r*ctheta;

               if ( x >= 0 && x < w )
               {
                  // Vertical coordinate on the source image
                  let y = h2 - r*stheta;

                  // Copy the source (interpolated) pixel sample to the
                  // polar-transformed location on the working image.
                  if ( y >= 0 && y < h )
                     tmp.setSampleValue( image.interpolate( x, y, c ) );
               }

               if ( ++e == EVENT_CHECK_RATE )
               {
                  // Process pending GUI events
                  processEvents();

                  // Check for abort condition
                  if ( this.abort )
                     break;

                  e = 0;
               }
            }

            // Check for abort condition
            if ( this.abort )
               break;

            this.infoLabel.text = format( "Computing: %d%%", Math.round( 100.0*status/N ) );
         }

         // Check for abort condition
         if ( this.abort )
            break;

         // Copy our working data to the channel c of image
         image.selectedChannel = c;
         image.apply( tmp );
      }

      image.resetChannelSelection();

      // Deallocate our working image
      tmp.free();
   };

   /*
    * Asynchronous generation routine.
    */
   this.generate = function()
   {
      // If we are already generating data, request job abortion and return.
      if ( this.busy )
      {
         this.abort = true;
         return;
      }

      // Start of job
      this.busy = true;

      do
      {
         this.abort = false;
         this.image.assign( ImageWindow.activeWindow.currentView.image );
         this.cartesianToPolar( this.image );
      }
      while ( this.abort && !this.terminate );

      // End of job
      this.busy = false;

      if ( !this.terminate )
      {
         this.infoLabel.text = "Ready.";
         this.renderControl.update();
      }
   };

   this.renderControl = new Control( this );
   this.renderControl.setMinSize( 400, 400 );

   this.renderControl.onPaint = function()
   {
      var G = new Graphics( this );
      if ( this.parent.busy )
         G.eraseRect( this.boundsRect );
      else
         G.drawScaledBitmap( this.boundsRect, this.parent.image.render() );
      G.end();
   };

   this.angleControl = new NumericControl( this );
   this.angleControl.real = false;
   this.angleControl.setRange( 0, 360 );
   this.angleControl.label.text = "Starting angle:";
   this.angleControl.slider.setRange( 0, 360 );
   this.angleControl.slider.minWidth = 400;
   this.angleControl.setValue( this.startingAngle );
   this.angleControl.toolTip = "<p>Starting angle of the Cartesian to Polar transform.</p>";

   this.angleControl.onValueUpdated = function( value )
   {
      this.parent.startingAngle = value;
      this.parent.generate();
   };

   this.infoLabel = new Label( this );
   this.infoLabel.frameStyle = FrameStyle_Box;
   this.infoLabel.margin = 4;
   this.infoLabel.text = "";

   this.sizer = new VerticalSizer;
   this.sizer.margin = 6;
   this.sizer.spacing = 6;
   this.sizer.add( this.renderControl, 100 );
   this.sizer.add( this.angleControl );
   this.sizer.add( this.infoLabel );

   // Ensure first-time generation
   this.onGetFocus = function()
   {
      if ( this.image.isEmpty ) // but only for the first time
         this.generate();
   }

   // Ensure unconditional job abortion upon dialog termination
   this.onHide = function()
   {
      this.terminate = this.abort = true;
   }

   this.windowTitle = "Asynchronous PolarTransform";
   this.adjustToContents();
}

AsynchronousPolarTransformDialog.prototype = new Dialog;

function main()
{
   console.hide();
   var dialog = new AsynchronousPolarTransformDialog;
   dialog.execute();
}

if ( ImageWindow.activeWindow.isNull )
   throw new Error( "I need an image window to work." );

main();

This script generates a polar transform, which is a relatively computing-intensive image transformation. You can move the slider to change the initial polar angle of the transform, or change the value in degrees directly, while the transform is being computed. Each time you change the angle, the current transform is aborted and a new one starts.

Let me know if this helps.
Juan Conejero
PixInsight Development Team
http://pixinsight.com/