PreviewAggregator Script

David Serrano

Well-known member
[English below]

Cuc?!

Normalmente, cuando estamos ajustando los par?metros de un proceso, trabajamos con un preview. Esto mola porque podemos tocar y probar los par?metros r?pidamente sin tener que esperar a que el proceso trabaje sobre la imagen entera. Sin embargo, este modo de trabajo tiene un peque?o inconveniente: al concentrarnos en un preview y basar los par?metros en los resultados que vamos obteniendo con ?l, es m?s que posible que otros previews definidos pasen a mostrar un comportamiento sub?ptimo sin que nos demos cuenta. Y cuando finalmente lo hacemos, nos vemos en la tesitura de tener que buscar unos par?metros que funcionen decentemente bien para todos los previews, y probar el proceso alternativamente en todos ellos.

He probado a jugar con un ImageContainer para aliviar este problema y, si bien se puede aplicar el proceso de una sola vez a todos los previews que queramos, me encontr? con dos pegas:

- No podemos ver todos los previews al mismo tiempo, y tenemos que alternar entre ellos para ver el resultado.
- El bot?n "Undo preview / Redo preview" se queda inactivo, no se pueden ver los cambios que el proceso ha hecho sobre los previews.

Esto provoca que las ventajas de usar ImageContainer sean muy pocas, y probablemente el inconveniente de no poder usar "Undo/Redo" las supera.

He hecho, por tanto, un scriptcillo que crea una imagen nueva conteniendo todos los previews que queramos. As?, los tenemos todos en la pantalla al mismo tiempo y podemos trabajar con ellos de una sola vez. El script tambi?n genera un preview conteniendo toda esta imagen nueva, ya que de todas formas ser?a lo primero que el usuario har?a con ella.

Espero que os guste y saqu?is buen provecho de ?l!

-
-

Hiya!

Usually, when we'are adjusting the parameters of some process, we work on a preview. This is nice because we can quickly touch and test the parameters without having to wait for the process to work on the entire image. However, this methodology has a little drawback: when we concentrate on a preview and base the parameters on the results we obtain with it, it's more than probable that the result in other defined previews becomes suboptimal without us noticing. And when we finally do, we're forced to look for a set of parameters that work fine for all previews, and alternately test the process on them all.

I tried to play with an ImageContainer to alleviate this problem and, while you can apply the process to all the previews you want at once, I've found two snags:

- We can't see all the previews at the same time, and we're forced to alternate among all of them to see the result.
- The button "Undo preview / Redo preview" becomes inactive, you can't see the changes that the process has done to the previews.

This causes that the advantage of using ImageContainer is very small, and probably surpassed by the drawback of not being able to use "Undo/Redo".

Therefore, I've done a little script that creates a new image containing all the previews we want. Thus, we have them all on the screen at the same time and we can work with them at once. The script also generates a preview containing this whole new image, since it would be the first thing the user would do on it anyway.

I hope you like it and get a lot of it!

--

Aqu? va la versi?n 0.2.1. Here goes version 0.2.1.

Code:
/*
    PreviewAggregator.js v0.2.1 - Joins previews into a new image.
    Copyright (C) 2009  David Serrano.

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, version 3 of the License.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/*
   Changelog:
   0.2.1: Bugfix: working without a mask was broken.
   0.2:   If all selected previews belong to same imagen, take into account
          the mask that the image may have, as per Oriol suggestion.
   0.1:   Initial version.
*/

#include <pjsr/Sizer.jsh>
#include <pjsr/FrameStyle.jsh>
#include <pjsr/TextAlign.jsh>
#include <pjsr/StdButton.jsh>
#include <pjsr/StdIcon.jsh>
#include <pjsr/ColorSpace.jsh>
#include <pjsr/SampleType.jsh>
#include <pjsr/UndoFlag.jsh>

#feature-id PreviewAggregator
#feature-info When using previews to fine-tune the parameters of a process, \
   it's often desired to pay attention two more than a preview at the same \
   time, so the adjustments done to the parameters in order to improve the \
   result in one area of the image don't degrade other areas. This script \
   creates an image containing other images' previews, so processes can be \
   applied to it and the user can see the results in all original previews \
   at once.
// #feature-icon KSIntegration.xpm

#define VERSION "0.2.1"

function pa_engine() {
   this.imgIDs = new Array;
   this.views = new Array;
   this.mask_views = new Array;
   this.anchors = new Array;
   this.new_image_width  = 0;
   this.new_image_height = 0;

   // Populates the views array.
   this.load = function() {
      var N = this.imgIDs.length;
      this.views.length = 0;
      for (var i = 0; i < N; i++) {
         this.views.push (new View (this.imgIDs[i]));
      }
   };

   // calculates new image dimensions
   this.imagesize = function () {
      var x = Math.ceil (Math.sqrt (this.views.length));
      var y = Math.ceil (this.views.length / x);

      var max_w = 0;
      var max_h = 0;
      for (var i = 0; i < this.views.length; i++) {
         var w = this.views[i].image.width;
         var h = this.views[i].image.height;
         if (w > max_w) { max_w = w; }
         if (h > max_h) { max_h = h; }
      }

      this.new_image_width  = x * max_w;
      this.new_image_height = y * max_h;
      //console.writeln ("x (",x,") y (",y,") max_w (",max_w,") max_h (",max_h,") width (",this.new_image_width,") height (",this.new_image_height,")");

      // now build array of anchors using x, y, max_w and max_h
      var anchor_x = -1;
      var anchor_y = 0;
      for (var i = 0; i < this.views.length; i++) {
         anchor_x++;
         if (anchor_x >= x) {   // technically '==' works, but let's cover us from any unexpected event
            anchor_x = 0;
            anchor_y++;
         }
         //console.writeln ("about to push point at x (",anchor_x*max_w,") y (",anchor_y*max_h,") for view (",i,")");
         this.anchors.push (new Point (anchor_x * max_w, anchor_y * max_h));
      }
   }

   this.parent_images = function() {
      var parent_images = {};
      for (var i = 0; i < this.imgIDs.length; i++) {
         var end_idx = this.imgIDs[i].indexOf ("->");
         var img_id = this.imgIDs[i].substr (0, end_idx);
         parent_images[img_id] = 1;
      }

      // this can probably be done by some sort of parent_images.length,
      // parent_images.keys or something like that, but didn't put much
      // effort in figuring out exactly how. Sorry for this, but...
      var n = 0;
      for (k in parent_images) { n++; }

      return n;
   }

   this.create_image = function() {
      var do_mask = 0;
      var orig_mask_win;
      if (this.views[0].window.mask.mainView.fullId) { console.writeln ("mask true (",this.views[0].window.mask.mainView.fullId,")"); }
      if (
         1 == this.parent_images() &&
         this.views[0].window.mask.mainView.fullId
      ) {
         do_mask = 1;
         orig_mask_win = this.views[0].window.mask;
      }

      this.imagesize();
      var window = new ImageWindow (
         this.new_image_width,
         this.new_image_height,
         this.views[0].image.numberOfChannels,
         this.views[0].image.bitsPerSample,
         this.views[0].image.sampleType != SampleType_Integer,
         this.views[0].image.colorSpace != ColorSpace_Gray,
         "Aggregated"
      );

      var mask_window;
      var mask_view;
      if (do_mask) {
         //console.writeln ("should work with a mask");
         // replicate previews in the mask
         for (var i = 0; i < this.views.length; i++) {
            var r = this.views[i].window.previewRect (this.views[i]);
            //console.writeln ("going to replicate preview (",i,") x (",r.x0,") y (",r.y0,") w (",r.width,") h (",r.height,")");
            this.mask_views[i] = orig_mask_win.createPreview (r, this.views[i].id);
         }

         // create another identical image for the mask
         new_mask_win = new ImageWindow (
            this.new_image_width,
            this.new_image_height,
            this.views[0].image.numberOfChannels,
            this.views[0].image.bitsPerSample,
            this.views[0].image.sampleType != SampleType_Integer,
            this.views[0].image.colorSpace != ColorSpace_Gray,
            "Aggregated_mask"
         );

         mask_view = new_mask_win.mainView;
         mask_view.beginProcess (UndoFlag_NoSwapFile);
      }

      var view = window.mainView;
      view.beginProcess (UndoFlag_NoSwapFile);
         view.image.fill (0);
         if (do_mask) { mask_view.image.fill (0); }

         // paste previews in their proper place
         for (var i = 0; i < this.views.length; i++) {
            //console.writeln ("applying image (",i,")");
            view.image.selectedPoint = this.anchors[i];
            view.image.apply (this.views[i].image);

            if (do_mask) {
               mask_view.image.selectedPoint = this.anchors[i];
               mask_view.image.apply (this.mask_views[i].image);
            }
         }
      view.endProcess();
      if (do_mask) {
         mask_view.endProcess();

         // delete created previews
         for (var i = 0; i < this.mask_views.length; i++) {
            orig_mask_win.deletePreview (this.mask_views[i]);
         }

         // assign mask
         window.mask = new_mask_win;
      }

      // usually users will create a preview on the new image. Anticipate it
      window.createPreview (0, 0, this.new_image_width, this.new_image_height);

      // TODO: select the new preview

      window.show();
      if (do_mask) { new_mask_win.show(); }
   }
}
var engine = new pa_engine();

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

   // ----- HELP LABEL

   this.helpLabel = new Label (this);
   this.helpLabel.frameStyle = FrameStyle_Box;
   this.helpLabel.margin = 4;
   this.helpLabel.wordWrapping = true;
   this.helpLabel.useRichText = true;
   this.helpLabel.text = "<b>PreviewAggregator v"+VERSION+"</b> - A script to create " +
      "a new image based on the contents of other images' previews. " +
      "Select the previews to be aggregated and click 'Ok'.";

   // ----- LIST OF TARGETS

   this.target_List = new TreeBox (this);
   this.target_List.setMinSize (400, 200);
   this.target_List.font = new Font ("monospace", 10); // best to show tabulated data
   this.target_List.numberOfColumns = 2;
   this.target_List.headerVisible = true;
   this.target_List.headerSorting = true;
   this.target_List.setHeaderText (0, "Views");
   this.target_List.setHeaderText (1, "Dimensions");
   this.target_List.setHeaderAlignment (0, Align_Left);
   this.target_List.setHeaderAlignment (1, Align_Left);

   var active_id = ImageWindow.activeWindow.mainView.fullId;
   // Node creation helper
   this.addViewNode = function (parent, view) {
      var node = new TreeBoxNode (parent);
      node.checkable = true;
      var id = view.fullId;
      // automatically select all previews in the active window
      var arrow = id.indexOf ("->");
      var parent_id = id.substring (0, arrow);
      if (active_id == parent_id) {
         node.checked = true;
      } else {
         node.checked = false;
      }

      node.setText (0, id);

      var image = view.image;
      var metadata = format ("%5d x %5d x %d", image.width, image.height, image.numberOfChannels);
      node.setText (1, metadata);

      return node;
   }

   // build the view tree structure
   var windows = ImageWindow.windows;
   for (var i = 0; i < windows.length; ++i) {
      var previews = windows[i].previews;
      for (var j = 0; j < previews.length; ++j) {
         this.addViewNode (this.target_List, previews[j]);
      }
   }
   this.target_List.sort();

   // Ensure that all columns are initially visible
   this.target_List.adjustColumnWidthToContents (0);
   this.target_List.adjustColumnWidthToContents (1);

   this.target_GroupBox = new GroupBox (this);
   this.target_GroupBox.title = "Previews to aggregate";
   this.target_GroupBox.sizer = new VerticalSizer;
   this.target_GroupBox.sizer.margin = 4;
   this.target_GroupBox.sizer.spacing = 4;
   this.target_GroupBox.sizer.add (this.target_List, 100);

   // ----- BUTTONS

   this.ok_Button = new PushButton (this);
   this.ok_Button.text = " OK ";
   // transfer the names of the selected images to the engine
   this.ok_Button.onClick = function() {
      for (n = 0; n < this.dialog.target_List.numberOfChildren; n++)
         if (this.dialog.target_List.child (n).checked)
            engine.imgIDs.push (this.dialog.target_List.child (n).text (0));
      this.dialog.ok();
   };

   this.cancel_Button = new PushButton (this);
   this.cancel_Button.text = " Cancel ";
   this.cancel_Button.onClick = function() {
      this.dialog.cancel();
   };

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

   // ----- PACK EVERYTHING

   this.sizer = new VerticalSizer;
   this.sizer.margin = 6;
   this.sizer.spacing = 6;
   this.sizer.add (this.helpLabel);
   this.sizer.addSpacing (4);
   this.sizer.add (this.target_GroupBox, 100);
   this.sizer.add (this.buttons_Sizer);

   this.windowTitle = "PreviewAggregator v" + VERSION;
   this.adjustToContents();
}
pa_dialog.prototype = new Dialog;

function main() {
   var dialog = new pa_dialog();

   for (;;) {
      if (!dialog.execute()) {
         break;
      }

      engine.load();
      engine.create_image();

      // Quit after successful execution.
      break;
   }
}
main();
 
Pues David, muchas gracias por compartirlo.

En estos momentos este script me va a ser muy ?til. Por ejemplo, ahora estoy procesando im?genes de gran campo. Siempre que defines previsualizaciones sobre los objetos interesantes suelen estar dispersados por todo el campo:

arx1235426923e.jpg


Una vez ejecutado el script PreviewAggregator se obtiene una imagen de un tama?o mucho menor y con todas las nebulas juntas:

opt1235427040l.jpg


Muy mol?n, si. :D

Saludos.
 
Hi,

I was playing a little with the sctipt, IMHO there is something to improve, if you want to use it in conjunction with a mask, the script don't build an equivalent mask for the new image. An option is to clone all the previews to the mask and use the script again in order to have the equivalent mask,

rgh1235428230d.png


why not implement this step in the script also?
 
[quote author="Juan Conejero"]Can I include this one in the next version?[/quote]

Please do! :)


[quote author="OriolLehmkuhl"]if you want to use it in conjunction with a mask, the script don't build an equivalent mask for the new image. An option is to clone all the previews to the mask and use the script again in order to have the equivalent mask[/quote]

Hmm, quite right. In fact, while developing and testing the script, I found this behaviour myself, although didn't regard it as a problem and kept making duplicate masks as a result :). Working on it right now ;), this will only work when all selected previews belong to the same image.

On a side note, it's best that all previews have the same size, so the resulting image doesn't have any area with a black background. To achieve this, use the "Clone preview" button; it's in the Preview toolbar.

Thanks C. Sonnenstein for publishing some screen shots, I'm too lazy to do that ;).
 
[quote author="David Serrano"]
On a side note, it's best that all previews have the same size, so the resulting image doesn't have any area with a black background. To achieve this, use the "Clone preview" button; it's in the Preview toolbar.[/quote]

Good! :wink: Thanks for trick :D


Captura.png


It's great to view the effect of our prossesing on the galaxies all togather and quick :p

regards,
 
He actualizado el primer mensaje con la versi?n 0.2. Por si acaso la voy a marcar como beta, que es tarde y no me he entretenido mucho en usarla y probarla.

Just updated the OP including version 0.2. I'd like to tag it as beta just in case, since it's late now and I didn't spend much time in using and testing it.
 
Vaya, ya veo que todo el mundo usa esto con m?scaras... lo acabo de probar sin m?scara, una semana despu?s de haber publicado la 0.2, y me he dado cuenta de que estaba roto ;). En fins, he actualizado con la 0.2.1, con este error corregido.
 
Hola David

Muy buen trabajo. S?lo quer?a avisarte de que ya tengo PreviewAggregator incluido en la 1.5 que estoy preparando ;)
 
What a great tool. Thanks for all the work you put into this. Lately the planning and building of PixInsight by Juan has, like the flowers of Spring, started to really bloom with these great scripts showing up.
 
Back
Top