PixInsight Forum (historical)

Software Development => New Scripts and Modules => Topic started by: David Serrano on 2009 April 08 02:14:11

Title: 3D star profile / Perfil estelar en 3D (BETA)
Post by: David Serrano on 2009 April 08 02:14:11
(http://pteam.pixinsight.com/david.serrano/pix/_3dplot.png)

Click on a small image (not beyond 100x100) to make it active and run the script. Previews are OK.

Hacer clic en una imagen pequeña (no mucho más de 100x100) para hacerla activa, y ejecutar el script. Se pueden usar previews.

Code: [Select]
#include <pjsr/UndoFlag.jsh>
#include <pjsr/FillRule.jsh>

var xform_cache = [];
function xform_coords (x, y, z) {
    var k = format ('%05d%05d%6.5f', x, y, z);
    if (xform_cache[k]) { return xform_cache[k]; }

    var angle = 10/180*Math.PI;
    var alt = 60/180*Math.PI;
    var new_x = x; var origin_x = 14;
    var new_y = y; var origin_y = 14;
    var scale = 7;

    // rotation
    var mod = Math.sqrt (
        Math.pow (Math.abs (origin_x - x), 2) +
        Math.pow (Math.abs (origin_y - y), 2)
    );
    var cur_angle = Math.atan2 (origin_y - new_y, new_x - origin_x);
    //console.writeln ('got x (',x,') y (',y,') mod (',mod,') cur_angle (',(cur_angle/Math.PI*180),')');
    new_x = origin_x + mod * Math.cos(cur_angle - angle);
    new_y = origin_y - mod * Math.sin(cur_angle - angle);
    //console.writeln ('new x (',new_x,') y (',new_y,')');
    //console.writeln ('--');

    // scaling and perspective
    new_x *= scale;
    new_y *= scale / (Math.PI/2 / alt);  // alt == 90/2?  perspective = scale/2
   
    // translation
    new_x += 180;
    new_y += 150;

    // z
    new_y -= scale * scale * scale * z * Math.cos (alt);

    return xform_cache[k] = new Point (new_x, new_y);
}

var pixel_cache = [];
function getpixel (i, x, y) {
    var k = format ('%05d%05d', x, y);
    if (pixel_cache[k]) { return pixel_cache[k]; }

    return pixel_cache[k] = Math.avg (
        i.sample (x, y, 0),
        i.sample (x, y, 1),
        i.sample (x, y, 2)
    );
}

var src = ImageWindow.activeWindow.currentView.image;
var w = new ImageWindow (12*src.width, 12*src.height, 3, 8, false, true, '_3dplot');
var v = w.currentView;
var i = v.image;

// setup
var white_pen   = new Pen (0xffffffff);
var black_pen   = new Pen (0xff000000);
var red_pen     = new Pen (0xffff0000);
var white_brush = new Brush (0xffffffff);
var black_brush = new Brush (0xff000000);
var red_brush   = new Brush (0xffff0000);

var bmp = new Bitmap (i.width, i.height);
var g = new Graphics (bmp);
v.beginProcess (UndoFlag_PixelData);
    bmp.fill (0xff008000);
    g.pen = red_pen; g.brush = white_brush;
       
    for (var n = 0; n < src.height-1; n++) {
        for (var m = 0; m < src.width-1; m++) {
            var p1 = xform_coords (m,   n,   getpixel (src, m,   n  ));
            var p2 = xform_coords (m+1, n,   getpixel (src, m+1, n  ));
            var p3 = xform_coords (m+1, n+1, getpixel (src, m+1, n+1));
            var p4 = xform_coords (m,   n+1, getpixel (src, m,   n+1));
            g.drawPolygon (new Array (p1, p2, p3, p4));
        }
    }
    i.blend (bmp);
    g.end();
v.endProcess();
w.show();


I didn't study in the university and this is the first time I try this kind of things, so I'd like to apologize to all those who read the code and take any offense at it ;).

No he ido a la universidad y es la primera vez que intento hacer esta clase de cosas, así pues pido disculpas a aquellos que lean el código y se vean profundamente ofendidos por él ;).
Title: 3D star profile / Perfil estelar en 3D (BETA)
Post by: Juan Conejero on 2009 April 08 08:33:22
Bravo David, absolutely wonderful work :D

It is amazing how a 3D look at the image can show so many things, so conspicuously --things that are barely visible on the real 2D image. This script can have lots of exciting applications. For example, how does a light-pollution gradient look like? Or how a bout a before/after comparison for a denoising routine such as ACDNR or GREYCstoration?

I love it :) And *of course* I'd like to include it for 1.5...

Quote
I didn't study in the university and this is the first time I try this kind of things, so I'd like to apologize to all those who read the code and take any offense at it


Nor did I. So no offense at all. And indeed not bad for your first time ;)
Title: 3D star profile / Perfil estelar en 3D (BETA)
Post by: Juan Conejero on 2009 April 08 11:04:31
Hi again,

Here's a quick and interesting test with David's 3D plot script.

Original image
(http://forum-images.pixinsight.com/legacy/3DPlot/test1.png)

The image after a strong application of a wavelet-based noise reduction routine (one of the new tools in version 1.5):
(http://forum-images.pixinsight.com/legacy/3DPlot/test2.png)

The plots (no need to specify which is which :) ):

(http://forum-images.pixinsight.com/legacy/3DPlot/_3dplot1.png)

(http://forum-images.pixinsight.com/legacy/3DPlot/_3dplot2.png)

This is really interesting and useful. For example, we can evaluate the features of the new noise reduction algorithm and the suitability of a particular application much better on the 3D graphs. Great!  (thanks David!) 8)
Title: 3D star profile / Perfil estelar en 3D (BETA)
Post by: Andres.Pozo on 2009 April 09 03:22:56
Hi,

I would replace this code:
Code: [Select]

var mod = Math.sqrt (
        Math.pow (Math.abs (origin_x - x), 2) +
        Math.pow (Math.abs (origin_y - y), 2)
    );

with
Code: [Select]

var mod = Math.sqrt ((origin_x - x)*(origin_x - x)+(origin_y - y)*(origin_y - y));


This substitution should make the script faster.

Also, the index in xform_cache could be made using  only the (x,y) coordinates. The z coordinate is redundant. This way the string of the index can be shorter and perhaps the searches faster.

I hope this helps.

Andrés.
Title: 3D star profile / Perfil estelar en 3D (BETA)
Post by: Andres.Pozo on 2009 April 09 06:06:53
Hi again,

I have been working a bit on this script and I have written a new version with this improvements:
Code: [Select]

#include <pjsr/UndoFlag.jsh>
#include <pjsr/FillRule.jsh>


function xform_coords (x, y, z) {
    var angle = 10/180*Math.PI;
    var alt = 60/180*Math.PI;
    var scaleXY = 7;
    var scaleZ = 350;

    // rotation
    var mod = Math.sqrt(x*x+y*y);
    var cur_angle = Math.atan2 (-y, x);
    var new_x = mod * Math.cos(cur_angle - angle);
    var new_y = -mod * Math.sin(cur_angle - angle);

    // scaling and perspective
    new_x *= scaleXY;
    new_y *= scaleXY / (Math.PI/2 / alt);  // alt == 90/2?  perspective = scale/2
   
    // z
    new_y -= scaleZ * z * Math.cos (alt);
    return new Point (new_x, new_y);
}

// This function computes the rectangle covered by the 3D image
function CalculateLimits(image)
{
   // The median of the source image is the level of the "floor" of the 3D image
   var stats = new ImageStatistics;
   stats.generate(image);
   var median=stats.median;

   var p1=xform_coords(0,0,median);
   var p2=xform_coords(image.width-1,0,median);
   var p3=xform_coords(0,image.height-1,median);
   var p4=xform_coords(image.width-1,image.height-1,median);

   var r=new Rect();
   r.left=Math.min(Math.min(p1.x,p2.x),Math.min(p3.x,p4.x));
   r.top=Math.min(Math.min(p1.y,p2.y),Math.min(p3.y,p4.y));
   r.right=Math.max(Math.max(p1.x,p2.x),Math.max(p3.x,p4.x));
   r.bottom=Math.max(Math.max(p1.y,p2.y),Math.max(p3.y,p4.y));
   return r;
}

// Extract the luminance of the source image
var src;
if(ImageWindow.activeWindow.currentView.image.numberOfChannels!=1)
{
   src =new Image();
   ImageWindow.activeWindow.currentView.image.extractLuminance(src);
} else {
   src = ImageWindow.activeWindow.currentView.image;
}

// Compute the rectangle of the 3D image in order to size the destination image
var limits=CalculateLimits(src);

var w = new ImageWindow (Math.round(limits.width), Math.round(limits.height), 3, 8, false, true, '_3dplot');
var v = w.currentView;
var i = v.image;

// Start of the process
var startts = new Date;
var bmp = new Bitmap (i.width, i.height);
var g = new Graphics (bmp);
v.beginProcess (UndoFlag_PixelData);
   bmp.fill (0xff606060);
   g.pen =  new Pen (0xff000000);
   g.brush = new Brush (0xffffffff);

   // Calculates the coordinates of each pixel applying the perspective transformation
   var xform=[];
   for (var n = 0; n < src.height; n++) {
      xform[n]=[];
      for (var m = 0; m < src.width; m++) {
         xform[n][m]=xform_coords (m, n, src.sample(m, n));
         xform[n][m].x-=limits.left;
         xform[n][m].y-=limits.top;
      }
   }

   // Paint the polygons from back to front
   for (var n = 0; n < src.height-1; n++) {
      for (var m = 0; m < src.width-1; m++) {
         var p1 = ((xform[n])[m]);
         var p2 = ((xform[n])[m+1]);
         var p3 = ((xform[n+1])[m+1]);
         var p4 = ((xform[n+1])[m]);
         g.drawPolygon (new Array (p1, p2, p3, p4));
      }
   }
   g.end();
   i.blend (bmp);
v.endProcess();
w.show();
var endts = new Date;
console.writeln(format ("<br />3D view: %.2f s",(endts.getTime() - startts.getTime()) / 1000));
Title: 3D star profile / Perfil estelar en 3D (BETA)
Post by: David Serrano on 2009 April 09 13:37:25
Juan, that's a wonderful NR algorithm... the background is completely flattened as the 3D plot reveals ;). Of course, it will be a honour to see this included in PixInsight 1.5. My question about receiving events was related to this script. The original idea was to monitor an image and update the plot whenever it changed.

Andrés, your improvements are awesome. Thank you very much for contributing them! :).

I'm feeling too lazy right now to implement a couple of remaining things:

- Confirmation if the image is too large. It's too easy to define a preview and run the script without activating the preview, thus running it on the entire image.
- User interface.
Title: 3D star profile / Perfil estelar en 3D (BETA)
Post by: C. Sonnenstein on 2009 April 10 01:15:10
Excellent, and very useful.

One thing: the generated 3dplot image can't draw the total height source image upper stars. Is there a easy way to show entire profiles modifying script code?
Title: 3D star profile / Perfil estelar en 3D (BETA)
Post by: Andres.Pozo on 2009 April 10 02:50:13
Quote from: "C. Sonnenstein"
Is there a easy way to show entire profiles modifying script code?

Yes, I'm working on it.
Title: 3D star profile / Perfil estelar en 3D (BETA)
Post by: Andres.Pozo on 2009 April 10 04:57:41
I have a new version with the following improvements:
(http://img26.imageshack.us/img26/7720/3dview.png)

Code: [Select]

#include <pjsr/UndoFlag.jsh>
#include <pjsr/FillRule.jsh>

// Creates the parameters object.
function DefaultParams()
{
   var params={};
   params.angle = 30;   // Perspective angle in degrees
   params.scaleXY = 6;  // Scale factor for X and Y axis
   params.scaleZ = 500; // Scale factor for Z axis
   params.mtf=0.1;      // Midtones transfer value (0,1)
   params.backgroundColor=0xff606060;
   params.polygonFill=0xffffffff;
   params.polygonBorder=0xff000000;

   // Precalculated sin and cos of angle
   params.sin_angle=Math.sin(params.angle*Math.PI/180);
   params.cos_angle=Math.cos(params.angle*Math.PI/180);
   return params;
}

// Perspective transformation
function Perspective(x, y, z, params)
{
   with(params){
      // x
      var new_x = (x-y*sin_angle) * scaleXY;

      // y
      var new_y = y*cos_angle * scaleXY;

      // z
      new_y -= scaleZ * z;
   }
   return new Point (new_x, new_y);
}

// Applies the perspective transformation to all the pixels in the image
function CreatePerspective(src, limits, perspecParams)
{
   // Calculates the coordinates of each pixel applying the perspective transformation
   var xform=[];
   limits.left=limits.top=1e10;
   limits.right=limits.bottom=-1e10;
   for (var y = 0; y < src.height; y++) {
      xform[y]=[];
      for (var x = 0; x < src.width; x++){
         var z=Math.mtf(perspecParams.mtf,src.sample(x,y));
         var pixel=Perspective(x, y, z, perspecParams);
         xform[y][x]=pixel;
         if(pixel.x<limits.left)   limits.left=pixel.x;
         if(pixel.x>limits.right)  limits.right=pixel.x;
         if(pixel.y<limits.top)    limits.top=pixel.y;
         if(pixel.y>limits.bottom) limits.bottom=pixel.y;
      }
   }
   return xform;
}

function ShowError(message)
{
   var msgb=new MessageBox(message,"Error generating 3D view");
   msgb.execute();
   return;
}

function main()
{
   if(ImageWindow.activeWindow.currentView.isNull)
      return ShowError("There is not an active image");
   if(ImageWindow.activeWindow.currentView.image.width>500 ||
      ImageWindow.activeWindow.currentView.image.height>500)
         return ShowError("The image is bigger than 500x500 pixels");
         
   var startts = new Date;

   // Extract the luminance of the source image
   var src;
   if(ImageWindow.activeWindow.currentView.image.numberOfChannels!=1)
   {
      src =new Image();
      ImageWindow.activeWindow.currentView.image.extractLuminance(src);
   } else
      src = ImageWindow.activeWindow.currentView.image;

   // Inits the parameters
   var perspecParams=DefaultParams();

   // Create the perspective
   var limits=new Rect;
   var xform=CreatePerspective(src,limits,perspecParams);
   //with(limits) console.writeln(format("%f %f %f %f",left,top,right,bottom));

   // Create the output window
   var w = new ImageWindow (Math.round(limits.width), Math.round(limits.height), 3, 8, false, true, '_3dplot');
   var v = w.currentView;
   var i = v.image;

   // Start of the process
   var bmp = new Bitmap (i.width, i.height);
   var g = new Graphics (bmp);

   // Applies a translation to the graphics to center the image
   g.translateTransformation(-limits.left,-limits.top);
   
   v.beginProcess(UndoFlag_NoSwapFile);
      bmp.fill (perspecParams.backgroundColor);
      g.pen =  new Pen (perspecParams.polygonBorder);
      g.brush = new Brush (perspecParams.polygonFill);

      // Paint the polygons from back to front
      for (var n = 0; n < src.height-1; n++) {
         for (var m = 0; m < src.width-1; m++) {
            var polygon=new Array(
               xform[n][m],
               xform[n][m+1],
               xform[n+1][m+1],
               xform[n+1][m]);
            g.drawPolygon (polygon);
         }
      }
      g.end();
      i.blend (bmp);
   v.endProcess();
   w.show();
   
   var endts = new Date;
   console.writeln(format ("<br />3D view: %.2f s",(endts.getTime() - startts.getTime()) / 1000));
}

main();
Title: 3D star profile / Perfil estelar en 3D (BETA)
Post by: Andres.Pozo on 2009 April 10 06:14:37
I have added an optional shading effect:
(http://img410.imageshack.us/img410/7720/3dview.png)
Code: [Select]

#include <pjsr/UndoFlag.jsh>
#include <pjsr/FillRule.jsh>

// Creates the parameters object.
function DefaultParams()
{
   var params={};
   params.angle = 30;   // Perspective angle in degrees
   params.scaleXY = 6;  // Scale factor for X and Y axis
   params.scaleZ = 500; // Scale factor for Z axis
   params.mtf=0.1;      // Midtones transfer value (0,1)
   params.backgroundColor=0xff403000;
   params.polygonFill=0xffffffff;
   params.polygonBorder=0x10000000;
   params.useShading=true; // Apply shading effect
   params.lightBrightness=700; // Light intensity for the shading effect

   // Precalculated sin and cos of angle
   params.sin_angle=Math.sin(params.angle*Math.PI/180);
   params.cos_angle=Math.cos(params.angle*Math.PI/180);
   return params;
}

// Perspective transformation
function Perspective(x, y, z, params)
{
   with(params){
      // x
      var new_x = (x-y*sin_angle) * scaleXY;

      // y
      var new_y = y*cos_angle * scaleXY;

      // z
      new_y -= scaleZ * z;
   }
   return new Point (new_x, new_y);
}

// Applies the perspective transformation to all the pixels in the image
function CreatePerspective(src, limits, perspecParams)
{
   // Calculates the coordinates of each pixel applying the perspective transformation
   var xform=[];
   limits.left=limits.top=1e10;
   limits.right=limits.bottom=-1e10;
   for (var y = 0; y < src.height; y++) {
      xform[y]=[];
      for (var x = 0; x < src.width; x++){
         var pixel=Perspective(x, y, src.sample(x,y), perspecParams);
         xform[y][x]=pixel;
         if(pixel.x<limits.left)   limits.left=pixel.x;
         if(pixel.x>limits.right)  limits.right=pixel.x;
         if(pixel.y<limits.top)    limits.top=pixel.y;
         if(pixel.y>limits.bottom) limits.bottom=pixel.y;
      }
   }
   return xform;
}

function ApplyMTF(src, mtf)
{
   // Calculates the coordinates of each pixel applying the perspective transformation
   for (var y = 0; y < src.height; y++)
      for (var x = 0; x < src.width; x++){
         var z=Math.mtf(mtf,src.sample(x,y));
         src.setSample(z,x,y);
      }
}

function ShowError(message)
{
   var msgb=new MessageBox(message,"Error generating 3D view");
   msgb.execute();
   return;
}

function main()
{
   if(ImageWindow.activeWindow.currentView.isNull)
      return ShowError("There is not an active image");
   if(ImageWindow.activeWindow.currentView.image.width>500 ||
      ImageWindow.activeWindow.currentView.image.height>500)
         return ShowError("The image is bigger than 500x500 pixels");
         
   var startts = new Date;

   // Extract the luminance of the source image
   var src =new Image();
   ImageWindow.activeWindow.currentView.image.extractLuminance(src);

   // Inits the parameters
   var perspecParams=DefaultParams();

   ApplyMTF(src, perspecParams.mtf);
   
   // Create the perspective
   var limits=new Rect;
   var xform=CreatePerspective(src,limits,perspecParams);
   //with(limits) console.writeln(format("%f %f %f %f",left,top,right,bottom));

   // Create the output window
   var w = new ImageWindow (Math.round(limits.width), Math.round(limits.height), 3, 8, false, true, '_3dplot');
   var v = w.currentView;
   var i = v.image;

   // Start of the process
   var bmp = new Bitmap (i.width, i.height);
   var g = new Graphics (bmp);

   // Applies a translation to the graphics to center the image
   g.translateTransformation(-limits.left,-limits.top);
   
   v.beginProcess(UndoFlag_NoSwapFile);
      bmp.fill (perspecParams.backgroundColor);
      g.pen =  new Pen (perspecParams.polygonBorder);
      var fillbrush=[];
      if(perspecParams.useShading){
         for(var c=0; c<256;++c)
            fillbrush[c]=new Brush(0xFF000000 | (c<<16) | (c<<8) | c);
      }else
         g.brush = new Brush (perspecParams.polygonFill);

      // Paint the polygons from back to front
      for (var n = 0; n < src.height-1; n++) {
         for (var m = 0; m < src.width-1; m++) {
            if(perspecParams.useShading){
               var slope=src.sample(m,n)-src.sample(m+1,n)+
                         src.sample(m,n+1)-src.sample(m+1,n+1);
               var color=Math.round(slope * perspecParams.lightBrightness + 128);
               color=Math.max(0,Math.min(255,color));
               g.brush=fillbrush[color];
            }
           
            var polygon=new Array(
               xform[n][m],
               xform[n][m+1],
               xform[n+1][m+1],
               xform[n+1][m]);
            g.drawPolygon (polygon);
         }
      }
      g.end();
      i.blend (bmp);
   v.endProcess();
   w.show();
   
   var endts = new Date;
   console.writeln(format ("<br />3D view: %.2f s",(endts.getTime() - startts.getTime()) / 1000));
}

main();
Title: 3D star profile / Perfil estelar en 3D (BETA)
Post by: Nocturnal on 2009 April 10 09:29:16
This is terrific work! Would it be worthwhile to re-implement this is PCL so it's compiled code rather than javascript?
Title: 3D star profile / Perfil estelar en 3D (BETA)
Post by: Juan Conejero on 2009 April 10 10:07:59
Hi Andrés,

Very nice! You're doing a fantastic work. Congratulations to both!

This script can be developed into something very powerful and useful. A user interface to define parameters would be nice. Also, you could add false color to improve visual detection of image features, and a nonlinear representation to enhance the dimmest parts of the image (for example, a deep-sky object is represented with a too low profile, and this script would be perfect to represent the profile of a galaxy for example.

I'd remove the error test at line #82. I'm generating 3D profiles for images of 1Kx1K pixels and larger without problems.

Andrés, do you agree with including this script in PI 1.5 distribution (the latest version available at the date of release) ?

Quote
Would it be worthwhile to re-implement this is PCL so it's compiled code rather than javascript?


Of course, Sander. As PCL/C++ code with some optimizations (generation of a downsampled representation, multithreaded implementation as an interruptible process), this script could run in nearly real-time. It could be an observer tool just like Statistics.

However, the JavaScript runtime in PixInsight has a great advantage over compiled C++ modules: you can develop a tool with JavaScript without exiting the PixInsight platform, with all the advantages of an interpreted language backed by a powerful, native runtime. We have used JavaScript to develop several important tools that were implemented in C++ after thorough testing as interpreted routines. An example is HDRWaveletTransform.
Title: 3D star profile / Perfil estelar en 3D (BETA)
Post by: Nocturnal on 2009 April 10 10:12:26
Right, I can see how a javascript module can be a great prototyping tool. I was actually considering writing a profile tool in PCL (just installed it) but this 3D mesh would be much nicer. I agree that having this work like a 'monitor' is very useful.

Well, maybe I'll write the profile tool anyway, as an exercise. With a single line it's easy to represent colors, in 3D that is more tricky because it gets cluttered.
Title: 3D star profile / Perfil estelar en 3D (BETA)
Post by: Andres.Pozo on 2009 April 10 10:21:33
Quote from: "Juan Conejero"

This script can be developed into something very powerful and useful. A user interface to define parameters would be nice. Also, you could add false color to improve visual detection of image features, and a nonlinear representation to enhance the dimmest parts of the image (for example, a deep-sky object is represented with a too low profile, and this script would be perfect to represent the profile of a galaxy for example.

The nonlinear scaling is done with the MTF that I added to the last version. A good default value would be the midtone value of the Screen Transfer Function of the image (if I knew how to get it).
Quote from: "Juan Conejero"

I'd remove the error test at line #82. I'm generating 3D profiles for images of 1Kx1K pixels and larger without problems.

The limit could perhaps be increased, however, since the process can not be aborted, I think there should be a limit. Not everybody has a machine like yours :D.
Quote from: "Juan Conejero"

Andrés, do you agree with including this script in PI 1.5 distribution (the latest version available at the date of release) ?

I have no problem with that. Of course, the original idea is of David Serrano and he should be who granted the permission.
Title: 3D star profile / Perfil estelar en 3D (BETA)
Post by: Juan Conejero on 2009 April 10 10:21:57
Sander,

I look forward to see that profiling tool! :) Please don't hesitate to ask for any help/information you may need.
Title: 3D star profile / Perfil estelar en 3D (BETA)
Post by: Juan Conejero on 2009 April 10 11:28:12
A few quick modifications:

- The script can now be aborted by the user. So a size warning/error is no longer needed (IMO).

- The script shows progress information on the console.

- The perspective transformation points are now calculated faster. See changes in the CreatePerspective function (line 44).

- The MTF transformation is now carried out using a HistogramTransformation instance on a temporary copy of the working image. See the ApplyMTF function (line 73).

Code: [Select]
#include <pjsr/UndoFlag.jsh>
#include <pjsr/FillRule.jsh>
#include <pjsr/ColorSpace.jsh>

// Creates the parameters object.
function DefaultParams()
{
   var params={};
   params.angle = 30;   // Perspective angle in degrees
   params.scaleXY = 6;  // Scale factor for X and Y axis
   params.scaleZ = 500; // Scale factor for Z axis
   params.mtf=0.1;      // Midtones transfer value (0,1)
   params.backgroundColor=0xff403000;
   params.polygonFill=0xffffffff;
   params.polygonBorder=0x10000000;
   params.useShading=true; // Apply shading effect
   params.lightBrightness=700; // Light intensity for the shading effect

   // Precalculated sin and cos of angle
   params.sin_angle=Math.sin(params.angle*Math.PI/180);
   params.cos_angle=Math.cos(params.angle*Math.PI/180);
   return params;
}

// Perspective transformation -- Replaced by faster inline code; see below
/*
function Perspective(x, y, z, params)
{
   with(params){
      // x
      var new_x = (x - ys) * scaleXY;

      // y
      var new_y = ycs - scaleZ * z;

      // z
      new_y -= scaleZ * z; // -----> ???
   }
   return new Point (new_x, new_y);
}
*/

// Applies the perspective transformation to all the pixels in the image
function CreatePerspective(src, limits, perspecParams)
{
   src.initializeStatus( "Computing 3D coordinates", src.height );

   // Calculates the coordinates of each pixel applying the perspective transformation
   var xform=[];
   limits.left=limits.top=1e10;
   limits.right=limits.bottom=-1e10;
   for (var y = 0; y < src.height; y++) {
      xform[y]=[];
      var ys = y * perspecParams.sin_angle;
      var ycs = y * perspecParams.cos_angle * perspecParams.scaleXY;
      for (var x = 0; x < src.width; x++) {
         var z = src.sample( x, y );
         var pixel = new Point( (x - ys)*perspecParams.scaleXY,
                                ycs - perspecParams.scaleZ*z );
         //Perspective(x, ys, ycs, src.sample(x,y), perspecParams);
         xform[y][x]=pixel;
         if(pixel.x<limits.left)   limits.left=pixel.x;
         if(pixel.x>limits.right)  limits.right=pixel.x;
         if(pixel.y<limits.top)    limits.top=pixel.y;
         if(pixel.y>limits.bottom) limits.bottom=pixel.y;
      }

      src.advanceStatus( 1 );
   }
   return xform;
}

function ApplyMTF(src, mtf)
{
   // Faster solution using a process instance
   var HT = new HistogramTransformation;
   with ( HT )
   {
      H = // c0, m, c1, r0, r1
      [[0, 0.5, 1, 0, 1],
       [0, 0.5, 1, 0, 1],
       [0, 0.5, 1, 0, 1],
       [0, mtf, 1, 0, 1],
       [0, 0.5, 1, 0, 1]];
   }

   var wtmp = new ImageWindow( 1, 1, 1, 16, false, src.colorSpace != ColorSpace_Gray );
   var v = wtmp.mainView;

   v.beginProcess( UndoFlag_NoSwapFile );
   v.image.assign( src );
   v.endProcess();

   HT.executeOn( v, false ); // no swap file
   src.assign( v.image );

   wtmp.close();

   /*
   // Calculates the coordinates of each pixel applying the perspective transformation
   for (var y = 0; y < src.height; y++)
      for (var x = 0; x < src.width; x++){
         var z=Math.mtf(mtf,src.sample(x,y));
         src.setSample(z,x,y);
      }
   */
}

function DieError(message)
{
   var msgb = new MessageBox(message,"Error generating 3D view");
   msgb.execute();
   throw Error( message );
}

function main()
{
   if (ImageWindow.activeWindow.currentView.isNull)
      DieError("There is not an active image");
   /*
   if(ImageWindow.activeWindow.currentView.image.width>500 ||
      ImageWindow.activeWindow.currentView.image.height>500)
         DieError("The image is bigger than 500x500 pixels");*/

   console.show();
   console.writeln( "<end><cbr><br>***** 3D Profiling Script *****" );
   console.flush();

   var startts = new Date;

   // Extract the luminance of the source image
   var src = new Image();
   ImageWindow.activeWindow.currentView.image.extractLuminance(src);

   // Inits the parameters
   var perspecParams = DefaultParams();

   // Apply a midtones transformation
   if ( perspecParams.mtf != 0.5 )
      ApplyMTF(src, perspecParams.mtf);

   // Allow the user to abort this script
   console.abortEnabled = true;

   // Allow status monitoring for our working image
   src.statusEnabled = true;

   // Create the perspective
   var limits = new Rect;
   var xform = CreatePerspective(src,limits,perspecParams);
   //with(limits) console.writeln(format("%f %f %f %f",left,top,right,bottom));

   // Create the output window
   var w = new ImageWindow (Math.round(limits.width), Math.round(limits.height), 3, 8, false, true, '_3dplot');
   var v = w.currentView;
   var i = v.image;

   // Start of the process
   var bmp = new Bitmap (i.width, i.height);
   var g = new Graphics (bmp);

   // Applies a translation to the graphics to center the image
   g.translateTransformation(-limits.left,-limits.top);

   src.initializeStatus( "Rendering 3D profile", src.height-1 );

   v.beginProcess(UndoFlag_NoSwapFile);
      bmp.fill (perspecParams.backgroundColor);
      g.pen =  new Pen (perspecParams.polygonBorder);
      var fillbrush=[];
      if(perspecParams.useShading){
         for(var c=0; c<256;++c)
            fillbrush[c]=new Brush(0xFF000000 | (c<<16) | (c<<8) | c);
      }else
         g.brush = new Brush (perspecParams.polygonFill);

      // Paint the polygons from back to front
      for (var n = 0; n < src.height-1; n++) {
         for (var m = 0; m < src.width-1; m++) {
            if(perspecParams.useShading){
               var slope=src.sample(m,n)-src.sample(m+1,n)+
                         src.sample(m,n+1)-src.sample(m+1,n+1);
               var color=Math.round(slope * perspecParams.lightBrightness + 128);
               color=Math.max(0,Math.min(255,color));
               g.brush=fillbrush[color];
            }
           
            var polygon=new Array(
               xform[n][m],
               xform[n][m+1],
               xform[n+1][m+1],
               xform[n+1][m]);
            g.drawPolygon (polygon);
         }

         src.advanceStatus( 1 );
      }
      g.end();
      i.blend (bmp);
   v.endProcess();
   w.show();
   
   var endts = new Date;
   console.writeln(format ("<br />3D view: %.2f s",(endts.getTime() - startts.getTime()) / 1000));
}

main();
Title: 3D star profile / Perfil estelar en 3D (BETA)
Post by: David Serrano on 2009 April 10 11:49:48
Quote from: "Andres.Pozo"
I have added an optional shading effect:


Diossss qué bueno!!! xD

I've seen you removed the parameter "alt" I had in my original script. It was meant to be a configurable value that indicated the altitude over the terrain, indicated in degress. Thus, a value of 0 would render the grid as if we were on the ground (untested!) and a value of 90 would give a 2D grid.


Quote from: "Andres.Pozo"
Code: [Select]
        return ShowError("The image is bigger than 500x500 pixels");


I agree with Juan on this. The image dimensions limit I suggested was meant to be some kind of "Are you sure?" dialog, to avoid someone ;) asking for a higher limit. I like to allow users to crash their machine if they want to. Detecting the amount of RAM in the system would be useful to make the limit dynamic.

I would implement it as an absolute number of pixels (width * height), thus allowing the user to plot a 2000x10 image.


Quote from: "Andres.Pozo"
Of course, the original idea is of David Serrano and he should be who granted the permission.


The idea may be mine, but at this point the code is yours ;).
Title: 3D star profile / Perfil estelar en 3D (BETA)
Post by: Andres.Pozo on 2009 April 10 13:23:34
I have merged the improvements of Juan in the code that I was writting. I also have implemented the visualization using false color mapping the height to a color palette.

Example of false color with shading:
(http://img214.imageshack.us/img214/9456/3dview1.png)

Example of false color without shading:
(http://img11.imageshack.us/img11/1477/3dview2.png)

Code: [Select]
#include <pjsr/UndoFlag.jsh>
#include <pjsr/FillRule.jsh>
#include <pjsr/ColorSpace.jsh>

// Creates the parameters object.
function DefaultParams()
{
   var params={};
   params.angle = 30;   // Perspective angle in degrees
   params.scaleXY = 6;  // Scale factor for X and Y axis
   params.scaleZ = 500; // Scale factor for Z axis
   params.mtf=0.1;      // Midtones transfer value (0,1)
   params.backgroundColor=0xff403000;
   params.polygonFill=0xffffffff;
   params.polygonBorder=0x80000000;
   params.useShading=true; // Apply shading effect
   params.lightBrightness=2000; // Light intensity for the shading effect
   params.palette = [ [0x80,0,0] , [0,0x80,0] , [0,0,0xC0], [0x80,0,0x80], [0x80,0x80,0], [0x80,0x80,0x80] ];
   
   // Precalculated sin and cos of angle
   params.sin_angle=Math.sin(params.angle*Math.PI/180);
   params.cos_angle=Math.cos(params.angle*Math.PI/180);
   return params;
}

// Applies the perspective transformation to all the pixels in the image
function CreatePerspective(src, limits, perspecParams)
{
   src.initializeStatus( "Computing 3D coordinates", src.height );

   // Calculates the coordinates of each pixel applying the perspective transformation
   var xform=[];
   limits.left=limits.top=1e10;
   limits.right=limits.bottom=-1e10;
   for (var y = 0; y < src.height; y++) {
      xform[y]=[];
      var ys = y * perspecParams.sin_angle;
      var ycs = y * perspecParams.cos_angle * perspecParams.scaleXY;
      for (var x = 0; x < src.width; x++){
         var z= src.sample(x,y);
         var pixel=new Point( (x - ys)*perspecParams.scaleXY,
                                ycs - perspecParams.scaleZ*z );
         xform[y][x]=pixel;
         if(pixel.x<limits.left)   limits.left=pixel.x;
         if(pixel.x>limits.right)  limits.right=pixel.x;
         if(pixel.y<limits.top)    limits.top=pixel.y;
         if(pixel.y>limits.bottom) limits.bottom=pixel.y;
      }
      src.advanceStatus( 1 );
   }
   return xform;
}

function ApplyMTF(src, mtf)
{
   // Faster solution using a process instance
   var HT = new HistogramTransformation;
   with ( HT )
   {
      H = // c0, m, c1, r0, r1
      [[0, 0.5, 1, 0, 1],
       [0, 0.5, 1, 0, 1],
       [0, 0.5, 1, 0, 1],
       [0, mtf, 1, 0, 1],
       [0, 0.5, 1, 0, 1]];
   }

   var wtmp = new ImageWindow( 1, 1, 1, 16, false, src.colorSpace != ColorSpace_Gray );
   var v = wtmp.mainView;

   v.beginProcess( UndoFlag_NoSwapFile );
   v.image.assign( src );
   v.endProcess();

   HT.executeOn( v, false ); // no swap file
   src.assign( v.image );

   wtmp.close();
}

function DieError(message)
{
   var msgb=new MessageBox(message,"Error generating 3D view");
   msgb.execute();
   throw Error( message );
}

function ApplyLighting(basecolor, light)
{
   var r=(basecolor[0]*light/1000);
   r=r>255?255:r;
   var g=(basecolor[1]*light/1000);
   g=g>255?255:g;
   var b=(basecolor[2]*light/1000);
   b=b>255?255:b;
   return 0xFF000000 | (r<<16)| (g<<8) | b;
}

function RenderImage(src,i,limits,xform,perspecParams)
{
   // Start of the process
   var bmp = new Bitmap (i.width, i.height);
   var g = new Graphics (bmp);
   g.antialiasing=true;

   // Applies a translation to the graphics to center the image
   g.translateTransformation(-limits.left,-limits.top);

   bmp.fill (perspecParams.backgroundColor);

   var fillbrush=[];
   if(!perspecParams.useShading){
      for(var c=0;c<perspecParams.palette.length;c++)
         fillbrush[c]=new Brush(0xFF000000 | (perspecParams.palette[c][0]<<16) | (perspecParams.palette[c][1]<<8) | perspecParams.palette[c][2]);
      g.pen =  new Pen (perspecParams.polygonBorder,0);
   } else
      // Using shading the polygon borders should be less visible
      g.pen =  new Pen ((perspecParams.polygonBorder&0x00FFFFFF) | 0x20000000);

   // The median of the source image is the level of the "floor" of the 3D image
   var stats = new ImageStatistics;
   stats.generate(src);
   var minz=stats.median;

   src.initializeStatus( "Rendering 3D profile", src.height-1 );
   // Paint the polygons from back to front
   for (var n = 0; n < src.height-1; n++) {
      for (var m = 0; m < src.width-1; m++) {
         var  z1=src.sample(m,n),
              z2=src.sample(m+1,n),
              z3=src.sample(m,n+1),
              z4=src.sample(m+1,n+1);

         // Get the palette index
         //var z=(z1+z2+z3+z4)/4;
         var z=Math.max(Math.max(z1,z2),Math.max(z3,z4));
         z=(z-minz)/(1-minz); //rescale for mapping the palete to (minz,1)
         var palidx=Math.round(z*perspecParams.palette.length);
         palidx=palidx<0 ? 0 : (palidx>=perspecParams.palette.length ? perspecParams.palette.length-1 : palidx);

         if(perspecParams.useShading){
            // Get the lighting factor
            var slope=z1-z2+z3-z4;
            var lighting=(slope+0.5)*perspecParams.lightBrightness;
            lighting=lighting<0 ? 0 : Math.round(lighting);

            g.brush=new Brush(ApplyLighting(perspecParams.palette[palidx], lighting));
         } else
            g.brush=fillbrush[palidx];

         var polygon=new Array(
            xform[n][m],
            xform[n][m+1],
            xform[n+1][m+1],
            xform[n+1][m]);
         g.drawPolygon (polygon);
      }
      src.advanceStatus( 1 );
   }
   g.end();
   i.blend (bmp);
}

function main()
{
   if(ImageWindow.activeWindow.currentView.isNull)
      return DieError("There is not an active image");

   console.show();
   console.writeln( "<end><cbr><br>***** 3D Profiling Script *****" );
   console.flush();

   var startts = new Date;

   // Extract the luminance of the source image
   var src =new Image();
   ImageWindow.activeWindow.currentView.image.extractLuminance(src);

   // Inits the parameters
   var perspecParams=DefaultParams();

   if( perspecParams.mtf!=0.5)
      ApplyMTF(src, perspecParams.mtf);

   // Allow the user to abort this script
   console.abortEnabled = true;

   // Allow status monitoring for our working image
   src.statusEnabled = true;

   // Create the perspective
   var limits=new Rect;
   var xform=CreatePerspective(src,limits,perspecParams);
   //with(limits) console.writeln(format("%f %f %f %f",left,top,right,bottom));

   // Create the output window
   var newid;
   if(ImageWindow.activeWindow.currentView.isPreview)
      newid=ImageWindow.activeWindow.mainView.id+'_'+ImageWindow.activeWindow.currentView.id+'_3dplot';
   else
      newid=ImageWindow.activeWindow.currentView.id+'_3dplot';
   var w = new ImageWindow (Math.round(limits.width), Math.round(limits.height), 3, 8, false, true, newid);
   try{
      var v = w.currentView;
      var i = v.image;

      v.beginProcess(UndoFlag_NoSwapFile);
      RenderImage(src,i,limits,xform,perspecParams);
      v.endProcess();

      w.show();

      var endts = new Date;
      console.writeln(format ("<br />3D view: %.2f s",(endts.getTime() - startts.getTime()) / 1000));
   } catch(err)
   {
      w.close();
      console.writeln(err);
   }
}

main();
Title: 3D star profile / Perfil estelar en 3D (BETA)
Post by: Nocturnal on 2009 April 10 13:29:03
Voodoo magic. And I've been programming since I got a ZX-81 too many years ago :) Very impressive.
Title: 3D star profile / Perfil estelar en 3D (BETA)
Post by: Andres.Pozo on 2009 April 11 12:13:27
Quote from: "David Serrano"

I've seen you removed the parameter "alt" I had in my original script. It was meant to be a configurable value that indicated the altitude over the terrain, indicated in degress. Thus, a value of 0 would render the grid as if we were on the ground (untested!) and a value of 90 would give a 2D grid.


Hi,

I have reimplemented the possibility of rotating the 3D view. It is implemented using a linear projection matrix so it is quite fast.

These are a couple of screenshots:
(http://img16.imageshack.us/img16/9456/3dview1.png)
(http://img16.imageshack.us/img16/1477/3dview2.png)
Code: [Select]
#include <pjsr/UndoFlag.jsh>
#include <pjsr/FillRule.jsh>
#include <pjsr/ColorSpace.jsh>

// Creates the parameters object.
function DefaultParams()
{
   var params={};
   params.azimut = 30;    // Perspective angle in degrees
   params.elevation = 20; // Perspective elevation angle
   params.scaleXY = 6;    // Scale factor for X and Y axis
   params.scaleZ = 50;   // Scale factor for Z axis
   params.mtf=0.5;        // Midtones transfer value (0,1)
   params.backgroundColor=0xff403000;
   params.polygonFill=0xffffffff;
   params.polygonBorder=0x80000000;
   params.useShading=true; // Apply shading effect
   params.lightBrightness=2000; // Light intensity for the shading effect
   params.palette = [ [0x80,0,0] , [0,0x80,0] , [0,0,0xC0], [0x80,0,0x80], [0x80,0x80,0], [0,0x80,0x80], [0x80,0x80,0x80] ];

   return params;
}

// Applies the perspective transformation to all the pixels in the image
function CreatePerspective(src, limits, perspecParams, minz)
{
   src.initializeStatus( "Computing 3D coordinates", src.height );

   // Calculates the coordinates of each pixel applying the perspective transformation
   var xform=[];
   limits.left=limits.top=1e10;
   limits.right=limits.bottom=-1e10;

   // Modify the zscale in order to normalize the peak height
   // when the mtf changes
   var zscale=perspecParams.scaleZ/(1-minz);

   // Projection matrix
   var alpha=Math.rad(90-perspecParams.elevation);
   var beta=Math.rad(perspecParams.azimut);
   var m00=Math.cos(beta);
   var m10=-Math.sin(beta);
   var m20=0;
   var m01=Math.cos(alpha)*Math.sin(beta);
   var m11=Math.cos(alpha)*Math.cos(beta);
   var m21=-Math.sin(alpha);

   for (var y = 0; y < src.height; y++) {
      xform[y]=[];
      for (var x = 0; x < src.width; x++){
         var z= src.sample(x,y)*zscale;
         var pixel=new Point( (m00*x+m10*y)*perspecParams.scaleXY,
                              (m01*x+m11*y+m21*z)*perspecParams.scaleXY );
         xform[y][x]=pixel;
         if(pixel.x<limits.left)   limits.left=pixel.x;
         if(pixel.x>limits.right)  limits.right=pixel.x;
         if(pixel.y<limits.top)    limits.top=pixel.y;
         if(pixel.y>limits.bottom) limits.bottom=pixel.y;
      }
      src.advanceStatus( 1 );
   }
   
   return xform;
}

function ApplyMTF(src, mtf)
{
   // Faster solution using a process instance
   var HT = new HistogramTransformation;
   with ( HT )
   {
      H = // c0, m, c1, r0, r1
      [[0, 0.5, 1, 0, 1],
       [0, 0.5, 1, 0, 1],
       [0, 0.5, 1, 0, 1],
       [0, mtf, 1, 0, 1],
       [0, 0.5, 1, 0, 1]];
   }

   var wtmp = new ImageWindow( 1, 1, 1, 16, false, src.colorSpace != ColorSpace_Gray );
   var v = wtmp.mainView;

   v.beginProcess( UndoFlag_NoSwapFile );
   v.image.assign( src );
   v.endProcess();

   HT.executeOn( v, false ); // no swap file
   src.assign( v.image );

   wtmp.close();
}

function DieError(message)
{
   var msgb=new MessageBox(message,"Error generating 3D view");
   msgb.execute();
   throw Error( message );
}

function ApplyLighting(basecolor, light)
{
   var r=(basecolor[0]*light/1000);
   r=r>255?255:r;
   var g=(basecolor[1]*light/1000);
   g=g>255?255:g;
   var b=(basecolor[2]*light/1000);
   b=b>255?255:b;
   return 0xFF000000 | (r<<16)| (g<<8) | b;
}

function RenderImage(src,i,limits,xform,perspecParams,minz)
{
   // Start of the process
   var bmp = new Bitmap (i.width, i.height);
   var g = new Graphics (bmp);
   g.antialiasing=true;

   // Applies a translation to the graphics to center the image
   g.translateTransformation(-limits.left,-limits.top);

   bmp.fill (perspecParams.backgroundColor);

   var fillbrush=[];
   if(!perspecParams.useShading){
      for(var c=0;c<perspecParams.palette.length;c++)
         fillbrush[c]=new Brush(0xFF000000 | (perspecParams.palette[c][0]<<16) | (perspecParams.palette[c][1]<<8) | perspecParams.palette[c][2]);
      g.pen =  new Pen (perspecParams.polygonBorder,0);
   } else
      // Using shading the polygon borders should be less visible
      g.pen =  new Pen ((perspecParams.polygonBorder&0x00FFFFFF) | 0x20000000);

   src.initializeStatus( "Rendering 3D profile", src.height-1 );
   // Paint the polygons from back to front
   for (var n = 0; n < src.height-1; n++) {
      for (var m = 0; m < src.width-1; m++) {
         var  z1=src.sample(m,n),
              z2=src.sample(m+1,n),
              z3=src.sample(m,n+1),
              z4=src.sample(m+1,n+1);

         // Get the palette index
         //var z=(z1+z2+z3+z4)/4;
         var zM=Math.max(Math.max(z1,z2),Math.max(z3,z4));
         var zm=Math.min(Math.min(z1,z2),Math.min(z3,z4));
         var z=(zm+zM)/2;
         z=(z-minz)/(1-minz); //rescale for mapping the palete to (minz,1)
         var palidx=Math.round(z*perspecParams.palette.length);
         palidx=palidx<0 ? 0 : (palidx>=perspecParams.palette.length ? perspecParams.palette.length-1 : palidx);

         if(perspecParams.useShading){
            // Get the lighting factor
            var slope=z1-z2+z3-z4;
            var lighting=(slope+0.5)*perspecParams.lightBrightness;
            lighting=lighting<0 ? 0 : Math.round(lighting);

            g.brush=new Brush(ApplyLighting(perspecParams.palette[palidx], lighting));
         } else
            g.brush=fillbrush[palidx];

         var polygon=new Array(
            xform[n][m],
            xform[n][m+1],
            xform[n+1][m+1],
            xform[n+1][m]);
         g.drawPolygon (polygon);
      }
      src.advanceStatus( 1 );
   }
   g.end();
   i.blend (bmp);
}

function main()
{
   if(ImageWindow.activeWindow.currentView.isNull)
      return DieError("There is not an active image");

   console.show();
   console.writeln( "<end><cbr><br>***** 3D Profiling Script *****" );
   console.flush();

   var startts = new Date;

   // Extract the luminance of the source image
   var src =new Image();
   ImageWindow.activeWindow.currentView.image.extractLuminance(src);

   // Inits the parameters
   var perspecParams=DefaultParams();

   if( perspecParams.mtf!=0.5)
      ApplyMTF(src, perspecParams.mtf);

   // Allow the user to abort this script
   console.abortEnabled = true;

   // Allow status monitoring for our working image
   src.statusEnabled = true;

   // The median of the source image is the level of the "floor" of the 3D image
   var stats = new ImageStatistics;
   stats.generate(src);
   var minz=stats.median;

   // Create the perspective
   var limits=new Rect;
   var xform=CreatePerspective(src,limits,perspecParams,minz);
   //with(limits) console.writeln(format("%f %f %f %f",left,top,right,bottom));

   // Create the output window
   var newid;
   if(ImageWindow.activeWindow.currentView.isPreview)
      newid=ImageWindow.activeWindow.mainView.id+'_'+ImageWindow.activeWindow.currentView.id+'_3dplot';
   else
      newid=ImageWindow.activeWindow.currentView.id+'_3dplot';
   var w = new ImageWindow (Math.round(limits.width), Math.round(limits.height), 3, 8, false, true, newid);
   try{
      var v = w.currentView;
      var i = v.image;

      v.beginProcess(UndoFlag_NoSwapFile);
      RenderImage(src,i,limits,xform,perspecParams,minz);
      v.endProcess();

      w.show();

      var endts = new Date;
      console.writeln(format ("<br />3D view: %.2f s",(endts.getTime() - startts.getTime()) / 1000));
   } catch(err)
   {
      w.close();
      console.writeln(err);
   }
}

main();
Title: 3D star profile / Perfil estelar en 3D (BETA)
Post by: David Serrano on 2009 April 11 15:30:12
Smooth color transitions. They are somewhat ugly but I don't care about it. The strange profile was caused by a quick Morphological ;):

(http://pteam.pixinsight.com/david.serrano/pix/_3dplot-0.8.png)

Differences:

Code: [Select]
--- 3dplot/3dplot-0.7.js 2009-04-11 22:53:11.000000000 +0200
+++ 3dplot/3dplot-0.8.js 2009-04-12 00:32:50.000000000 +0200
@@ -16,6 +16,7 @@
    params.polygonBorder=0x80000000;
    params.useShading=true; // Apply shading effect
    params.lightBrightness=2000; // Light intensity for the shading effect
+   params.palette_elems = 256;
    params.palette = [ [0x80,0,0] , [0,0x80,0] , [0,0,0xC0], [0x80,0,0x80], [0x80,0x80,0], [0,0x80,0x80], [0x80,0x80,0x80] ];
 
    return params;
@@ -108,6 +109,33 @@
    return 0xFF000000 | (r<<16)| (g<<8) | b;
 }
 
+// the resulting palette doesn't have to be exactly 'num' elements long.
+function ExpandPalette(params)
+{
+   var p = params.palette;
+   var num = params.palette_elems;
+   var expanded = new Array;
+   var steps = Math.round (num / p.length);  // steps between two provided palette elements
+
+   for (var c = 0; c < p.length - 1; c++) {
+      var from = p[c];
+      var to = p[c+1];
+      var step_red   = (to[0] - from[0]) / steps;
+      var step_green = (to[1] - from[1]) / steps;
+      var step_blue  = (to[2] - from[2]) / steps;
+      for (s = 0; s < steps; s++) {
+         var new_red   = from[0] + s * step_red;
+         var new_green = from[1] + s * step_green;
+         var new_blue  = from[2] + s * step_blue;
+         expanded.push ([ new_red, new_green, new_blue ]);
+      }
+   }
+   // add last user-supplied palette element
+   expanded.push (p[p.length-1]);
+
+   params.palette = expanded;
+}
+
 function RenderImage(src,i,limits,xform,perspecParams,minz)
 {
    // Start of the process
@@ -120,6 +148,8 @@
 
    bmp.fill (perspecParams.backgroundColor);
 
+   ExpandPalette (perspecParams);
+
    var fillbrush=[];
    if(!perspecParams.useShading){
       for(var c=0;c<perspecParams.palette.length;c++)


Full code:

Code: [Select]
#include <pjsr/UndoFlag.jsh>
#include <pjsr/FillRule.jsh>
#include <pjsr/ColorSpace.jsh>

// Creates the parameters object.
function DefaultParams()
{
   var params={};
   params.azimut = 30;    // Perspective angle in degrees
   params.elevation = 20; // Perspective elevation angle
   params.scaleXY = 6;    // Scale factor for X and Y axis
   params.scaleZ = 50;   // Scale factor for Z axis
   params.mtf=0.5;        // Midtones transfer value (0,1)
   params.backgroundColor=0xff403000;
   params.polygonFill=0xffffffff;
   params.polygonBorder=0x80000000;
   params.useShading=true; // Apply shading effect
   params.lightBrightness=2000; // Light intensity for the shading effect
   params.palette_elems = 256;
   params.palette = [ [0x80,0,0] , [0,0x80,0] , [0,0,0xC0], [0x80,0,0x80], [0x80,0x80,0], [0,0x80,0x80], [0x80,0x80,0x80] ];

   return params;
}

// Applies the perspective transformation to all the pixels in the image
function CreatePerspective(src, limits, perspecParams, minz)
{
   src.initializeStatus( "Computing 3D coordinates", src.height );

   // Calculates the coordinates of each pixel applying the perspective transformation
   var xform=[];
   limits.left=limits.top=1e10;
   limits.right=limits.bottom=-1e10;

   // Modify the zscale in order to normalize the peak height
   // when the mtf changes
   var zscale=perspecParams.scaleZ/(1-minz);

   // Projection matrix
   var alpha=Math.rad(90-perspecParams.elevation);
   var beta=Math.rad(perspecParams.azimut);
   var m00=Math.cos(beta);
   var m10=-Math.sin(beta);
   var m20=0;
   var m01=Math.cos(alpha)*Math.sin(beta);
   var m11=Math.cos(alpha)*Math.cos(beta);
   var m21=-Math.sin(alpha);

   for (var y = 0; y < src.height; y++) {
      xform[y]=[];
      for (var x = 0; x < src.width; x++){
         var z= src.sample(x,y)*zscale;
         var pixel=new Point( (m00*x+m10*y)*perspecParams.scaleXY,
                              (m01*x+m11*y+m21*z)*perspecParams.scaleXY );
         xform[y][x]=pixel;
         if(pixel.x<limits.left)   limits.left=pixel.x;
         if(pixel.x>limits.right)  limits.right=pixel.x;
         if(pixel.y<limits.top)    limits.top=pixel.y;
         if(pixel.y>limits.bottom) limits.bottom=pixel.y;
      }
      src.advanceStatus( 1 );
   }
   
   return xform;
}

function ApplyMTF(src, mtf)
{
   // Faster solution using a process instance
   var HT = new HistogramTransformation;
   with ( HT )
   {
      H = // c0, m, c1, r0, r1
      [[0, 0.5, 1, 0, 1],
       [0, 0.5, 1, 0, 1],
       [0, 0.5, 1, 0, 1],
       [0, mtf, 1, 0, 1],
       [0, 0.5, 1, 0, 1]];
   }

   var wtmp = new ImageWindow( 1, 1, 1, 16, false, src.colorSpace != ColorSpace_Gray );
   var v = wtmp.mainView;

   v.beginProcess( UndoFlag_NoSwapFile );
   v.image.assign( src );
   v.endProcess();

   HT.executeOn( v, false ); // no swap file
   src.assign( v.image );

   wtmp.close();
}

function DieError(message)
{
   var msgb=new MessageBox(message,"Error generating 3D view");
   msgb.execute();
   throw Error( message );
}

function ApplyLighting(basecolor, light)
{
   var r=(basecolor[0]*light/1000);
   r=r>255?255:r;
   var g=(basecolor[1]*light/1000);
   g=g>255?255:g;
   var b=(basecolor[2]*light/1000);
   b=b>255?255:b;
   return 0xFF000000 | (r<<16)| (g<<8) | b;
}

// the resulting palette doesn't have to be exactly 'num' elements long.
function ExpandPalette(params)
{
   var p = params.palette;
   var num = params.palette_elems;
   var expanded = new Array;
   var steps = Math.round (num / p.length);  // steps between two provided palette elements

   for (var c = 0; c < p.length - 1; c++) {
      var from = p[c];
      var to = p[c+1];
      var step_red   = (to[0] - from[0]) / steps;
      var step_green = (to[1] - from[1]) / steps;
      var step_blue  = (to[2] - from[2]) / steps;
      for (s = 0; s < steps; s++) {
         var new_red   = from[0] + s * step_red;
         var new_green = from[1] + s * step_green;
         var new_blue  = from[2] + s * step_blue;
         expanded.push ([ new_red, new_green, new_blue ]);
      }
   }
   // add last user-supplied palette element
   expanded.push (p[p.length-1]);

   params.palette = expanded;
}

function RenderImage(src,i,limits,xform,perspecParams,minz)
{
   // Start of the process
   var bmp = new Bitmap (i.width, i.height);
   var g = new Graphics (bmp);
   g.antialiasing=true;

   // Applies a translation to the graphics to center the image
   g.translateTransformation(-limits.left,-limits.top);

   bmp.fill (perspecParams.backgroundColor);

   ExpandPalette (perspecParams);

   var fillbrush=[];
   if(!perspecParams.useShading){
      for(var c=0;c<perspecParams.palette.length;c++)
         fillbrush[c]=new Brush(0xFF000000 | (perspecParams.palette[c][0]<<16) | (perspecParams.palette[c][1]<<8) | perspecParams.palette[c][2]);
      g.pen =  new Pen (perspecParams.polygonBorder,0);
   } else
      // Using shading the polygon borders should be less visible
      g.pen =  new Pen ((perspecParams.polygonBorder&0x00FFFFFF) | 0x20000000);

   src.initializeStatus( "Rendering 3D profile", src.height-1 );
   // Paint the polygons from back to front
   for (var n = 0; n < src.height-1; n++) {
      for (var m = 0; m < src.width-1; m++) {
         var  z1=src.sample(m,n),
              z2=src.sample(m+1,n),
              z3=src.sample(m,n+1),
              z4=src.sample(m+1,n+1);

         // Get the palette index
         //var z=(z1+z2+z3+z4)/4;
         var zM=Math.max(Math.max(z1,z2),Math.max(z3,z4));
         var zm=Math.min(Math.min(z1,z2),Math.min(z3,z4));
         var z=(zm+zM)/2;
         z=(z-minz)/(1-minz); //rescale for mapping the palete to (minz,1)
         var palidx=Math.round(z*perspecParams.palette.length);
         palidx=palidx<0 ? 0 : (palidx>=perspecParams.palette.length ? perspecParams.palette.length-1 : palidx);

         if(perspecParams.useShading){
            // Get the lighting factor
            var slope=z1-z2+z3-z4;
            var lighting=(slope+0.5)*perspecParams.lightBrightness;
            lighting=lighting<0 ? 0 : Math.round(lighting);

            g.brush=new Brush(ApplyLighting(perspecParams.palette[palidx], lighting));
         } else
            g.brush=fillbrush[palidx];

         var polygon=new Array(
            xform[n][m],
            xform[n][m+1],
            xform[n+1][m+1],
            xform[n+1][m]);
         g.drawPolygon (polygon);
      }
      src.advanceStatus( 1 );
   }
   g.end();
   i.blend (bmp);
}

function main()
{
   if(ImageWindow.activeWindow.currentView.isNull)
      return DieError("There is not an active image");

   console.show();
   console.writeln( "<end><cbr><br>***** 3D Profiling Script *****" );
   console.flush();

   var startts = new Date;

   // Extract the luminance of the source image
   var src =new Image();
   ImageWindow.activeWindow.currentView.image.extractLuminance(src);

   // Inits the parameters
   var perspecParams=DefaultParams();

   if( perspecParams.mtf!=0.5)
      ApplyMTF(src, perspecParams.mtf);

   // Allow the user to abort this script
   console.abortEnabled = true;

   // Allow status monitoring for our working image
   src.statusEnabled = true;

   // The median of the source image is the level of the "floor" of the 3D image
   var stats = new ImageStatistics;
   stats.generate(src);
   var minz=stats.median;

   // Create the perspective
   var limits=new Rect;
   var xform=CreatePerspective(src,limits,perspecParams,minz);
   //with(limits) console.writeln(format("%f %f %f %f",left,top,right,bottom));

   // Create the output window
   var newid;
   if(ImageWindow.activeWindow.currentView.isPreview)
      newid=ImageWindow.activeWindow.mainView.id+'_'+ImageWindow.activeWindow.currentView.id+'_3dplot';
   else
      newid=ImageWindow.activeWindow.currentView.id+'_3dplot';
   var w = new ImageWindow (Math.round(limits.width), Math.round(limits.height), 3, 8, false, true, newid);
   try{
      var v = w.currentView;
      var i = v.image;

      v.beginProcess(UndoFlag_NoSwapFile);
      RenderImage(src,i,limits,xform,perspecParams,minz);
      v.endProcess();

      w.show();

      var endts = new Date;
      console.writeln(format ("<br />3D view: %.2f s",(endts.getTime() - startts.getTime()) / 1000));
   } catch(err)
   {
      w.close();
      console.writeln(err);
   }
}

main();
Title: 3D star profile / Perfil estelar en 3D (BETA)
Post by: Andres.Pozo on 2009 April 12 02:18:23
Quote from: "David Serrano"
Smooth color transitions.


That's a very good idea!!

One warning, the rendering function only works with an azimut angle between 0º an 90º. For different angles the painting of the polygons must be done in a different order.

Also, if the rendering was done painting triangles (instead of 4 vertex polygons) the color transitions and the shading would be smoother. However, the rendering would be also much slower and perhaps the polygon borders should not be drawn.
Title: 3D star profile / Perfil estelar en 3D (BETA)
Post by: David Serrano on 2009 April 12 04:54:03
Finally, some user interface.

I've given up fighting against NumericControl.slider.setRange, even after reading a thread (http://pixinsight.com/forum/viewtopic.php?t=272) discussing its usage. The intended interval for the MTF is 0.005 but it jumps from 0.001 to 0.010 when changing the range from [1, 1000] to [1, 1001]. I'd also like the Light brightness to go in steps of 10.

Andrés, do you agree on GPLv3?

Code: [Select]
/*
    3DPlot v0.9
    Iteratively stretch histogram with an appropriate luminance mask each time.
    Copyright (C) 2009  Andrés Pozo, Juan Conejero, 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.9:   License, changelog, bare-bones user interface.
   0.8:   Smooth color transitions.
   0.7:   Reimplemented the possibility of rotating the 3D view.
   0.6:   Visualization now maps the height to a color palette.
   0.5:   The script can now be aborted by the user.
          The script shows progress information on the console.
          The perspective transformation points are now calculated faster.
          The MTF transformation is now carried out using a HT.
   0.4:   Optional shading effect added.
   0.3:   The resulting image shows the full extent of the 3D view.
          The code has been reorganized and structured.
          A MTF is applied to the image to improve the visualization.
          The perspective transformation has been simplified again.
          There is a couple of checks before starting the algorithm.
   0.2:   It now works on grayscale images.
          The 3D image is centered in the output image.
          The code is a bit simpler.
          It is now 7 times faster.
   0.1:   Initial test version.
*/

#define VERSION "0.9"

#include <pjsr/ColorSpace.jsh>
#include <pjsr/FillRule.jsh>
#include <pjsr/FrameStyle.jsh>
#include <pjsr/NumericControl.jsh>
#include <pjsr/UndoFlag.jsh>

#feature-id 3DPlot
#feature-info Please write something here.
//#feature-icon 3dplot.xpm

// Creates the parameters object.
function DefaultParams()
{
   this.azimut = 30;    // Perspective angle in degrees
   this.elevation = 20; // Perspective elevation angle
   this.scaleXY = 6;    // Scale factor for X and Y axis
   this.scaleZ = 50;   // Scale factor for Z axis
   this.mtf=0.5;        // Midtones transfer value (0,1)
   this.backgroundColor=0xff403000;
   this.polygonFill=0xffffffff;
   this.polygonBorder=0x80000000;
   this.useShading=true; // Apply shading effect
   this.lightBrightness=2000; // Light intensity for the shading effect
   this.palette_elems = 256;
   this.palette = [ [0x80,0,0] , [0,0x80,0] , [0,0,0xC0], [0x80,0,0x80], [0x80,0x80,0], [0,0x80,0x80], [0x80,0x80,0x80] ];
}
var perspecParams = new DefaultParams;

// Applies the perspective transformation to all the pixels in the image
function CreatePerspective(src, limits, perspecParams, minz)
{
   src.initializeStatus( "Computing 3D coordinates", src.height );

   // Calculates the coordinates of each pixel applying the perspective transformation
   var xform=[];
   limits.left=limits.top=1e10;
   limits.right=limits.bottom=-1e10;

   // Modify the zscale in order to normalize the peak height
   // when the mtf changes
   var zscale=perspecParams.scaleZ/(1-minz);

   // Projection matrix
   var alpha=Math.rad(90-perspecParams.elevation);
   var beta=Math.rad(perspecParams.azimut);
   var m00=Math.cos(beta);
   var m10=-Math.sin(beta);
   var m20=0;
   var m01=Math.cos(alpha)*Math.sin(beta);
   var m11=Math.cos(alpha)*Math.cos(beta);
   var m21=-Math.sin(alpha);

   for (var y = 0; y < src.height; y++) {
      xform[y]=[];
      for (var x = 0; x < src.width; x++){
         var z= src.sample(x,y)*zscale;
         var pixel=new Point( (m00*x+m10*y)*perspecParams.scaleXY,
                              (m01*x+m11*y+m21*z)*perspecParams.scaleXY );
         xform[y][x]=pixel;
         if(pixel.x<limits.left)   limits.left=pixel.x;
         if(pixel.x>limits.right)  limits.right=pixel.x;
         if(pixel.y<limits.top)    limits.top=pixel.y;
         if(pixel.y>limits.bottom) limits.bottom=pixel.y;
      }
      src.advanceStatus( 1 );
   }
   
   return xform;
}

function ApplyMTF(src, mtf)
{
   // Faster solution using a process instance
   var HT = new HistogramTransformation;
   with ( HT )
   {
      H = // c0, m, c1, r0, r1
      [[0, 0.5, 1, 0, 1],
       [0, 0.5, 1, 0, 1],
       [0, 0.5, 1, 0, 1],
       [0, mtf, 1, 0, 1],
       [0, 0.5, 1, 0, 1]];
   }

   var wtmp = new ImageWindow( 1, 1, 1, 16, false, src.colorSpace != ColorSpace_Gray );
   var v = wtmp.mainView;

   v.beginProcess( UndoFlag_NoSwapFile );
   v.image.assign( src );
   v.endProcess();

   HT.executeOn( v, false ); // no swap file
   src.assign( v.image );

   wtmp.close();
}

function DieError(message)
{
   var msgb=new MessageBox(message,"Error generating 3D view");
   msgb.execute();
   throw Error( message );
}

function ApplyLighting(basecolor, light)
{
   var r=(basecolor[0]*light/1000);
   r=r>255?255:r;
   var g=(basecolor[1]*light/1000);
   g=g>255?255:g;
   var b=(basecolor[2]*light/1000);
   b=b>255?255:b;
   return 0xFF000000 | (r<<16)| (g<<8) | b;
}

// the resulting palette doesn't have to be exactly 'num' elements long.
function ExpandPalette(params)
{
   var p = params.palette;
   var num = params.palette_elems;
   var expanded = new Array;
   var steps = Math.round (num / p.length);  // steps between two provided palette elements

   for (var c = 0; c < p.length - 1; c++) {
      var from = p[c];
      var to = p[c+1];
      var step_red   = (to[0] - from[0]) / steps;
      var step_green = (to[1] - from[1]) / steps;
      var step_blue  = (to[2] - from[2]) / steps;
      for (s = 0; s < steps; s++) {
         var new_red   = from[0] + s * step_red;
         var new_green = from[1] + s * step_green;
         var new_blue  = from[2] + s * step_blue;
         expanded.push ([ new_red, new_green, new_blue ]);
      }
   }
   // add last user-supplied palette element
   expanded.push (p[p.length-1]);

   params.palette = expanded;
}

function RenderImage(src,i,limits,xform,perspecParams,minz)
{
   // Start of the process
   var bmp = new Bitmap (i.width, i.height);
   var g = new Graphics (bmp);
   g.antialiasing=true;

   // Applies a translation to the graphics to center the image
   g.translateTransformation(-limits.left,-limits.top);

   bmp.fill (perspecParams.backgroundColor);

   ExpandPalette (perspecParams);

   var fillbrush=[];
   if(!perspecParams.useShading){
      for(var c=0;c<perspecParams.palette.length;c++)
         fillbrush[c]=new Brush(0xFF000000 | (perspecParams.palette[c][0]<<16) | (perspecParams.palette[c][1]<<8) | perspecParams.palette[c][2]);
      g.pen =  new Pen (perspecParams.polygonBorder,0);
   } else
      // Using shading the polygon borders should be less visible
      g.pen =  new Pen ((perspecParams.polygonBorder&0x00FFFFFF) | 0x20000000);

   src.initializeStatus( "Rendering 3D profile", src.height-1 );
   // Paint the polygons from back to front
   for (var n = 0; n < src.height-1; n++) {
      for (var m = 0; m < src.width-1; m++) {
         var  z1=src.sample(m,n),
              z2=src.sample(m+1,n),
              z3=src.sample(m,n+1),
              z4=src.sample(m+1,n+1);

         // Get the palette index
         //var z=(z1+z2+z3+z4)/4;
         var zM=Math.max(Math.max(z1,z2),Math.max(z3,z4));
         var zm=Math.min(Math.min(z1,z2),Math.min(z3,z4));
         var z=(zm+zM)/2;
         z=(z-minz)/(1-minz); //rescale for mapping the palete to (minz,1)
         var palidx=Math.round(z*perspecParams.palette.length);
         palidx=palidx<0 ? 0 : (palidx>=perspecParams.palette.length ? perspecParams.palette.length-1 : palidx);

         if(perspecParams.useShading){
            // Get the lighting factor
            var slope=z1-z2+z3-z4;
            var lighting=(slope+0.5)*perspecParams.lightBrightness;
            lighting=lighting<0 ? 0 : Math.round(lighting);

            g.brush=new Brush(ApplyLighting(perspecParams.palette[palidx], lighting));
         } else
            g.brush=fillbrush[palidx];

         var polygon=new Array(
            xform[n][m],
            xform[n][m+1],
            xform[n+1][m+1],
            xform[n+1][m]);
         g.drawPolygon (polygon);
      }
      src.advanceStatus( 1 );
   }
   g.end();
   i.blend (bmp);
}

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

   // help label
   this.helpLabel = new Label (this);
   with (this.helpLabel) {
      frameStyle = FrameStyle_Box;
       margin = 4;
       wordWrapping = true;
       useRichText = true;
       text = "<b>3DPlot v"+VERSION+"</b> - A " +
          "script that wants some description.";
   }

   // iterations
   this.azimut_NC = new NumericControl (this);
   with (this.azimut_NC) {
      label.text = "Azimut:";
      setRange (0, 90);
      slider.setRange (0, 90);
      slider.minWidth = 200;
      setPrecision (0);
      setValue (perspecParams.azimut);
      toolTip = "Azimut of view.";
      onValueUpdated = function (value) {
        perspecParams.azimut = value;
      }
   }

   // median
   this.elevation_NC = new NumericControl (this);
   with (this.elevation_NC) {
      label.text = "Elevation over the terrain:";
      setRange (0, 90);
      slider.setRange (0, 90);
      slider.minWidth = 200;
      setPrecision (0);
      setValue (perspecParams.elevation);
      toolTip = "Elevation of the observer over the terrain.";
      onValueUpdated = function (value) {
         perspecParams.elevation = value;
      }
   }

   // scaleXY
   this.scaleXY_NC = new NumericControl (this);
   with (this.scaleXY_NC) {
      label.text = "Scale of X and Y coordinates:";
      setRange (1, 10);
      slider.setRange (1, 10);
      slider.minWidth = 50;
      setPrecision (0);
      setValue (perspecParams.scaleXY);
      toolTip = "Scale of X and Y coordinates.";
      onValueUpdated = function (value) {
         perspecParams.scaleXY = value;
      }
   }

   // scaleZ
   this.scaleZ_NC = new NumericControl (this);
   with (this.scaleZ_NC) {
      label.text = "Scale of Z coordinate:";
      setRange (1, 100);
      slider.setRange (1, 100);
      slider.minWidth = 80;
      setPrecision (0);
      setValue (perspecParams.scaleZ);
      toolTip = "Scale of Z coordinate.";
      onValueUpdated = function (value) {
         perspecParams.scaleZ = value;
      }
   }

   // mtf
   this.mtf_NC = new NumericControl (this);
   with (this.mtf_NC) {
      label.text = "MTF:";
      setRange (0.005, 0.995);
      slider.setRange (1, 990);
      slider.minWidth = 800;
      setPrecision (3);
      setValue (perspecParams.mtf);
      toolTip = "MidtonesTransferFunction to apply in order to improve faint detail visualization.";
      onValueUpdated = function (value) {
         perspecParams.mtf = value;
      }
   }

   // useShading
   this.shading_CB = new CheckBox (this);
   with (this.shading_CB) {
      text = "Use shading";
      checked = perspecParams.useShading;
      onCheck = function (checked) { perspecParams.useShading = checked; }
      toolTip = "Check to toggle the use of shading.";
   }

   // lightBrightness
   this.brightness_NC = new NumericControl (this);
   with (this.brightness_NC) {
      label.text = "Light brightness:";
      setRange (100, 5000);
      slider.setRange (1, 490);
      slider.minWidth = 200;
      setPrecision (0);
      setValue (perspecParams.lightBrightness);
      toolTip = "Intensity of the light.";
      onValueUpdated = function (value) {
         perspecParams.lightBrightness = value;
      }
   }

   // buttons
   this.ok_Button = new PushButton (this);
   this.ok_Button.text = " OK ";
   this.ok_Button.onClick = function() { 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;
   with (this.sizer) {
      margin = 6;
      spacing = 6;
      add (this.helpLabel);
      addSpacing (4);
      add (this.azimut_NC);
      add (this.elevation_NC);
      add (this.scaleXY_NC);
      add (this.scaleZ_NC);
      add (this.mtf_NC);
      add (this.shading_CB);
      add (this.brightness_NC);
      add (this.buttons_Sizer);
   }

   this.windowTitle = "3DPlot v" + VERSION;
   this.adjustToContents();
}
_3dplot_dialog.prototype = new Dialog;
var dialog = new _3dplot_dialog;

function main()
{
   if(ImageWindow.activeWindow.currentView.isNull)
      return DieError("There is not an active image");

   if (!dialog.execute())
      return;

   console.show();
   console.writeln( "<end><cbr><br>***** 3D Profiling Script *****" );
   console.flush();

   var startts = new Date;

   // Extract the luminance of the source image
   var src =new Image();
   ImageWindow.activeWindow.currentView.image.extractLuminance(src);

   // Inits the parameters
   //var perspecParams=DefaultParams();

   if( perspecParams.mtf!=0.5)
      ApplyMTF(src, perspecParams.mtf);

   // Allow the user to abort this script
   console.abortEnabled = true;

   // Allow status monitoring for our working image
   src.statusEnabled = true;

   // The median of the source image is the level of the "floor" of the 3D image
   var stats = new ImageStatistics;
   stats.generate(src);
   var minz=stats.median;

   // Create the perspective
   var limits=new Rect;
   var xform=CreatePerspective(src,limits,perspecParams,minz);
   //with(limits) console.writeln(format("%f %f %f %f",left,top,right,bottom));

   // Create the output window
   var newid;
   if(ImageWindow.activeWindow.currentView.isPreview)
      newid=ImageWindow.activeWindow.mainView.id+'_'+ImageWindow.activeWindow.currentView.id+'_3dplot';
   else
      newid=ImageWindow.activeWindow.currentView.id+'_3dplot';
   var w = new ImageWindow (Math.round(limits.width), Math.round(limits.height), 3, 8, false, true, newid);
   try{
      var v = w.currentView;
      var i = v.image;

      v.beginProcess(UndoFlag_NoSwapFile);
      RenderImage(src,i,limits,xform,perspecParams,minz);
      v.endProcess();

      w.show();

      var endts = new Date;
      console.writeln(format ("<br />3D view: %.2f s",(endts.getTime() - startts.getTime()) / 1000));
   } catch(err)
   {
      w.close();
      console.writeln(err);
   }
}

main();
Title: 3D star profile / Perfil estelar en 3D (BETA)
Post by: Andres.Pozo on 2009 April 12 06:30:20
Quote from: "David Serrano"
Finally, some user interface.

I've given up fighting against NumericControl.slider.setRange, even after reading a thread (http://pixinsight.com/forum/viewtopic.php?t=272) discussing its usage. The intended interval for the MTF is 0.005 but it jumps from 0.001 to 0.010 when changing the range from [1, 1000] to [1, 1001]. I'd also like the Light brightness to go in steps of 10.

Andrés, do you agree on GPLv3?



David, the GPLv3 would be fine. However, I think that Juan should be also added to the list of contributors. He added some good optimizations and most of the code for console management.
Title: 3D star profile / Perfil estelar en 3D (BETA)
Post by: David Serrano on 2009 April 12 07:22:52
Quote from: "Andres.Pozo"
David, the GPLv3 would be fine. However, I think that Juan should be also added to the list of contributors.


Oops, that's true. I've updated the code accordingly ;).
Title: 3D star profile / Perfil estelar en 3D (BETA)
Post by: Niall Saunders on 2009 April 12 13:33:57
Hi Guys,

Fantastic utility !!

Might I make a suggestion for further, future, development?

Take your basic idea, and code structure, but use it to visualise FWHM of all stars in the image.

Could it be used to visualise star elongation (where FWHM in the X-axis does not equal FWHM in the Y-axis) ?

Could it be used to visualise areas of the image where FWHM is different from the 'median' FWHM?

To me, this sort of approach would allow you to visualise optical abberations such as coma and collimation.

Also, if the stars themselves were 'removed' (by masking, etc.) it could be used to visualise any background gradients - perhaps it would have some use in just visualising the image extracted in a DBE operation.

I can see that, before too long, PixInsight will need an interface that allows it to drop a Process Container instance on each newly acquired image, as it arrives in a specified directory - to allow us imagers to see what our 'RAW' data looks like, dark and flat calibrated, perhaps even with a running 'average combine', maybe even Kalmann-filtered if required.

PixInsight - 'per PI ad astra'
Title: 3D star profile / Perfil estelar en 3D (BETA)
Post by: David Serrano on 2009 April 12 14:19:56
I've noted a couple of strange behaviours in this script:

(http://pteam.pixinsight.com/david.serrano/pix/_3dplot-0.9.png)

The red portion of the palette dominates half of the height. It seems like the beginning of the palette was situated near the largest star, at the lower left edge of the mesh, and that cells below that level were painted red. I think the first element of the palette should be assigned to black pixels in the source image, and the last element to white pixels.

The second weirdness is in the content of the defined preview. There seems to be a green arc along the star. It could be an artefact or it could be genuine, I don't know, but there doesn't seem to be any height change that justifies the color.

Nice donuts eh? ;)
Title: 3D star profile / Perfil estelar en 3D (BETA)
Post by: Juan Conejero on 2009 April 12 16:46:19
New version with improved dialog and a few optimizations.

One of the changes is somewhat subtle but very important. In previous versions a function was used to calculate 3D rendition points and the limits of the rendered scene, namely:

Code: [Select]
function CreatePerspective(src, limits, perspecParams, minz)

The problem is that the limits argument was intended to be passed by reference, but that is impossible: in JavaScript, function parameters are always passed by value.

Luckily, limits was working as a by-reference argument (or the script wouldn't work at all), but this is only due to some peculiarities of my implementation (the way I've embedded Mozilla's JavaScript engine into PI Core, and how I've connected some internal PI objects to expose them through the JS runtime).

For this reason, I have replaced the old CreatePerspective function by a _3dplot_xform object that owns both the transformation's point cube (the points member) and the transformation's bounding rectangle (the limits member).

Code: [Select]
/*
    3DPlot v1.0
    A script to generate three-dimensional image renditions.
    Copyright (C) 2009  Andrés Pozo, David Serrano, Juan Conejero

    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:
   1.0:   Several improvements to the user interface and a few optimizations.
          Replaced CreatePerspective() by a _3dplot_xform object.
          Some identifiers changed for improved naming coherency.
   0.9:   License, changelog, bare-bones user interface.
   0.8:   Smooth color transitions.
   0.7:   Reimplemented the possibility of rotating the 3D view.
   0.6:   Visualization now maps the height to a color palette.
   0.5:   The script can now be aborted by the user.
          The script shows progress information on the console.
          The perspective transformation points are now calculated faster.
          The MTF transformation is now carried out using a HT.
   0.4:   Optional shading effect added.
   0.3:   The resulting image shows the full extent of the 3D view.
          The code has been reorganized and structured.
          A MTF is applied to the image to improve the visualization.
          The perspective transformation has been simplified again.
          There is a couple of checks before starting the algorithm.
   0.2:   It now works on grayscale images.
          The 3D image is centered in the output image.
          The code is a bit simpler.
          It is now 7 times faster.
   0.1:   Initial test version.
*/

#define TITLE "3DPlot"
#define VERSION "1.0"

#include <pjsr/ColorSpace.jsh>
#include <pjsr/FillRule.jsh>
#include <pjsr/FrameStyle.jsh>
#include <pjsr/NumericControl.jsh>
#include <pjsr/UndoFlag.jsh>

#feature-id 3DPlot
#feature-info Please write something here.
//#feature-icon 3dplot.xpm

// Creates the parameters object.
function _3dplot_params()
{
   this.srcView = ImageWindow.activeWindow.currentView;
   this.azimuth = 30;    // Perspective angle in degrees
   this.elevation = 20; // Perspective elevation angle
   this.scaleXY = 6;    // Scale factor for X and Y axis
   this.scaleZ = 50;   // Scale factor for Z axis
   this.mtf=0.5;        // Midtones transfer value (0,1)
   this.backgroundColor=0xff403000;
   this.polygonFill=0xffffffff;
   this.polygonBorder=0x80000000;
   this.useShading=true; // Apply shading effect
   this.lightBrightness=2000; // Light intensity for the shading effect
   this.palette_elems = 256;
   this.palette = [ [0x80,0,0] , [0,0x80,0] , [0,0,0xC0], [0x80,0,0x80], [0x80,0x80,0], [0,0x80,0x80], [0x80,0x80,0x80] ];
}
var perspecParams = new _3dplot_params;

// Applies the perspective transformation to all the pixels in the image
function _3dplot_xform(src, perspecParams, minz)
{
   // Calculates the coordinates of each pixel applying the perspective transformation
   this.points=new Array;
   this.limits=new Rect( 1e10, 1e10, -1e10, -1e10 );

   // Modify the zscale in order to normalize the peak height
   // when the mtf changes
   var zscale=perspecParams.scaleZ/(1-minz);

   // Projection matrix
   var alpha=Math.rad(90-perspecParams.elevation);
   var beta=Math.rad(perspecParams.azimuth);
   var m00=Math.cos(beta);
   var m10=-Math.sin(beta);
   var m20=0;
   var m01=Math.cos(alpha)*Math.sin(beta);
   var m11=Math.cos(alpha)*Math.cos(beta);
   var m21=-Math.sin(alpha)*zscale;

   src.initializeStatus( "Computing 3D coordinates", src.height );

   for (var y = 0; y < src.height; y++) {
      this.points[y]=new Array;
      var m10y = m10*y;
      var m11y = m11*y;
      for (var x = 0; x < src.width; x++){
         var z = src.sample(x,y);
         var p = new Point( m00*x + m10y, m01*x + m11y + m21*z );
         p.mul( perspecParams.scaleXY );
         this.points[y][x] = p;
         if (p.x < this.limits.left)   this.limits.left = p.x;
         if (p.x > this.limits.right)  this.limits.right = p.x;
         if (p.y < this.limits.top)    this.limits.top = p.y;
         if (p.y > this.limits.bottom) this.limits.bottom = p.y;
      }
      src.advanceStatus( 1 );
   }

   gc();
}

function ApplyMTF(src, mtf)
{
   // Faster solution using a process instance
   var HT = new HistogramTransformation;
   with ( HT )
   {
      H = // c0, m, c1, r0, r1
      [[0, 0.5, 1, 0, 1],
       [0, 0.5, 1, 0, 1],
       [0, 0.5, 1, 0, 1],
       [0, mtf, 1, 0, 1],
       [0, 0.5, 1, 0, 1]];
   }

   var wtmp = new ImageWindow( 1, 1, 1, 16, false, src.colorSpace != ColorSpace_Gray );
   var v = wtmp.mainView;

   v.beginProcess( UndoFlag_NoSwapFile );
   v.image.assign( src );
   v.endProcess();

   HT.executeOn( v, false ); // no swap file
   src.assign( v.image );

   wtmp.close();
}

function DieError(message)
{
   var msgb=new MessageBox(message,"Error generating 3D view");
   msgb.execute();
   throw Error( message );
}

function ApplyLighting(basecolor, light)
{
   var r=(basecolor[0]*light/1000);
   r=r>255?255:r;
   var g=(basecolor[1]*light/1000);
   g=g>255?255:g;
   var b=(basecolor[2]*light/1000);
   b=b>255?255:b;
   return 0xFF000000 | (r<<16)| (g<<8) | b;
}

// the resulting palette doesn't have to be exactly 'num' elements long.
function ExpandPalette(params)
{
   var p = params.palette;
   var num = params.palette_elems;
   var expanded = new Array;
   var steps = Math.round (num / p.length);  // steps between two provided palette elements

   for (var c = 0; c < p.length - 1; c++) {
      var from = p[c];
      var to = p[c+1];
      var step_red   = (to[0] - from[0]) / steps;
      var step_green = (to[1] - from[1]) / steps;
      var step_blue  = (to[2] - from[2]) / steps;
      for (s = 0; s < steps; s++) {
         var new_red   = from[0] + s * step_red;
         var new_green = from[1] + s * step_green;
         var new_blue  = from[2] + s * step_blue;
         expanded.push ([ new_red, new_green, new_blue ]);
      }
   }
   // add last user-supplied palette element
   expanded.push (p[p.length-1]);

   params.palette = expanded;
}

function RenderImage(src,i,limits,xform,perspecParams,minz)
{
   // Start of the process
   var bmp = new Bitmap (i.width, i.height);
   var g = new Graphics (bmp);
   g.antialiasing=true;

   // Applies a translation to the graphics to center the image
   g.translateTransformation(-limits.left,-limits.top);

   bmp.fill (perspecParams.backgroundColor);

   ExpandPalette (perspecParams);

   var fillbrush=[];
   if(!perspecParams.useShading){
      for(var c=0;c<perspecParams.palette.length;c++)
         fillbrush[c]=new Brush(0xFF000000 | (perspecParams.palette[c][0]<<16) | (perspecParams.palette[c][1]<<8) | perspecParams.palette[c][2]);
      g.pen =  new Pen (perspecParams.polygonBorder,0);
   } else
      // Using shading the polygon borders should be less visible
      g.pen =  new Pen ((perspecParams.polygonBorder&0x00FFFFFF) | 0x20000000);

   src.initializeStatus( "Rendering 3D profile", src.height-1 );
   // Paint the polygons from back to front
   for (var n = 0; n < src.height-1; n++) {
      for (var m = 0; m < src.width-1; m++) {
         var  z1=src.sample(m,n),
              z2=src.sample(m+1,n),
              z3=src.sample(m,n+1),
              z4=src.sample(m+1,n+1);

         // Get the palette index
         //var z=(z1+z2+z3+z4)/4;
         var zM=Math.max(Math.max(z1,z2),Math.max(z3,z4));
         var zm=Math.min(Math.min(z1,z2),Math.min(z3,z4));
         var z=(zm+zM)/2;
         z=(z-minz)/(1-minz); //rescale for mapping the palete to (minz,1)
         var palidx=Math.round(z*perspecParams.palette.length);
         palidx=palidx<0 ? 0 : (palidx>=perspecParams.palette.length ? perspecParams.palette.length-1 : palidx);

         if(perspecParams.useShading){
            // Get the lighting factor
            var slope=z1-z2+z3-z4;
            var lighting=(slope+0.5)*perspecParams.lightBrightness;
            lighting=lighting<0 ? 0 : Math.round(lighting);

            g.brush=new Brush(ApplyLighting(perspecParams.palette[palidx], lighting));
         } else
            g.brush=fillbrush[palidx];

         var polygon=new Array(
            xform[n][m],
            xform[n][m+1],
            xform[n+1][m+1],
            xform[n+1][m]);
         g.drawPolygon (polygon);
      }
      src.advanceStatus( 1 );
   }
   g.end();
   i.blend (bmp);
}

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

   var labelWidth1 = this.font.width( "Polygon border color:" );
   var editWidth1 = 9*this.font.width( "0" );
   var editWidth2 = 12*this.font.width( "0" );

   // help label
   this.helpLabel = new Label (this);
   with (this.helpLabel) {
      frameStyle = FrameStyle_Box;
       margin = 4;
       wordWrapping = true;
       useRichText = true;
       text = "<p><b>" + TITLE + " v" + VERSION + "</b> &mdash; A " +
          "script to generate three-dimensional image renditions.</p>" +
          "<p>Copyright &copy; 2009 Andr&eacute;s Pozo / David Serrano / Juan Conejero</p>";
   }

   // source image

   this.srcImage_Label = new Label( this );
   this.srcImage_Label.minWidth = labelWidth1;
   this.srcImage_Label.text = "Source image:";
   this.srcImage_Label.textAlignment = TextAlign_Right|TextAlign_VertCenter;

   this.srcImage_ViewList = new ViewList( this );
   this.srcImage_ViewList.minWidth = 250;
   this.srcImage_ViewList.getAll(); // include main views as well as previews
   this.srcImage_ViewList.currentView = perspecParams.srcView;
   this.srcImage_ViewList.toolTip = "<p>Select the image to be rendered.</p>";
   this.srcImage_ViewList.onViewSelected = function( view )
   {
      perspecParams.srcView = view;
   };

   this.srcImage_Sizer = new HorizontalSizer;
   this.srcImage_Sizer.spacing = 4;
   this.srcImage_Sizer.add( this.srcImage_Label );
   this.srcImage_Sizer.add( this.srcImage_ViewList, 100 );

   // azimuth
   this.azimuth_NC = new NumericControl (this);
   with (this.azimuth_NC) {
      real = false;
      label.text = "Azimuth:";
      label.minWidth = labelWidth1;
      setRange (0, 90);
      slider.setRange (0, 90);
      slider.minWidth = 250;
      edit.minWidth = editWidth1;;
      setValue (perspecParams.azimuth);
      toolTip = "<p>Azimuth angle in degrees.</p>";
      onValueUpdated = function (value) {
        perspecParams.azimuth = value;
      }
   }

   // elevation
   this.elevation_NC = new NumericControl (this);
   with (this.elevation_NC) {
      real = false;
      label.text = "Elevation:";
      label.minWidth = labelWidth1;
      setRange (0, 90);
      slider.setRange (0, 90);
      slider.minWidth = 250;
      edit.minWidth = editWidth1;;
      setValue (perspecParams.elevation);
      toolTip = "<p>Observer's elevation angle above the ground, in degrees.</p>";
      onValueUpdated = function (value) {
         perspecParams.elevation = value;
      }
   }

   // scaleXY
   this.scaleXY_NC = new NumericControl (this);
   with (this.scaleXY_NC) {
      real = false;
      label.text = "X-Y plane scale:";
      label.minWidth = labelWidth1;
      setRange (1, 10);
      slider.setRange (1, 10);
      slider.minWidth = 250;
      edit.minWidth = editWidth1;;
      setValue (perspecParams.scaleXY);
      toolTip = "<p>Scale of X and Y coordinates in rendition pixels per image pixels.</p>";
      onValueUpdated = function (value) {
         perspecParams.scaleXY = value;
      }
   }

   // scaleZ
   this.scaleZ_NC = new NumericControl (this);
   with (this.scaleZ_NC) {
      real = false;
      label.text = "Z-axis scale:";
      label.minWidth = labelWidth1;
      setRange (1, 100);
      slider.setRange (1, 100);
      slider.minWidth = 250;
      edit.minWidth = editWidth1;;
      setValue (perspecParams.scaleZ);
      toolTip = "<p>Scaling factor of Z-axis coordinates.</p>";
      onValueUpdated = function (value) {
         perspecParams.scaleZ = value;
      }
   }

   // mtf
   this.mtf_NC = new NumericControl (this);
   with (this.mtf_NC) {
      label.text = "Midtones balance:";
      label.minWidth = labelWidth1;
      setRange (0, 1);
      slider.setRange (1, 500);
      slider.minWidth = 250;
      setPrecision (3);
      edit.minWidth = editWidth1;;
      setValue (perspecParams.mtf);
      toolTip = "<p>This parameter is part of a histogram transformation applied to " +
                "improve faint detail visualization.</p>";
      onValueUpdated = function (value) {
         perspecParams.mtf = value;
      }
   }

   // lightBrightness
   this.brightness_NC = new NumericControl (this);
   with (this.brightness_NC) {
      real = false;
      label.text = "Brightness:";
      label.minWidth = labelWidth1;
      setRange (100, 5000);
      slider.setRange (1, 50);
      slider.minWidth = 250;
      edit.minWidth = editWidth1;;
      setValue (perspecParams.lightBrightness);
      toolTip = "<p>Intensity of incident light.</p>";
      onValueUpdated = function (value) {
         perspecParams.lightBrightness = value;
      }
   }

   // useShading
   this.shading_CB = new CheckBox (this);
   with (this.shading_CB) {
      text = "Use shading";
      checked = perspecParams.useShading;
      onCheck = function (checked) { perspecParams.useShading = checked; }
      toolTip = "<p>If this option is selected, a special shading algorithm will be used " +
                "to enhance the 3-D rendition.</p>";
   }

   this.shading_Sizer = new HorizontalSizer;
   this.shading_Sizer.addSpacing( labelWidth1+4 );
   this.shading_Sizer.add( this.shading_CB );
   this.shading_Sizer.addStretch();

   // background color

   this.backgroundColor_Label = new Label( this );
   this.backgroundColor_Label.text = "Background color:";
   this.backgroundColor_Label.textAlignment = TextAlign_Right|TextAlign_VertCenter;
   this.backgroundColor_Label.minWidth = labelWidth1;

   this.backgroundColor_Edit = new Edit( this );
   this.backgroundColor_Edit.text = format( "%X", perspecParams.backgroundColor );
   this.backgroundColor_Edit.setFixedWidth( editWidth2 );
   this.backgroundColor_Edit.toolTip = "<p>Background color encoded as a 32-bit hexadecimal integer.<br/>" +
                                "(AARRGGBB format: AA=alpha (transparency), RR=red, GG=green, BB=blue)</p>";
   this.backgroundColor_Edit.onEditCompleted = function()
   {
      perspecParams.backgroundColor = parseInt( this.text, 16 );
      this.text = format( '%X', perspecParams.backgroundColor );
   };

   this.backgroundColor_Sizer = new HorizontalSizer;
   this.backgroundColor_Sizer.spacing = 4;
   this.backgroundColor_Sizer.add( this.backgroundColor_Label );
   this.backgroundColor_Sizer.add( this.backgroundColor_Edit );
   this.backgroundColor_Sizer.addStretch();

   // polygon fill color

   this.polygonFill_Label = new Label( this );
   this.polygonFill_Label.text = "Polygon fill color:";
   this.polygonFill_Label.textAlignment = TextAlign_Right|TextAlign_VertCenter;
   this.polygonFill_Label.minWidth = labelWidth1;

   this.polygonFill_Edit = new Edit( this );
   this.polygonFill_Edit.text = format( "%X", perspecParams.polygonFill );
   this.polygonFill_Edit.setFixedWidth( editWidth2 );
   this.polygonFill_Edit.toolTip = "<p>Polygon fill color encoded as a 32-bit hexadecimal integer.<br/>" +
                                "(AARRGGBB format: AA=alpha (transparency), RR=red, GG=green, BB=blue)</p>";
   this.polygonFill_Edit.onEditCompleted = function()
   {
      perspecParams.polygonFill = parseInt( this.text, 16 );
      this.text = format( '%X', perspecParams.polygonFill );
   };

   this.polygonFill_Sizer = new HorizontalSizer;
   this.polygonFill_Sizer.spacing = 4;
   this.polygonFill_Sizer.add( this.polygonFill_Label );
   this.polygonFill_Sizer.add( this.polygonFill_Edit );
   this.polygonFill_Sizer.addStretch();

   // polygon border color

   this.polygonBorder_Label = new Label( this );
   this.polygonBorder_Label.text = "Polygon border color:";
   this.polygonBorder_Label.textAlignment = TextAlign_Right|TextAlign_VertCenter;
   this.polygonBorder_Label.minWidth = labelWidth1;

   this.polygonBorder_Edit = new Edit( this );
   this.polygonBorder_Edit.text = format( "%X", perspecParams.polygonBorder );
   this.polygonBorder_Edit.setFixedWidth( editWidth2 );
   this.polygonBorder_Edit.toolTip = "<p>Polygon border color encoded as a 32-bit hexadecimal integer.<br/>" +
                                "(AARRGGBB format: AA=alpha (transparency), RR=red, GG=green, BB=blue)</p>";
   this.polygonBorder_Edit.onEditCompleted = function()
   {
      perspecParams.polygonBorder = parseInt( this.text, 16 );
      this.text = format( '%X', perspecParams.polygonBorder );
   };

   this.polygonBorder_Sizer = new HorizontalSizer;
   this.polygonBorder_Sizer.spacing = 4;
   this.polygonBorder_Sizer.add( this.polygonBorder_Label );
   this.polygonBorder_Sizer.add( this.polygonBorder_Edit );
   this.polygonBorder_Sizer.addStretch();

   // ### TODO: Better color selection controls - Include a ColorSelectionDialog object in PJSR?

   // ### TODO: Allow selecting palettes

   // buttons
   this.ok_Button = new PushButton (this);
   this.ok_Button.text = "OK";
   this.ok_Button.onClick = function() { 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 = 8;
   this.buttons_Sizer.addStretch();
   this.buttons_Sizer.add (this.ok_Button);
   this.buttons_Sizer.add (this.cancel_Button);

   // pack everything
   this.sizer = new VerticalSizer;
   with (this.sizer) {
      margin = 8;
      spacing = 6;
      add (this.helpLabel);
      addSpacing (4);
      add (this.srcImage_Sizer);
      add (this.azimuth_NC);
      add (this.elevation_NC);
      add (this.scaleXY_NC);
      add (this.scaleZ_NC);
      add (this.mtf_NC);
      add (this.brightness_NC);
      add (this.shading_Sizer);
      add (this.backgroundColor_Sizer);
      add (this.polygonFill_Sizer);
      add (this.polygonBorder_Sizer);
      add (this.buttons_Sizer);
   }

   this.windowTitle = TITLE + " v" + VERSION;
   this.adjustToContents();
   this.setFixedSize();
}
_3dplot_dialog.prototype = new Dialog;

function FullViewIdAsValidViewId( fullId )
{
   return fullId.replace( "->", "_" );
}

function main()
{
   if(ImageWindow.activeWindow.isNull)
      DieError("There is no active image");

   console.hide();

   for ( ;; )
   {
      if (!(new _3dplot_dialog).execute())
         return;

      if ( !perspecParams.srcView.isNull )
         break;

      (new MessageBox( "No source view has been selected!",
                       TITLE, StdIcon_Error, StdButton_Ok )).execute();
   }

   console.show();
   console.writeln( "<end><cbr><br>***** 3D Profiling Script *****" );
   console.flush();

   var startts = new Date;

   // Extract the luminance of the source image
   var src = new Image();
   perspecParams.srcView.image.extractLuminance(src);

   if( perspecParams.mtf!=0.5)
      ApplyMTF(src, perspecParams.mtf);

   // Allow the user to abort this script
   console.abortEnabled = true;

   // Allow status monitoring for our working image
   src.statusEnabled = true;

   // The median of the source image is the level of the "floor" of the 3D image
   var minz=src.median();

   // Create the perspective
   var xform=new _3dplot_xform(src, perspecParams, minz);
   //with(xform.limits) console.writeln(format("%f %f %f %f",left,top,right,bottom));

   // Create the output window
   var newid = FullViewIdAsValidViewId( perspecParams.srcView.fullId ) + "_3dplot";
   var w = new ImageWindow (Math.round(xform.limits.width),
                            Math.round(xform.limits.height), 3, 8, false, true, newid);
   try{
      var v = w.mainView;
      var i = v.image;

      v.beginProcess(UndoFlag_NoSwapFile);
      RenderImage(src, i, xform.limits, xform.points, perspecParams, minz);
      v.endProcess();

      w.show();

      var endts = new Date;
      console.writeln(format ("<br />3D view: %.2f s",(endts.getTime() - startts.getTime()) / 1000));
   } catch(err)
   {
      w.close();
      console.writeln(err);
   }
}

main();
Title: 3D star profile / Perfil estelar en 3D (BETA)
Post by: Andres.Pozo on 2009 April 13 02:00:14
Quote from: "David Serrano"
I've noted a couple of strange behaviours in this script:
The red portion of the palette dominates half of the height. It seems like the beginning of the palette was situated near the largest star, at the lower left edge of the mesh, and that cells below that level were painted red. I think the first element of the palette should be assigned to black pixels in the source image, and the last element to white pixels.


It seems that this image has been generated using a very low mtf value. The algorithm is using the median of the image (after applying the mtf) to set the origin of the palette. All the points lower than the median are assigned the first element of the palette. With a very low mtf the noise of the image is magnified and also the range of values lower than the median.
We could try to find another value for the origin of the palette, but with normal mtf's, the median works fine. The minimum z could be an option, but the cold pixels would distort the palette. Perhaps we could try finding the value in the percentile 1% of the histogram or something similar.

Also, the lighting function is a quite simple approximation. It multiplies the lighting factor by the RGB channels of the base color. The black color can not be used because the lighting function would always return 0. The effect of "black" would be achieved using a dark (20%?) gray.
Title: 3D star profile / Perfil estelar en 3D (BETA)
Post by: David Serrano on 2009 April 13 14:55:15
Quote from: "Andres.Pozo"
It seems that this image has been generated using a very low mtf value.


Not exactly. The image was bright in itself because it's part of M45 nebulosity. The largest star is Alcyone, the brightest one in the cluster.


Quote from: "Andres.Pozo"
The minimum z could be an option, but the cold pixels would distort the palette.


I, as a user, would see no distortion in the cold pixels. After all, if the background in my image isn't absolute black, the background in the generated 3D map shouldn't be absolute red (or whatever color was the first in the palette). I guess we have two equally valid points of view over this matter ;).
Title: Re: 3D star profile / Perfil estelar en 3D (BETA)
Post by: Andres.Pozo on 2009 June 11 09:58:58
PI 1.5.2 now allows to get the STF active in a view. I have modified the 3dplot script to apply the same STF active in the view instead asking the user for a MTF value. This also solves the problem of finding a good value for the "floor" of the plot.

The configuration dialog is now a bit simpler but I also think the plot is more controllable using the STF window. A couple of examples:
(http://img218.imageshack.us/img218/6348/screenshot2l.jpg)

(http://img41.imageshack.us/img41/2505/screenshot1oma.jpg)

This is the new code:
Code: [Select]
// ****************************************************************************
// PixInsight JavaScript Runtime API - PJSR Version 1.0
// Copyright (C) 2003-2009 Pleiades Astrophoto. All Rights Reserved.
// ****************************************************************************
// pjsr/3DPlot.js - Released 2009/06/05 15:52:25 UTC
// ****************************************************************************
// This file is part of the PJSR. The PJSR is a JavaScript API for development
// of scripts on the PixInsight platform (ECMAScript 262-3 compliant).
// The PJSR is provided in the hope that it will be useful, but it is
// distributed "AS IS" WITHOUT ANY WARRANTY; without even the warranties of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// ****************************************************************************

/*
   3DPlot v1.2
   A script to generate three-dimensional image renditions.
   Copyright (C) 2009 Andrés del Pozo, David Serrano, Juan Conejero

   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:
   1.2:   Uses the STF active in the view instead asking the user for a MTF value
   1.1:   Added SVG output. This is mainly intended to test the new SVG
          generation capabilities available in PI 1.5.
          Fixed generation of image renditions with transparency. When
          necessary, an alpha chanel is now generated automatically.
   1.0:   Several improvements to the user interface and a few optimizations.
          Replaced CreatePerspective() by a _3dplot_xform object.
          Some identifiers changed for coherency.
   0.9:   License, changelog, bare-bones user interface.
   0.8:   Smooth color transitions.
   0.7:   Reimplemented the possibility of rotating the 3D view.
   0.6:   Visualization now maps the height to a color palette.
   0.5:   The script can now be aborted by the user.
          The script shows progress information on the console.
          The perspective transformation points are now calculated faster.
          The MTF transformation is now carried out using a HT.
   0.4:   Optional shading effect added.
   0.3:   The resulting image shows the full extent of the 3D view.
          The code has been reorganized and structured.
          A MTF is applied to the image to improve the visualization.
          The perspective transformation has been simplified again.
          There is a couple of checks before starting the algorithm.
   0.2:   It now works on grayscale images.
          The 3D image is centered in the output image.
          The code is a bit simpler.
          It is now 7 times faster.
   0.1:   Initial test version.
*/

#define TITLE "3DPlot"
#define VERSION "1.2"

#include <pjsr/ColorSpace.jsh>
#include <pjsr/FillRule.jsh>
#include <pjsr/FrameStyle.jsh>
#include <pjsr/NumericControl.jsh>
#include <pjsr/UndoFlag.jsh>

#feature-id    Utilities > 3DPlot
#feature-info  A script to generate three-dimensional image renditions. \
               Optional output as a new image or as a SVG file.<br/> \
               <br/> \
               Copyright &copy; 2009 Andr&eacute;s Pozo, David Serrano (PTeam), \
               Juan Conejero (PTeam)
//#feature-icon 3dplot.xpm

// Creates the parameters object.
function _3dplot_params()
{
   this.srcView = ImageWindow.activeWindow.currentView;
   this.svgOutput = false;
   this.svgFileName = File.systemTempDirectory + "/3dplot.svg";
   this.azimuth = 30;            // Perspective angle in degrees
   this.elevation = 20;          // Perspective elevation angle
   this.scaleXY = 6;             // Scale factor for X and Y axis
   this.scaleZ = 50;             // Scale factor for Z axis
   this.backgroundColor = 0xff403000;
   this.polygonFill = 0xffffffff;
   this.polygonBorder = 0x80000000;
   this.useShading = true;       // Apply shading effect
   this.lightBrightness = 2000;  // Light intensity for the shading effect
   this.paletteElems = 256;
   this.palette = [ [0x80,0,0] , [0,0x80,0] , [0,0,0xC0], [0x80,0,0x80], [0x80,0x80,0], [0,0x80,0x80], [0x80,0x80,0x80] ];
}
var perspecParams = new _3dplot_params;

// Applies the perspective transformation to all the pixels in the image
function _3dplot_xform(src, perspecParams)
{
   // Calculates the coordinates of each pixel applying the perspective transformation
   this.points=new Array;
   this.limits=new Rect( 1e10, 1e10, -1e10, -1e10 );

   // Projection matrix
   var alpha=Math.rad(90-perspecParams.elevation);
   var beta=Math.rad(perspecParams.azimuth);
   var m00=Math.cos(beta);
   var m10=-Math.sin(beta);
   var m20=0;
   var m01=Math.cos(alpha)*Math.sin(beta);
   var m11=Math.cos(alpha)*Math.cos(beta);
   var m21=-Math.sin(alpha)*perspecParams.scaleZ;

   src.initializeStatus( "Computing 3D coordinates", src.height );

   for (var y = 0; y < src.height; y++) {
      this.points[y]=new Array;
      var m10y = m10*y;
      var m11y = m11*y;
      for (var x = 0; x < src.width; x++){
         var z = src.sample(x,y);
         var p = new Point( m00*x + m10y, m01*x + m11y + m21*z );
         p.mul( perspecParams.scaleXY );
         this.points[y][x] = p;
         if (p.x < this.limits.left)   this.limits.left = p.x;
         if (p.x > this.limits.right)  this.limits.right = p.x;
         if (p.y < this.limits.top)    this.limits.top = p.y;
         if (p.y > this.limits.bottom) this.limits.bottom = p.y;
      }
      src.advanceStatus( 1 );
   }

   gc();
}

function ApplySTF(src, stf)
{
   console.writeln(stf[0]);
   console.writeln(stf[1]);
   console.writeln(stf[2]);
   console.writeln(stf[3]);
   var low=(stf[0][1]+stf[1][1]+stf[2][1])/3;
   var mtf=(stf[0][0]+stf[1][0]+stf[2][0])/3;
   var hgh=(stf[0][2]+stf[1][2]+stf[2][2])/3;

   // Faster solution using a process instance
   var HT = new HistogramTransformation;
   HT.H =
      [[  0, 0.5,   1, 0, 1],
       [  0, 0.5,   1, 0, 1],
       [  0, 0.5,   1, 0, 1],
       [low, mtf, hgh, 0, 1],
       [  0, 0.5,   1, 0, 1]];


   var wtmp = new ImageWindow( 1, 1, 1, 16, false, src.colorSpace != ColorSpace_Gray );
   var v = wtmp.mainView;

   v.beginProcess( UndoFlag_NoSwapFile );
   v.image.assign( src );
   v.endProcess();

   HT.executeOn( v, false ); // no swap file
   src.assign( v.image );

   wtmp.close();
}

function DieError(message)
{
   var msgb=new MessageBox(message,"Error generating 3D view");
   msgb.execute();
   throw Error( message );
}

function ApplyLighting(basecolor, light)
{
   var r=(basecolor[0]*light/1000);
   r=r>255?255:r;
   var g=(basecolor[1]*light/1000);
   g=g>255?255:g;
   var b=(basecolor[2]*light/1000);
   b=b>255?255:b;
   return 0xFF000000 | (r<<16)| (g<<8) | b;
}

// the resulting palette doesn't have to be exactly 'num' elements long.
function ExpandPalette(params)
{
   var p = params.palette;
   var num = params.paletteElems;
   var expanded = new Array;
   var steps = Math.round (num / p.length);  // steps between two provided palette elements

   for (var c = 0; c < p.length - 1; c++) {
      var from = p[c];
      var to = p[c+1];
      var step_red   = (to[0] - from[0]) / steps;
      var step_green = (to[1] - from[1]) / steps;
      var step_blue  = (to[2] - from[2]) / steps;
      for (var s = 0; s < steps; s++) {
         var new_red   = from[0] + s * step_red;
         var new_green = from[1] + s * step_green;
         var new_blue  = from[2] + s * step_blue;
         expanded.push ([ new_red, new_green, new_blue ]);
      }
   }
   // add last user-supplied palette element
   expanded.push (p[p.length-1]);

   params.palette = expanded;
}

function Render( src, g, limits, xform, perspecParams)
{
   g.antialiasing=true;

   // Applies a translation to the graphics to center the image
   g.translateTransformation(-limits.left,-limits.top);

   ExpandPalette (perspecParams);

   var fillbrush=[];
   if(!perspecParams.useShading){
      for(var c=0;c<perspecParams.palette.length;c++)
         fillbrush[c]=new Brush(0xFF000000 | (perspecParams.palette[c][0]<<16) | (perspecParams.palette[c][1]<<8) | perspecParams.palette[c][2]);
      g.pen =  new Pen (perspecParams.polygonBorder,0);
   } else
      // Using shading the polygon borders should be less visible
      g.pen =  new Pen ((perspecParams.polygonBorder&0x00FFFFFF) | 0x20000000);

   src.initializeStatus( "Rendering 3D profile", src.height-1 );
   // Paint the polygons from back to front
   for (var n = 0; n < src.height-1; n++) {
      for (var m = 0; m < src.width-1; m++) {
         var  z1=src.sample(m,n),
              z2=src.sample(m+1,n),
              z3=src.sample(m,n+1),
              z4=src.sample(m+1,n+1);

         // Get the palette index
         //var z=(z1+z2+z3+z4)/4;
         var zM=Math.max(Math.max(z1,z2),Math.max(z3,z4));
         var zm=Math.min(Math.min(z1,z2),Math.min(z3,z4));
         var z=(zm+zM)/2;
         var palidx=Math.round(z*perspecParams.palette.length);
         palidx=palidx<0 ? 0 : (palidx>=perspecParams.palette.length ? perspecParams.palette.length-1 : palidx);

         if(perspecParams.useShading){
            // Get the lighting factor
            var slope=z1-z2+z3-z4;
            var lighting=(slope+0.5)*perspecParams.lightBrightness;
            lighting=lighting<0 ? 0 : Math.round(lighting);

            g.brush=new Brush(ApplyLighting(perspecParams.palette[palidx], lighting));
         } else
            g.brush=fillbrush[palidx];

         var polygon=new Array(
            xform[n][m],
            xform[n][m+1],
            xform[n+1][m+1],
            xform[n+1][m]);
         g.drawPolygon (polygon);
      }
      src.advanceStatus( 1 );
   }
}

function RenderImage( src, img, limits, xform, perspecParams)
{
   var bmp = new Bitmap( img.width, img.height );
   bmp.fill( perspecParams.backgroundColor );
   var g = new Graphics( bmp );
   Render( src, g, limits, xform, perspecParams);
   g.end();
   img.blend( bmp );
}

function RenderSVG( src, svg, limits, xform, perspecParams )
{
   var g = new Graphics( svg );
   g.fillRect( svg.size, new Brush( perspecParams.backgroundColor ) );
   Render( src, g, limits, xform, perspecParams );
   g.end();
}

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

   var labelWidth1 = this.font.width( "Polygon border color:" );
   var editWidth1 = 9*this.font.width( "0" );
   var editWidth2 = 12*this.font.width( "0" );

   // help label
   this.helpLabel = new Label (this);
   with (this.helpLabel) {
      frameStyle = FrameStyle_Box;
       margin = 4;
       wordWrapping = true;
       useRichText = true;
       text = "<p><b>" + TITLE + " v" + VERSION + "</b> &mdash; A " +
          "script to generate 3D image renditions.</p>" +
          "<p>Copyright &copy; 2009 Andr&eacute;s Pozo / David Serrano / Juan Conejero</p>";
   }

   // source image

   this.srcImage_Label = new Label( this );
   this.srcImage_Label.minWidth = labelWidth1;
   this.srcImage_Label.text = "Source image:";
   this.srcImage_Label.textAlignment = TextAlign_Right|TextAlign_VertCenter;

   this.srcImage_ViewList = new ViewList( this );
   this.srcImage_ViewList.minWidth = 250;
   this.srcImage_ViewList.getAll(); // include main views as well as previews
   this.srcImage_ViewList.currentView = perspecParams.srcView;
   this.srcImage_ViewList.toolTip = "<p>Select the image to be rendered.</p>";
   this.srcImage_ViewList.onViewSelected = function( view )
   {
      perspecParams.srcView = view;
   };

   this.srcImage_Sizer = new HorizontalSizer;
   this.srcImage_Sizer.spacing = 4;
   this.srcImage_Sizer.add( this.srcImage_Label );
   this.srcImage_Sizer.add( this.srcImage_ViewList, 100 );

   // SVG Output

   this.svgOutput_CB = new CheckBox (this);
   with (this.svgOutput_CB) {
      text = "SVG Format";
      checked = perspecParams.svgOutput;
      onCheck = function (checked) {
         perspecParams.svgOutput = checked;
         this.dialog.svgFileName_Label.enabled = checked;
         this.dialog.svgFileName_Edit.enabled = checked;
         this.dialog.svgFileName_Button.enabled = checked;
      };
      toolTip = "<p>Select this option to generate the 3D rendition in Scalable Vector Graphics " +
                "format (SVG). Otherwise the rendition will be generated as a new image window.</p>" +
                "<p><b>* Warning *</b> SVG files can be very large even for relatively small images.</p>";
   }

   this.svgOutput_Sizer = new HorizontalSizer;
   this.svgOutput_Sizer.addSpacing( labelWidth1+4 );
   this.svgOutput_Sizer.add( this.svgOutput_CB );
   this.svgOutput_Sizer.addStretch();

   // SVG Filename

   this.svgFileName_Label = new Label( this );
   with ( this.svgFileName_Label ) {
      text = "SVG output file:";
      textAlignment = TextAlign_Right|TextAlign_VertCenter;
      minWidth = labelWidth1;
      enabled = perspecParams.svgOutput;
   }

   this.svgFileName_Edit = new Edit( this );
   with ( this.svgFileName_Edit ) {
      text = perspecParams.svgFileName;
      enabled = perspecParams.svgOutput;
      onEditCompleted = function() { perspecParams.svgFileName = this.text; };
   }

   this.svgFileName_Button = new ToolButton( this );
   with ( this.svgFileName_Button ) {
      icon = new Bitmap( ":/images/icons/select.png" );
      toolTip = "<p>Select a SVG output file.</p>";
      enabled = perspecParams.svgOutput;
      onClick = function() {
         var gdd = new SaveFileDialog;
         gdd.initialPath = perspecParams.svgFileName;
         gdd.caption = "Select SVG Output File";
         gdd.overwritePrompt = true;
         gdd.filters = [["SVG Files", "*.svg"]];

         if ( gdd.execute() )
         {
            perspecParams.svgFileName = gdd.fileName;
            this.dialog.svgFileName_Edit.text = perspecParams.svgFileName;
         }
      };
   }

   this.svgFileName_Sizer = new HorizontalSizer;
   with ( this.svgFileName_Sizer ) {
      spacing = 4;
      add( this.svgFileName_Label );
      add( this.svgFileName_Edit, 100 );
      add( this.svgFileName_Button );
   }

   // azimuth
   this.azimuth_NC = new NumericControl (this);
   with (this.azimuth_NC) {
      real = false;
      label.text = "Azimuth:";
      label.minWidth = labelWidth1;
      setRange (0, 90);
      slider.setRange (0, 90);
      slider.minWidth = 250;
      edit.minWidth = editWidth1;;
      setValue (perspecParams.azimuth);
      toolTip = "<p>Azimuth angle in degrees.</p>";
      onValueUpdated = function (value) {
        perspecParams.azimuth = value;
      }
   }

   // elevation
   this.elevation_NC = new NumericControl (this);
   with (this.elevation_NC) {
      real = false;
      label.text = "Elevation:";
      label.minWidth = labelWidth1;
      setRange (0, 90);
      slider.setRange (0, 90);
      slider.minWidth = 250;
      edit.minWidth = editWidth1;;
      setValue (perspecParams.elevation);
      toolTip = "<p>Observer's elevation angle above the ground, in degrees.</p>";
      onValueUpdated = function (value) {
         perspecParams.elevation = value;
      }
   }

   // scaleXY
   this.scaleXY_NC = new NumericControl (this);
   with (this.scaleXY_NC) {
      real = false;
      label.text = "X-Y plane scale:";
      label.minWidth = labelWidth1;
      setRange (1, 10);
      slider.setRange (1, 10);
      slider.minWidth = 250;
      edit.minWidth = editWidth1;;
      setValue (perspecParams.scaleXY);
      toolTip = "<p>Scale of X and Y coordinates in rendition pixels per image pixels.</p>";
      onValueUpdated = function (value) {
         perspecParams.scaleXY = value;
      }
   }

   // scaleZ
   this.scaleZ_NC = new NumericControl (this);
   with (this.scaleZ_NC) {
      real = false;
      label.text = "Z-axis scale:";
      label.minWidth = labelWidth1;
      setRange (1, 100);
      slider.setRange (1, 100);
      slider.minWidth = 250;
      edit.minWidth = editWidth1;;
      setValue (perspecParams.scaleZ);
      toolTip = "<p>Scaling factor of Z-axis coordinates.</p>";
      onValueUpdated = function (value) {
         perspecParams.scaleZ = value;
      }
   }

   // mtf
   /*this.mtf_NC = new NumericControl (this);
   with (this.mtf_NC) {
      label.text = "Midtones balance:";
      label.minWidth = labelWidth1;
      setRange (0, 1);
      slider.setRange (1, 500);
      slider.minWidth = 250;
      setPrecision (3);
      edit.minWidth = editWidth1;;
      setValue (perspecParams.mtf);
      toolTip = "<p>This parameter is part of a histogram transformation applied to " +
                "improve faint detail visualization.</p>";
      onValueUpdated = function (value) {
         perspecParams.mtf = value;
      }
   }*/

   // lightBrightness
   this.brightness_NC = new NumericControl (this);
   with (this.brightness_NC) {
      real = false;
      label.text = "Brightness:";
      label.minWidth = labelWidth1;
      setRange (100, 5000);
      slider.setRange (1, 50);
      slider.minWidth = 250;
      edit.minWidth = editWidth1;;
      setValue (perspecParams.lightBrightness);
      toolTip = "<p>Intensity of incident light.</p>";
      onValueUpdated = function (value) {
         perspecParams.lightBrightness = value;
      }
   }

   // useShading
   this.shading_CB = new CheckBox (this);
   with (this.shading_CB) {
      text = "Use shading";
      checked = perspecParams.useShading;
      onCheck = function (checked) { perspecParams.useShading = checked; }
      toolTip = "<p>If this option is selected, a special shading algorithm will be used " +
                "to enhance the 3-D rendition.</p>";
   }

   this.shading_Sizer = new HorizontalSizer;
   this.shading_Sizer.addSpacing( labelWidth1+4 );
   this.shading_Sizer.add( this.shading_CB );
   this.shading_Sizer.addStretch();

   // background color

   this.backgroundColor_Label = new Label( this );
   this.backgroundColor_Label.text = "Background color:";
   this.backgroundColor_Label.textAlignment = TextAlign_Right|TextAlign_VertCenter;
   this.backgroundColor_Label.minWidth = labelWidth1;

   this.backgroundColor_Edit = new Edit( this );
   this.backgroundColor_Edit.text = format( "%X", perspecParams.backgroundColor );
   this.backgroundColor_Edit.setFixedWidth( editWidth2 );
   this.backgroundColor_Edit.toolTip = "<p>Background color encoded as a 32-bit hexadecimal integer.<br/>" +
                                "(AARRGGBB format: AA=alpha (transparency), RR=red, GG=green, BB=blue)</p>";
   this.backgroundColor_Edit.onEditCompleted = function()
   {
      perspecParams.backgroundColor = parseInt( this.text, 16 );
      this.text = format( '%X', perspecParams.backgroundColor );
   };

   this.backgroundColor_Sizer = new HorizontalSizer;
   this.backgroundColor_Sizer.spacing = 4;
   this.backgroundColor_Sizer.add( this.backgroundColor_Label );
   this.backgroundColor_Sizer.add( this.backgroundColor_Edit );
   this.backgroundColor_Sizer.addStretch();

   // polygon fill color

   this.polygonFill_Label = new Label( this );
   this.polygonFill_Label.text = "Polygon fill color:";
   this.polygonFill_Label.textAlignment = TextAlign_Right|TextAlign_VertCenter;
   this.polygonFill_Label.minWidth = labelWidth1;

   this.polygonFill_Edit = new Edit( this );
   this.polygonFill_Edit.text = format( "%X", perspecParams.polygonFill );
   this.polygonFill_Edit.setFixedWidth( editWidth2 );
   this.polygonFill_Edit.toolTip = "<p>Polygon fill color encoded as a 32-bit hexadecimal integer.<br/>" +
                                "(AARRGGBB format: AA=alpha (transparency), RR=red, GG=green, BB=blue)</p>";
   this.polygonFill_Edit.onEditCompleted = function()
   {
      perspecParams.polygonFill = parseInt( this.text, 16 );
      this.text = format( '%X', perspecParams.polygonFill );
   };

   this.polygonFill_Sizer = new HorizontalSizer;
   this.polygonFill_Sizer.spacing = 4;
   this.polygonFill_Sizer.add( this.polygonFill_Label );
   this.polygonFill_Sizer.add( this.polygonFill_Edit );
   this.polygonFill_Sizer.addStretch();

   // polygon border color

   this.polygonBorder_Label = new Label( this );
   this.polygonBorder_Label.text = "Polygon border color:";
   this.polygonBorder_Label.textAlignment = TextAlign_Right|TextAlign_VertCenter;
   this.polygonBorder_Label.minWidth = labelWidth1;

   this.polygonBorder_Edit = new Edit( this );
   this.polygonBorder_Edit.text = format( "%X", perspecParams.polygonBorder );
   this.polygonBorder_Edit.setFixedWidth( editWidth2 );
   this.polygonBorder_Edit.toolTip = "<p>Polygon border color encoded as a 32-bit hexadecimal integer.<br/>" +
                                "(AARRGGBB format: AA=alpha (transparency), RR=red, GG=green, BB=blue)</p>";
   this.polygonBorder_Edit.onEditCompleted = function()
   {
      perspecParams.polygonBorder = parseInt( this.text, 16 );
      this.text = format( '%X', perspecParams.polygonBorder );
   };

   this.polygonBorder_Sizer = new HorizontalSizer;
   this.polygonBorder_Sizer.spacing = 4;
   this.polygonBorder_Sizer.add( this.polygonBorder_Label );
   this.polygonBorder_Sizer.add( this.polygonBorder_Edit );
   this.polygonBorder_Sizer.addStretch();

   // ### TODO: Better color selection controls - Include a ColorSelectionDialog object in PJSR?

   // ### TODO: Allow selecting palettes

   // buttons
   this.ok_Button = new PushButton (this);
   this.ok_Button.text = "OK";
   this.ok_Button.onClick = function() { 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 = 8;
   this.buttons_Sizer.addStretch();
   this.buttons_Sizer.add (this.ok_Button);
   this.buttons_Sizer.add (this.cancel_Button);

   // pack everything
   this.sizer = new VerticalSizer;
   with (this.sizer) {
      margin = 8;
      spacing = 6;
      add (this.helpLabel);
      addSpacing (4);
      add (this.srcImage_Sizer);
      add (this.svgOutput_Sizer);
      add (this.svgFileName_Sizer);
      add (this.azimuth_NC);
      add (this.elevation_NC);
      add (this.scaleXY_NC);
      add (this.scaleZ_NC);
      //add (this.mtf_NC);
      add (this.brightness_NC);
      add (this.shading_Sizer);
      add (this.backgroundColor_Sizer);
      add (this.polygonFill_Sizer);
      add (this.polygonBorder_Sizer);
      add (this.buttons_Sizer);
   }

   this.windowTitle = TITLE + " v" + VERSION;
   this.adjustToContents();
   this.setFixedSize();
}
_3dplot_dialog.prototype = new Dialog;

function FullViewIdAsValidViewId( fullId )
{
   return fullId.replace( "->", "_" );
}

function main()
{
   if(ImageWindow.activeWindow.isNull)
      DieError("There is no active image");

   console.hide();

   for ( ;; )
   {
      if (!(new _3dplot_dialog).execute())
         return;

      if ( !perspecParams.srcView.isNull )
         break;

      (new MessageBox( "No source view has been selected!",
                       TITLE, StdIcon_Error, StdButton_Ok )).execute();
   }

   console.show();
   console.writeln( "<end><cbr><br>***** 3D Profiling Script *****" );
   console.flush();

   var startts = new Date;

   // Extract the luminance of the source image
   var src = new Image();
   perspecParams.srcView.image.extractLuminance(src);

   ApplySTF(src, perspecParams.srcView.stf);

   // Allow the user to abort this script
   console.abortEnabled = true;

   // Allow status monitoring for our working image
   src.statusEnabled = true;

   // Create the perspective
   var xform=new _3dplot_xform(src, perspecParams);

   try
   {
      var width = Math.round( xform.limits.width );
      var height = Math.round( xform.limits.height );

      if ( perspecParams.svgOutput )
      {
         console.writeln( "<end><cbr>Generating SVG output: ", perspecParams.svgFileName );
         var svg = new SVG( perspecParams.svgFileName, width, height );
         RenderSVG( src, svg, xform.limits, xform.points, perspecParams );
      }
      else
      {
         var newid = FullViewIdAsValidViewId( perspecParams.srcView.fullId ) + "_3dplot";
         console.writeln( "<end><cbr>Generating image output: ", newid );
         var isTransparent = (perspecParams.backgroundColor & 0xff000000) != 0xff000000;
         var w = new ImageWindow( width, height, isTransparent ? 4 : 3, 8, false, true, newid );
         var v = w.mainView;
         var i = v.image;

         v.beginProcess( UndoFlag_NoSwapFile );
         RenderImage( src, i, xform.limits, xform.points, perspecParams );
         v.endProcess();

         w.show();
      }

      var endts = new Date;
      console.writeln( format( "<end><cbr>3D view: %.2f s", (endts.getTime() - startts.getTime())/1000 ) );
   }
   catch(err)
   {
      console.writeln( err );
   }
}

main();

// ****************************************************************************
// EOF pjsr/3DPlot.js - Released 2009/06/05 15:52:25 UTC
Title: Re: 3D star profile / Perfil estelar en 3D (BETA)
Post by: Nocturnal on 2009 June 11 10:33:55
So who's going to add some code to allow the image to be rotated and tilted with the mouse? :)
Title: Re: 3D star profile / Perfil estelar en 3D (BETA)
Post by: Andres.Pozo on 2009 June 11 11:38:46
So who's going to add some code to allow the image to be rotated and tilted with the mouse? :)

Easy. Juan only would have to add a Javascript encapsulation of OpenGL  :P :P
Title: Re: 3D star profile / Perfil estelar en 3D (BETA)
Post by: Juan Conejero on 2009 June 11 15:32:12
Quote
Easy. Juan only would have to add a Javascript encapsulation of OpenGL  tongueout tongueout

Aha. Nice try  :cheesy:

Actually, that is perfectly doable with the current Graphics object  >:D