Author Topic: Folder watch and file auto-open for scope collimation  (Read 4600 times)

Offline sharkmelley

  • PTeam Member
  • PixInsight Addict
  • ***
  • Posts: 241
    • Mark Shelley Astrophotography
Folder watch and file auto-open for scope collimation
« on: 2014 November 22 04:12:24 »
Hi,

Is it possible in PJSR to watch a folder for any new image file that is dropped into it so it can then be opened and processed using, for example, AberrationInspector, FWHMEccentricity (or some variant).  The intention is that this would happen in a "hands free" continuous loop.

I'm looking at this in the context of collimating a scope and camera to speed up the process of making an adjustment then displaying the results.

Regards,

Mark
Takahashi Epsilon 180ED
H-alpha modified Sony A7S
http://www.markshelley.co.uk/Astronomy/

Offline Juan Conejero

  • PTeam Member
  • PixInsight Jedi Grand Master
  • ********
  • Posts: 7111
    • http://pixinsight.com/
Re: Folder watch and file auto-open for scope collimation
« Reply #1 on: 2014 November 22 09:42:09 »
Hi Mark,

Yes, this can be done with the FileWatcher and FileFind objects in PixInsight. I have adapted one of our test scripts with comments to describe how this can be done safely and efficiently:

Code: [Select]
/*
 * FileWatcher / FileFind demo script.
 */

#include <pjsr/TextAlign.jsh>
#include <pjsr/Sizer.jsh>

function FileWatcherDemoDialog( dirPath )
{
   this.__base__ = Dialog;
   this.__base__();

   //

   this.watchedDirectory = dirPath;
   this.busy = false; // flag to prevent reentrant FileWatcher events
   this.dirty = true; // flag to signal a pending FileWatcher update event

   //

   /*
    * We use a periodic timer and a 'dirty' flag to ensure that our dialog is
    * always responsive to accumulated FileWatcher events. This is necessary
    * because FileWatcher events are asynchronous. Multiple FileWatcher events
    * may happen while we are updating GUI elements (which are relatively
    * expensive operations). See also the comments in updateDirectory().
    */

   this.fileWatcher = new FileWatcher( [dirPath] );
   this.fileWatcher.dialog = this;   // necessary because FileWatcher is not a Control object
   this.fileWatcher.onDirectoryChanged = function( /*dirPath*/ )
   {
      this.dialog.dirty = true;
   };

   this.updateTimer = new Timer;
   this.updateTimer.interval = 0.5;  // timing interval in seconds
   this.updateTimer.periodic = true; // periodic or single shot timer
   this.updateTimer.dialog = this;   // necessary because Timer is not a Control object
   this.updateTimer.onTimeout = function()
   {
      if ( this.dialog.dirty )
         this.dialog.updateDirectory();
   };

   //

   this.directory_Tree = new TreeBox( this );
   this.directory_Tree.alternateRowColor = true;
   this.directory_Tree.headerVisible = false;
   this.directory_Tree.numberOfColumns = 3; // name, size, lastModified
   this.directory_Tree.rootDecoration = true;
   this.directory_Tree.uniformRowHeight = true;
   this.directory_Tree.minWidth = 600;
   this.directory_Tree.minHeight = 400;

   //

   /*
    * Returns a readable textual representation of a file size in bytes with
    * automatic units conversion.
    */
   this.fileSizeAsString = function( bytes, precision )
   {
      const kb = 1024;
      const mb = 1024 * kb;
      const gb = 1024 * mb;
      const tb = 1024 * gb;
      if ( bytes >= tb )
         return format( "%.*g TiB", precision, bytes/tb );
      if ( bytes >= gb )
         return format( "%.*g GiB", precision, bytes/gb );
      if ( bytes >= mb )
         return format( "%.*g MiB", precision, bytes/mb );
      if ( bytes >= kb )
         return format( "%.*g KiB", precision, bytes/kb );
      return format( "%lld B", bytes );
   };

   /*
    * Recursive routine to explore a directory tree. The parent TreeBox is
    * populated with the contents of the specified dirPath directory.
    */
   this.searchFiles = function( parent, dirPath )
   {
      var files = [];
      var directories = [];
      var find = new FileFind;
      if ( find.begin( dirPath + "/*" ) )
         do
         {
            if ( find.name != "." && find.name != ".." )
            {
               var item = { name:find.name, size:find.size, lastModified:find.lastModified };
               if ( find.isDirectory )
                  directories.push( item );
               else
                  files.push( item );
            }
         }
         while ( find.next() );

      var dirNode = new TreeBoxNode( parent );
      dirNode.setText( 0, File.extractNameAndSuffix( dirPath ) );
      dirNode.expanded = true;

      for ( var i = 0; i < directories.length; ++i )
         this.searchFiles( dirNode, dirPath + '/' + directories[i].name );

      for ( var i = 0; i < files.length; ++i )
      {
         var fileNode = new TreeBoxNode( dirNode );
         fileNode.setText( 0, File.extractNameAndSuffix( files[i].name ) );
         fileNode.setText( 1, this.fileSizeAsString( files[i].size, 3 ) );
         fileNode.setAlignment( 1, TextAlign_Right );
         fileNode.setText( 2, files[i].lastModified.toLocaleDateString() + ' ' + files[i].lastModified.toLocaleTimeString() );
      }
   };

   /*
    * Regenerate the directory tree.
    */
   this.updateDirectory = function()
   {
      /*
       * ### NB: We prevent reentrant events with the 'busy' property.
       * This is necessary because FileWatcher events are asynchronous, so they
       * may happen while we are regenerating our TreeBox.
       */
      if ( !this.busy )
      {
         this.busy = true;
         this.dirty = false;
         this.directory_Tree.clear();
         this.searchFiles( this.directory_Tree, this.watchedDirectory );
         this.directory_Tree.adjustColumnWidthToContents( 0 );
         this.directory_Tree.adjustColumnWidthToContents( 1 );
         this.busy = false;
      }
   };

   //

   this.ok_Button = new PushButton( this );
   this.ok_Button.text = "OK";
   this.ok_Button.onClick = function()
   {
      this.dialog.ok();
   };

   this.buttons_Sizer = new HorizontalSizer;
   this.buttons_Sizer.addStretch();
   this.buttons_Sizer.add( this.ok_Button );
   this.buttons_Sizer.addStretch();

   //

   this.sizer = new VerticalSizer;
   this.sizer.margin = 8;
   this.sizer.spacing = 8;
   this.sizer.add( this.directory_Tree );
   this.sizer.add( this.buttons_Sizer );

   this.windowTitle = "FileWatcher Demo";
   this.adjustToContents();

   //

   /*
    * We start watching the filesystem as soon as our dialog is shown.
    */
   this.onShow = function()
   {
      this.updateTimer.start();
   };

   /*
    * The safest way to stop FileWatcher and Timer events is to cancel them
    * when our dialog is hidden.
    */
   this.onHide = function()
   {
      this.updateTimer.stop();
      this.fileWatcher.clear();
   };

   //

   this.updateDirectory();
}

FileWatcherDemoDialog.prototype = new Dialog;

(new FileWatcherDemoDialog( File.systemTempDirectory )).execute();

The scripts watches the system temporary storage directory by default (/tmp on UNIX/Linux, C:\Users\<current-user>\<something>\Temp on Windows). You can change File.systemTempDirectory to other directory of your choice if you wish. Just run the script, place the script's dialog on a convenient location on the screen, and copy/delete some files on the watched directory with your system's file explorer utility.

FileWatcher events are a bit difficult to handle because they are asynchronous. Please read the code comments to learn how to work around this problem efficiently with a periodic timer and 'dirty' flags. Let me know if this is what you were looking for.

Edit: Fixed a minor error in the code example (line 133: clear the dirty flag *before* regenerating the tree box).
« Last Edit: 2014 November 22 10:08:04 by Juan Conejero »
Juan Conejero
PixInsight Development Team
http://pixinsight.com/

Offline sharkmelley

  • PTeam Member
  • PixInsight Addict
  • ***
  • Posts: 241
    • Mark Shelley Astrophotography
Re: Folder watch and file auto-open for scope collimation
« Reply #2 on: 2014 November 22 23:59:19 »
Hi Juan,

Thanks for your reply and especially for the code fragment.  I'll give it a try.

Mark
Takahashi Epsilon 180ED
H-alpha modified Sony A7S
http://www.markshelley.co.uk/Astronomy/