Author Topic: Cosmetic Correction script  (Read 17553 times)

Offline NKV

  • PTeam Member
  • PixInsight Guru
  • ****
  • Posts: 677
Cosmetic Correction script
« on: 2010 April 11 06:23:07 »
Hi all,
the script allows you to use bad_pixel_map_image (or MasterDark like bad pixel map) to correct defective pixels.
Especially if some unstable hot pixels is not disappear after ImageCalibration.
Dialog screen and demonstration gif in attachment.
PS The script working like in MaximDL, but little bit better, because is CFA ready ;)

Code: [Select]
#define VERSION   1.5
#define TITLE     CosmeticCorrection

#feature-id    Utilities > CosmeticCorrection

#feature-info A cosmetic correction utility.<br/>\
   <br/> \
   The script replace value of bad pixels on averaged value from appropriate near pixels. \
   Script required map of defective pixels or MasterDark file. <br/>\
   <br/> \
   Copyright (C) 2009 Nikolay Volkov

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


function CosmeticCorrectionDialog()
{
   this.__base__ = Dialog;
   this.__base__();
   this.windowTitle = #TITLE + " Script";

   var DeadThreshold = 0.0;
   var DeadCount = 0;
   var HotThreshold = 1;
   var HotCount = 1000;
   var TransferFunction = 1.0;
   var CFA = false;
   var MasterDark = "";
   var inputFiles = new Array();
   var outputDirectory = "";
   var postfix = "_cc";
   var masterDarkImage = ImageWindow();
   var h = new Histogram ();

   if ( Parameters.isGlobalTarget || Parameters.isViewTarget )
   {
      DeadThreshold = Parameters.getReal( "DeadThreshold" );
      DeadCount = Parameters.getUInt( "DeadCount" );
      HotThreshold = Parameters.getReal( "HotThreshold" );
      HotCount = Parameters.getUInt( "HotCount" );
      TransferFunction = Parameters.getReal( "TransferFunction" );
      CFA = Parameters.getBoolean( "CFA" );
      MasterDark = Parameters.get( "MasterDark" );
      outputDirectory = Parameters.get( "outputDirectory" );
      postfix = Parameters.get( "postfix" );
      for ( var i = 0; Parameters.has( "File_"+i) ; ++i )
      {
         inputFiles[i] = Parameters.getString( "File_"+i);
         Parameters.remove( "File_"+i)
      }
      if (MasterDark) masterDarkImage = ImageWindow.open( MasterDark )[0];
   }

   this.exportParameters = function()
   {
      Parameters.set( "DeadThreshold", DeadThreshold );
      Parameters.set( "DeadCount", DeadCount );
      Parameters.set( "HotThreshold", HotThreshold );
      Parameters.set( "HotCount", HotCount );
      Parameters.set( "TransferFunction", TransferFunction );
      Parameters.set( "CFA", CFA );
      Parameters.set( "MasterDark", MasterDark );
      Parameters.set( "outputDirectory", outputDirectory );
      Parameters.set( "postfix", postfix );
      for ( var i = 0; i < inputFiles.length; ++i ) Parameters.set( "File_"+i, inputFiles[i] );
   }

   //Header --------------------------------------------------------------------------------------
   this.helpLabel = new Label( this );
   with ( this.helpLabel )
   {
      frameStyle = FrameStyle_Sunken;
      margin = 4;
      wordWrapping = true;
      useRichText = true;
      text = "<p><b>" + #TITLE + " v" + #VERSION +
             "</b> &mdash; This script replaces Target pixel value with an average " +
             "value of 8 neighbor pixels in case its corresponding MasterDark " +
             "pixel value lies outside of the threshold boundary.";
   }


   // Input File List -----------------------------------------------------------------------------------
   this.files_TreeBox = new TreeBox( this );
   with ( this.files_TreeBox )
   {
      rootDecoration = false;
      numberOfColumns = 1;
      multipleSelection = true;
      headerVisible = false;
      for ( var i = 0; i < inputFiles.length; ++i )
      {
         var node = new TreeBoxNode( this.dialog.files_TreeBox );
         node.setText( 0, inputFiles[i] );
      }
     
      onNodeDoubleClicked = function()
      {
         var f = ImageWindow.open( currentNode.text(0) )[0];
         f.show();
      }
   }

   this.filesAdd_Button = new PushButton( this );
   this.filesAdd_Button.text = " Add ";
   this.filesAdd_Button.toolTip = "<p>Add image files to the input images list.</p>";
   this.filesAdd_Button.onClick = function()
   {
      var ofd = new OpenFileDialog;
      ofd.multipleSelections = true;
      ofd.caption = "Select Images";
      ofd.loadImageFilters();
      if ( ofd.execute() )
      {
         this.dialog.files_TreeBox.canUpdate = false;
         for ( var i = 0; i < ofd.fileNames.length; ++i )
         {
            var node = new TreeBoxNode( this.dialog.files_TreeBox );
            node.setText( 0, ofd.fileNames[i] );
            inputFiles.push( ofd.fileNames[i] );
         }
         this.dialog.files_TreeBox.canUpdate = true;
         this.dialog.controlEnable();
      }
   };

   this.filesClear_Button = new PushButton( this );
   this.filesClear_Button.text = " Clear ";
   this.filesClear_Button.toolTip = "<p>Clear the list of input images.</p>";
   this.filesClear_Button.onClick = function()
   {
      this.dialog.files_TreeBox.clear();
      inputFiles.length = 0;
      this.dialog.controlEnable();
   };

   this.filesInvert_Button = new PushButton( this );
   this.filesInvert_Button.text = " Invert Selection ";
   this.filesInvert_Button.toolTip = "<p>Invert the current selection of input images.</p>";
   this.filesInvert_Button.onClick = function()
   {
      for ( var i = 0; i < this.dialog.files_TreeBox.numberOfChildren; ++i )
         this.dialog.files_TreeBox.child( i ).selected =
               !this.dialog.files_TreeBox.child( i ).selected;
   };

   this.filesRemove_Button = new PushButton( this );
   this.filesRemove_Button.text = " Remove Selected ";
   this.filesRemove_Button.toolTip = "<p>Remove all selected images from the input images list.</p>";
   this.filesRemove_Button.onClick = function()
   {
      inputFiles.length = 0;
      for ( var i = 0; i < this.dialog.files_TreeBox.numberOfChildren; ++i )
         if ( !this.dialog.files_TreeBox.child( i ).selected )
            inputFiles.push( this.dialog.files_TreeBox.child( i ).text( 0 ) );
      for ( var i = this.dialog.files_TreeBox.numberOfChildren; --i >= 0; )
         if ( this.dialog.files_TreeBox.child( i ).selected )
            this.dialog.files_TreeBox.remove( i );
      this.dialog.controlEnable();
   };


   //Output --------------------------------------------------------------------------------------
   this.outputDir_Edit = new Edit( this );
   this.outputDir_Edit.readOnly = true;
   this.outputDir_Edit.text = outputDirectory;
   this.outputDir_Edit.toolTip =
      "<p>If specified, all corrected images will be written to the output directory.</p>" +
      "<p>If not specified, corrected images will be written to the same directories " +
      "of their corresponding input images.</p>";

   this.outputDirSelect_Button = new ToolButton( this );
   with ( this.outputDirSelect_Button )
   {
      icon = new Bitmap( ":/images/icons/select.png" );
      toolTip = "<p>Select the output directory.</p>";
      onClick = function()
      {
         var gdd = new GetDirectoryDialog;
         gdd.initialPath = outputDirectory;
         gdd.caption = "Select Output Directory";
         if ( gdd.execute() )
         {
            outputDirectory = gdd.directory;
            this.dialog.outputDir_Edit.text = outputDirectory;
         }
      }
   }

   this.postfixLabel = new Label( this );
   with ( this.postfixLabel )
   {
      text = "Postfix: ";
      textAlignment = TextAlign_VertCenter;
   }

   this.postfixEdit = new Edit( this );
   with ( this.postfixEdit )
   {
      text = postfix;
      setFixedWidth( this.font.width( "MMM" ) );
      toolTip = "<p>This is a postfix that will be appended to the file name.</p>";
      onEditCompleted = function() postfix = this.text;
   }


   //MasterDark image --------------------------------------------------------------------------------------

   this.MasterDark_Label = new Edit( this );
   with ( this.MasterDark_Label )
   {
      if (MasterDark) text = File.extractName(MasterDark)
      else text = "Select MasterDark";
      readOnly = true;
   }

   this.MasterDark_ViewList = new ToolButton( this );
   with ( this.MasterDark_ViewList )
   {
      icon = new Bitmap( ":/images/icons/select.png" );
      toolTip = "Select MasterDark";
      onClick = function()
      {
         var ofd = new OpenFileDialog;
         ofd.multipleSelections = false;
         ofd.caption = "Select MasterDark";
         ofd.loadImageFilters();
         if (ofd.execute())
         {
            if (MasterDark == ofd.fileNames[0]) return;
            if (MasterDark) masterDarkImage.forceClose();
            MasterDark = ofd.fileNames[0];
            parent.MasterDark_Label.text = File.extractName(MasterDark);
            masterDarkImage = ImageWindow.open( MasterDark )[0];
            parent.controlEnable();
         }
      }
   }


   // DeadPixel sliders ------------------------------------------------------------------------
    this.DeadThresholdControl = new NumericControl( this );
    with (this.DeadThresholdControl)
    {
      label.text = "Threshold:";
      toolTip = "Define dead pixel clipping value";
      setRange( 0, 1 );
      slider.setRange(0,65536);
      slider.minWidth = 200;
      setPrecision( 8 );
      setValue (DeadThreshold);
      onValueUpdated = function( value )
      {
         DeadThreshold = value;
         var count=0;
         for (var i=0; i < h.histogramLevel(value); i++)count +=h.count(i);
         dialog.DeadCountControl.setValue(count);
         DeadCount = count;
      }
    }

   this.DeadCountControl = new NumericControl( this );
   with (this.DeadCountControl)
   {
      label.text = "Count:";
      toolTip = "How many dead pixel cipped ?";
      setRange( 0, 10000 );
      slider.setRange(0,10000 );
      slider.minWidth = 200;
      setPrecision( 0 );
      setValue (DeadCount);
      onValueUpdated = function( value )
      {
         DeadCount = value;
         DeadThreshold = h.normalizedClipLow(value);
         dialog.DeadThresholdControl.setValue(DeadThreshold);
      }
   }

   this.Dead_GroupBox = new GroupBox( this );
   with (this.Dead_GroupBox)
   {
      title = "Dead Pixel";
      sizer = new VerticalSizer;
      sizer.margin = 4;
      sizer.spacing = 4;
      sizer.add( this.DeadCountControl );
      sizer.add( this.DeadThresholdControl );
   }


   // HotPixel sliders ----------------------------------------------------------------------------
    this.HotThresholdControl = new NumericControl( this );
    with (this.HotThresholdControl)
    {
      label.text = "Threshold:";
      toolTip = "Define hot pixel clipping value";
      setRange( 0, 1 );
      slider.setRange(0,65536 );
      slider.minWidth = 200;
      setPrecision( 8 );
      setValue (HotThreshold);
      onValueUpdated = function( value )
      {
         HotThreshold = value;
         var count=0;
         for (var i=h.histogramLevel(value); i < h.lastLevel; i++)count +=h.count(i);
         dialog.HotCountControl.setValue(count);
         HotCount = count;
      }
    }

   this.HotCountControl = new NumericControl( this );
   with (this.HotCountControl)
   {
      label.text = "Count:";
      toolTip = "How many hot pixel cipped ?";
      setRange( 0, 10000 );
      slider.setRange(0,10000 );
      slider.minWidth = 200;
      setPrecision( 0 );
      setValue (HotCount);
      onValueUpdated = function( value )
      {
         HotCount = value;
         HotThreshold = h.normalizedClipHigh(value);
         dialog.HotThresholdControl.setValue(HotThreshold);
      }
   }

   this.Hot_GroupBox = new GroupBox( this );
   with (this.Hot_GroupBox)
   {
      title = "Hot Pixel";
      sizer = new VerticalSizer;
      sizer.margin = 4;
      sizer.spacing = 4;
      sizer.add( this.HotCountControl );
      sizer.add( this.HotThresholdControl );
   }


   // Transfer function --------------------------------------------------------------------------------------
   this.TransferFunction = new NumericControl(this);
   with (this.TransferFunction)
   {
      label.text = "Transfer function:";
      toolTip = "<p>0   = no correction.<br/>" +
                "0.5 = 50% Original + 50% corrected.<br/>" +
                "1   = 100% corrected.<p>";
      setRange( 0, 1 );
      slider.setRange(0,1000000 );
      slider.minWidth = 200;
      setPrecision( 8 );
      setValue (TransferFunction);
      onValueUpdated = function( value ) TransferFunction = value;
   }


   // NewInstance button --------------------------------------------------------------------------------------
   this.newInstance_Button = new ToolButton( this );
   with ( this.newInstance_Button )
   {
      icon = new Bitmap( ":/images/interface/dragObject.png" );
      toolTip = "New Instance";
      onMousePress = function()
      {
         this.hasFocus = true;
         parent.exportParameters();
         this.pushed = false;
         parent.newInstance();
      }
   }


   // CFA CheckBox --------------------------------------------------------------------------------------
   this.CFA_CheckBox = new CheckBox( this );
   with ( this.CFA_CheckBox )
   {
      text = "CFA";
      checked = CFA;
      toolTip = "Check if image is CFA. UnCheck if image from Monohrom imager";
      onCheck = function( checked ) CFA = checked;
   }


   // Standart buttons --------------------------------------------------------------------------------------
   this.ok_Button = new PushButton( this );
   with ( this.ok_Button )
   {
      text = "OK";
      onClick = function()
      {
         if (!MasterDark) return;
         parent.apply();
         this.dialog.ok();
      }
   }


   // Sizer ---------------------------------------------------------------------------------------
   this.filesButtons_Sizer = new HorizontalSizer;
   with (this.filesButtons_Sizer)
   {
      spacing = 4;
      add( this.filesAdd_Button );
      addStretch();
      add( this.filesClear_Button );
      addStretch();
      add( this.filesInvert_Button );
      add( this.filesRemove_Button );
   }

   this.outputDir_GroupBox = new GroupBox( this );
   with (this.outputDir_GroupBox)
   {
      title = "Output";
      sizer = new HorizontalSizer;
      sizer.margin = 6;
      sizer.spacing = 4;
      sizer.add( this.outputDir_Edit, 100 );
      sizer.add( this.outputDirSelect_Button );
      sizer.add( this.postfixLabel );
      sizer.add( this.postfixEdit);

   }

   this.MasterDark_Sizer = new HorizontalSizer;
   with ( this.MasterDark_Sizer )
   {
      spacing = 4;
      add( this.MasterDark_Label );
      add( this.MasterDark_ViewList,1);
   }

   this.buttons_Sizer = new HorizontalSizer;
   with ( this.buttons_Sizer )
   {
      add( this.newInstance_Button );
      addStretch();
      add( this.CFA_CheckBox,1 );
      spacing = 92;
      add( this.ok_Button );
   }

   this.sizer = new VerticalSizer;
   with ( this.sizer )
   {
      margin = 6;
      spacing = 4;
      add( this.helpLabel );
      add( this.files_TreeBox );
      add( this.filesButtons_Sizer );
      add( this.outputDir_GroupBox );
      add( this.MasterDark_Sizer );
      add( this.Dead_GroupBox );
      add( this.Hot_GroupBox );
      add( this.TransferFunction );
      add( this.buttons_Sizer );
   }

   this.adjustToContents();
   this.setFixedSize();


   this.controlEnable = function ()
   {
      var e = Boolean(MasterDark);
      with (this.dialog)
      {
         DeadThresholdControl.enabled=e;
         DeadCountControl.enabled=e;
         HotThresholdControl.enabled=e;
         HotCountControl.enabled=e;
         newInstance_Button.enabled=e;
         if ( inputFiles.length > 0 ) ok_Button.enabled=e;
         else ok_Button.enabled=false;
         if (e)
         {
            h.generate(masterDarkImage.mainView.image);
            HotCountControl.onValueUpdated(HotCount);
            DeadCountControl.onValueUpdated(DeadCount);
         }
      }
   }
   this.controlEnable();

   this.engine = function(LightImage)
   {
      var img = LightImage.mainView.image;
      var TempWin = new ImageWindow( img.width, img.height, img.numberOfChannels,
                          img.bitsPerSample, img.isReal, img.isColor, "Convolved");
      var TempView = TempWin.mainView;
      with (TempView)
      {
         beginProcess( UndoFlag_NoSwapFile );
         console.writeln( "Processing CFA:", CFA );
         image.apply( img );
         if (CFA)
            image.convolve([ 1,0,1,0,1,
                             0,0,0,0,0,
                             1,0,0,0,1,
                             0,0,0,0,0,
                             1,0,1,0,1 ]);
         else
            image.convolve([ 1,1,1,
                             1,0,1,
                             1,1,1 ]);
         endProcess();
      }

      var MDWin = masterDarkImage;
      // PixelMath iif ( (MasterDark > HotPixelThreshold) or(MasterDark < DeadPixelThreshold) , (Convolved*x+targetView*(1-x)) , targetView )
      var mergeThem = new PixelMath;
      with (mergeThem)
      {
         expression = "iif((" + MDWin.mainView.id + ">" + HotThreshold + ") || (" + MDWin.mainView.id + "<" + DeadThreshold
                     + "),(" +TempWin.mainView.id +"*"+TransferFunction+" + " + LightImage.mainView.id +"*( 1 - " + TransferFunction + ")),"+LightImage.mainView.id+")";
         useSingleExpression = true;
         rescale = false;

         if ( LightImage.visible) // execute on Image on the screen
         {  // create new image with STF
            createNewImage = true;
            newImageId = LightImage.mainView.id + postfix;
            executeOn( LightImage.mainView, false );
            var stf = LightImage.currentView.stf;
            ImageWindow.windowById(newImageId).currentView.stf = stf ;
         }
         else
         {
            executeOn( LightImage.mainView, false ); // execute on ImageContainer or FileList
         }
      }
      TempWin.close();
   }

   this.apply = function()
   {
      console.show();
      for ( var i = 0; i < inputFiles.length; ++i )
      {
         var LightImage = ImageWindow.open( inputFiles[i] )[0];
         this.engine(LightImage);
         if (outputDirectory) var outputFilePath = outputDirectory;
         else var outputFilePath = File.extractDrive(inputFiles[i]) + File.extractDirectory(inputFiles[i]);
         outputFilePath += "/" + File.extractName(inputFiles[i]) + postfix +  File.extractExtension(inputFiles[i]);
         if ( File.exists( outputFilePath ) )
            for ( var u = 1; ; ++u )
            {
               var tryFilePath = File.appendToName( outputFilePath, u.toString() );
               if ( !File.exists( tryFilePath ) ) { outputFilePath = tryFilePath; break; }
            }
         LightImage.saveAs( outputFilePath, false, false, false, false );
         LightImage.forceClose();
      }
   }

   this.applyToVievTarget = function()
   {
      var LightImage = ImageWindow.windowById(Parameters.targetView.id);
      this.engine(LightImage);
      this.onHide();
   }
   
   this.onHide = function()
   {
      if (!MasterDark) return;
      if (!masterDarkImage.isNull) masterDarkImage.forceClose();
   }
}


CosmeticCorrectionDialog.prototype = new Dialog;
var dialog = new CosmeticCorrectionDialog;
if ( Parameters.isViewTarget ) dialog.applyToVievTarget();
else
{
   console.hide();
   dialog.execute();
}
« Last Edit: 2010 June 24 04:15:12 by NKV »

Offline mmirot

  • PixInsight Padawan
  • ****
  • Posts: 881
Re: Cosmetic Correction script
« Reply #1 on: 2010 April 11 08:14:03 »


Thanks for this addition.  Can the map be saved? Can this be applied to multiple images?

Btw, I agree the whole calibration process is largely unfinished.
I am not sure it why it was release without any features,  ie bad pixel, maps, master frame generation and a way to automated the output into registration etc.

Max

Offline NKV

  • PTeam Member
  • PixInsight Guru
  • ****
  • Posts: 677
Re: Cosmetic Correction script
« Reply #2 on: 2010 April 11 08:28:12 »
Can this be applied to multiple images?
Of course YES. Just drop that small blue triangle to desktop (it's create ProcessInstance) and drop small blue triangle from ImageContainer over just created Instance icon.

As Juan say: here starts the real fun. Do you see that small blue triangle at the bottom left corner. Isn't it a nice decoration? Well, not quite a decorative item, actually: yes, a script can now generate new instances, just as a regular interface does in PixInsight.

Offline Juan Conejero

  • PTeam Member
  • PixInsight Jedi Grand Master
  • ********
  • Posts: 7111
    • http://pixinsight.com/
Re: Cosmetic Correction script
« Reply #3 on: 2010 April 11 12:27:57 »
Hi Nikolay,

Excellent! This is a nice little script that can be very useful. Thank you for sharing it ;)

Max,

Quote
Btw, I agree the whole calibration process is largely unfinished.
I am not sure it why it was release without any features,  ie bad pixel, maps, master frame generation and a way to automated the output into registration etc.

The current ImageCalibration process is just the initial version of this tool. It is an open tool released under GPL V3. I definitely want this tool to be the result of a community collaboration. As it is right now, ImageCalibration is a solid foundation, but it is not finished at all.

Quote
master frame generation

PixInsight is a modular platform. As such, the general philosophy behind the platform is divide and conquer: Small tools that implement very specific tasks and can be pipelined through scripts, instead of monolithic tools that try to implement everything in an inflexible way. Master frame generation will be implemented as an independent tool.

Quote
a way to automate the output into registration

Again, this involves a different tool, for the same reason noted above. In this particular case, this task is an ideal candidate to be implemented as a script.

Patience :) We are doing the things well, and this is always incompatible with doing the things fast.
Juan Conejero
PixInsight Development Team
http://pixinsight.com/

Offline NKV

  • PTeam Member
  • PixInsight Guru
  • ****
  • Posts: 677
Re: Cosmetic Correction script
« Reply #4 on: 2010 April 12 01:52:01 »
Excellent! This is a nice little script that can be very useful. Thank you for sharing it ;)
:)

BTW, I update script code. Now CFA ready. See post #1.

Best regards,
Nikolay.
« Last Edit: 2010 April 12 06:10:48 by NKV »

Offline vicent_peris

  • PTeam Member
  • PixInsight Padawan
  • ****
  • Posts: 988
    • http://www.astrofoto.es/
Re: Cosmetic Correction script
« Reply #5 on: 2010 April 12 09:46:43 »
That's great, Nikolay!

I think this script MUST be implemented inside the IC module. It can be specially useful if you have very few dark and bias frames. In this situation, more hotter pixel remain somewhat under corrected. By regulating the threshold this can be perfectly corrected.

I don't know if the script does an average or a median of the adjacent pixels. I think, for this application, the best is an average. We would want a median to better correct impulsional noise. But, as we are just selecting those nasty pixels, an average is more rigorous IMHO.

Finally, I think this implementation would be far better than a cosmetic mask, as the problematic pixels are always selected from the same master frame with wich you are calibrating the images.


Regards,
Vicent.

Offline NKV

  • PTeam Member
  • PixInsight Guru
  • ****
  • Posts: 677
Re: Cosmetic Correction script
« Reply #6 on: 2010 April 13 02:51:15 »
Vicent, thank you.

I don't know if the script does an average or a median of the adjacent pixels.
The script does an average of the adjacent pixels. I hope... because it's doing via convolution. I don't know how to work a convolution. 8) But I understand that convolution spread every pixel in image to around by Matrix. So the Matrix is very important part of the script (see code).
Code: [Select]
            if (this.CFA)
               image.convolve([ 1,0,1,0,1,
                                0,0,0,0,0,
                                1,0,0,0,1,
                                0,0,0,0,0,
                                1,0,1,0,1 ]);
            else
               image.convolve([ 1,1,1,
                                1,0,1,
                                1,1,1 ]);

Quote
I think, for this application, the best is an average. We would want a median to better correct impulsional noise. But, as we are just selecting those nasty pixels, an average is more rigorous IMHO.
I got solution! So now, if you like, possible preserve some noise from original bad pixels.
For example we can totally remove very-very hot pixels in first run. And after that run script again with less aggressive transfer function, but more aggressive threshold. ;)

PS Code was updated. Added transfer function.  See post #1.

Offline NKV

  • PTeam Member
  • PixInsight Guru
  • ****
  • Posts: 677
Re: Cosmetic Correction script
« Reply #7 on: 2010 May 20 01:57:02 »
Code was updated.
New:
-added control of pixels quantity.
-new ImageWindow (with STF) if not via ImageContainer.

Changes:
-Improved speed.
-MasterDark should be open.

Offline Carlos Milovic

  • PTeam Member
  • PixInsight Jedi Master
  • ******
  • Posts: 2172
  • Join the dark side... we have cookies
    • http://www.astrophoto.cl
Re: Cosmetic Correction script
« Reply #8 on: 2010 May 20 06:19:40 »
Hi Nikolay

Great work :) Stay tunned, because we'll release the source code of the new DefectMap process, which is very similar to your script (although, it does not manages bayered images directly, but incorporates more structuring elements, convolutions and morphological operators). It will be an open source project (just like ImageCalibration and many other processes). It was supposed to be released last week, with the 1.6.1 core release, but since it was delayed I'm considering a "beta pre-release" this weekend.
Regards,

Carlos Milovic F.
--------------------------------
PixInsight Project Developer
http://www.pixinsight.com

Offline NKV

  • PTeam Member
  • PixInsight Guru
  • ****
  • Posts: 677
Re: Cosmetic Correction script
« Reply #9 on: 2010 May 20 06:42:11 »
Great work :) Stay tuned, because we'll release the source code of the new DefectMap process, which is very similar to your script
Good news. Nevertheless, I continue the experiment in script writing. I must get a minimum knowledge before they step into the jungle of PCL.

Carlos, Maybe you know how to stop (or generate an error in) ImageContainer script.

Best regards,
Nikolay.

Offline Carlos Milovic

  • PTeam Member
  • PixInsight Jedi Master
  • ******
  • Posts: 2172
  • Join the dark side... we have cookies
    • http://www.astrophoto.cl
Re: Cosmetic Correction script
« Reply #10 on: 2010 May 20 08:56:25 »
Hi Nikolay

My knowledge of javascript is very limited... I can read (and understand) it, but I never wrote one myself, so there is a lot that I just don't know how to do. Sorry.

BTW, another difference from the process and your script is that I assumed that the defect map has been already constructed, so it just uses it. That means, if anyone wants to use the master dark as defect map input, he should process it with other tools to include just the bad pixels.
I'll comment more details with the release (code and compilations for windows and linux, 32 & 64). I plan to do this tomorrow (it is a holyday in Chile, so I'll be home all day).
Regards,

Carlos Milovic F.
--------------------------------
PixInsight Project Developer
http://www.pixinsight.com

Offline NKV

  • PTeam Member
  • PixInsight Guru
  • ****
  • Posts: 677
Re: Cosmetic Correction script
« Reply #11 on: 2010 June 24 03:02:12 »
Cosmetic Correction v1.5

+ FileList (with add/clear/invert/remove)
+ OutputDir
+ postfix

If you prefer execute on ImageContainer, note:  OutputDir and postfix as defined in ImageContainer.

Best regards,
Nikolay.

Offline NKV

  • PTeam Member
  • PixInsight Guru
  • ****
  • Posts: 677
Re: Cosmetic Correction script
« Reply #12 on: 2011 October 09 05:28:38 »
Cosmetic Correction v1.6

+ BugFixed: wrong pixels count in RGB images.
+ BugFixed: CFA RGB zero pedestal.
+ Increased dialog responsibility and performance in RGB mode.

Best regards,
Nikolay.

Code: [Select]
#feature-id    Utilities > CosmeticCorrection

#feature-info A cosmetic correction utility.<br/>\
   <br/>\
   This script replaces bad pixel values (hot and cold pixels) with averaged values from \
   the appropriate neighbor pixels.<br/>\
   <br/>\
   The script requires a map image of defective pixels or a master dark frame. <br/>\
   <br/>\
   Copyright (C) 2009 - 2011 Nikolay Volkov

#define VERSION   1.6
#define TITLE     CosmeticCorrection

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


function CosmeticCorrectionDialog()
{
   this.__base__ = Dialog;
   this.__base__();
   this.windowTitle = #TITLE + " Script";

   var DeadThreshold = 0.0;
   var DeadCount = 0;
   var HotThreshold = 1;
   var HotCount = 1000;
   var TransferFunction = 1.0;
   var CFA = false;
   var MasterDark = "";
   var inputFiles = new Array();
   var outputDirectory = "";
   var postfix = "_cc";
   var masterDarkImage = ImageWindow();
   var h = new Histogram ();
   var histogram = new Array();
   var channels = 0 ;
   var minLevel = new Array();
   var maxLevel = new Array();


   if ( Parameters.isGlobalTarget || Parameters.isViewTarget )
   {
      DeadThreshold = Parameters.getReal( "DeadThreshold" );
      DeadCount = Parameters.getUInt( "DeadCount" );
      HotThreshold = Parameters.getReal( "HotThreshold" );
      HotCount = Parameters.getUInt( "HotCount" );
      TransferFunction = Parameters.getReal( "TransferFunction" );
      CFA = Parameters.getBoolean( "CFA" );
      MasterDark = Parameters.get( "MasterDark" );
      outputDirectory = Parameters.get( "outputDirectory" );
      postfix = Parameters.get( "postfix" );
      for ( var i = 0; Parameters.has( "File_"+i) ; ++i )
      {
         inputFiles[i] = Parameters.getString( "File_"+i);
         Parameters.remove( "File_"+i)
      }
      if (MasterDark) masterDarkImage = ImageWindow.open( MasterDark )[0];
   }

   this.exportParameters = function()
   {
      Parameters.set( "DeadThreshold", DeadThreshold );
      Parameters.set( "DeadCount", DeadCount );
      Parameters.set( "HotThreshold", HotThreshold );
      Parameters.set( "HotCount", HotCount );
      Parameters.set( "TransferFunction", TransferFunction );
      Parameters.set( "CFA", CFA );
      Parameters.set( "MasterDark", MasterDark );
      Parameters.set( "outputDirectory", outputDirectory );
      Parameters.set( "postfix", postfix );
      for ( var i = 0; i < inputFiles.length; ++i ) Parameters.set( "File_"+i, inputFiles[i] );
   }

   //Header --------------------------------------------------------------------------------------
   this.helpLabel = new Label( this );
   with ( this.helpLabel )
   {
      frameStyle = FrameStyle_Sunken;
      margin = 4;
      wordWrapping = true;
      useRichText = true;
      text = "<p><b>" + #TITLE + " v" + #VERSION +
             "</b> &mdash; This script replaces Target pixel value with an average " +
             "value of 8 neighbor pixels in case its corresponding MasterDark " +
             "pixel value lies outside of the threshold boundary.";
   }

   //
   var emWidth = this.font.width( 'M' );
   var labelWidth1 = this.font.width( "Threshold:" ) + emWidth;

   // Input File List -----------------------------------------------------------------------------------
   this.files_TreeBox = new TreeBox( this );
   with ( this.files_TreeBox )
   {
      rootDecoration = false;
      numberOfColumns = 1;
      multipleSelection = true;
      headerVisible = false;
      for ( var i = 0; i < inputFiles.length; ++i )
      {
         var node = new TreeBoxNode( this.dialog.files_TreeBox );
         node.setText( 0, inputFiles[i] );
      }

      onNodeDoubleClicked = function()
      {
         var f = ImageWindow.open( currentNode.text(0) )[0];
         f.show();
      }
   }

   this.filesAdd_Button = new PushButton( this );
   this.filesAdd_Button.text = "Add";
   this.filesAdd_Button.toolTip = "<p>Add image files to the input images list.</p>";
   this.filesAdd_Button.onClick = function()
   {
      var ofd = new OpenFileDialog;
      ofd.multipleSelections = true;
      ofd.caption = "Select Images";
      ofd.loadImageFilters();
      if ( ofd.execute() )
      {
         this.dialog.files_TreeBox.canUpdate = false;
         for ( var i = 0; i < ofd.fileNames.length; ++i )
         {
            var node = new TreeBoxNode( this.dialog.files_TreeBox );
            node.setText( 0, ofd.fileNames[i] );
            inputFiles.push( ofd.fileNames[i] );
         }
         this.dialog.files_TreeBox.canUpdate = true;
         this.dialog.controlEnable();
      }
   };

   this.filesClear_Button = new PushButton( this );
   this.filesClear_Button.text = "Clear";
   this.filesClear_Button.toolTip = "<p>Clear the list of input images.</p>";
   this.filesClear_Button.onClick = function()
   {
      this.dialog.files_TreeBox.clear();
      inputFiles.length = 0;
      this.dialog.controlEnable();
   };

   this.filesInvert_Button = new PushButton( this );
   this.filesInvert_Button.text = "Invert Selection";
   this.filesInvert_Button.toolTip = "<p>Invert the current selection of input images.</p>";
   this.filesInvert_Button.onClick = function()
   {
      for ( var i = 0; i < this.dialog.files_TreeBox.numberOfChildren; ++i )
         this.dialog.files_TreeBox.child( i ).selected =
               !this.dialog.files_TreeBox.child( i ).selected;
   };

   this.filesRemove_Button = new PushButton( this );
   this.filesRemove_Button.text = "Remove Selected";
   this.filesRemove_Button.toolTip = "<p>Remove all selected images from the input images list.</p>";
   this.filesRemove_Button.onClick = function()
   {
      inputFiles.length = 0;
      for ( var i = 0; i < this.dialog.files_TreeBox.numberOfChildren; ++i )
         if ( !this.dialog.files_TreeBox.child( i ).selected )
            inputFiles.push( this.dialog.files_TreeBox.child( i ).text( 0 ) );
      for ( var i = this.dialog.files_TreeBox.numberOfChildren; --i >= 0; )
         if ( this.dialog.files_TreeBox.child( i ).selected )
            this.dialog.files_TreeBox.remove( i );
      this.dialog.controlEnable();
   };


   //Output --------------------------------------------------------------------------------------
   this.outputDir_Edit = new Edit( this );
   this.outputDir_Edit.readOnly = true;
   this.outputDir_Edit.minWidth = 32*emWidth;
   this.outputDir_Edit.text = outputDirectory;
   this.outputDir_Edit.toolTip =
      "<p>If specified, all corrected images will be written to the output directory.</p>" +
      "<p>If not specified, corrected images will be written to the same directories " +
      "of their corresponding input images.</p>";

   this.outputDirSelect_Button = new ToolButton( this );
   with ( this.outputDirSelect_Button )
   {
      icon = new Bitmap( ":/images/icons/select.png" );
      toolTip = "<p>Select the output directory.</p>";
      onClick = function()
      {
         var gdd = new GetDirectoryDialog;
         gdd.initialPath = outputDirectory;
         gdd.caption = "Select Output Directory";
         if ( gdd.execute() )
         {
            outputDirectory = gdd.directory;
            this.dialog.outputDir_Edit.text = outputDirectory;
         }
      }
   }

   this.postfixLabel = new Label( this );
   with ( this.postfixLabel )
   {
      text = "Postfix:";
      textAlignment = TextAlign_Right|TextAlign_VertCenter;
   }

   this.postfixEdit = new Edit( this );
   with ( this.postfixEdit )
   {
      text = postfix;
      setFixedWidth( this.font.width( "MMM" ) );
      toolTip = "<p>This is a postfix that will be appended to the file names of output corrected images.</p>";
      onEditCompleted = function() postfix = this.text;
   }


   //MasterDark image --------------------------------------------------------------------------------------

   this.MasterDark_Label = new Edit( this );
   with ( this.MasterDark_Label )
   {
      if (MasterDark) text = File.extractName(MasterDark)
      else text = "Select MasterDark";
      readOnly = true;
   }

   this.MasterDark_ViewList = new ToolButton( this );
   with ( this.MasterDark_ViewList )
   {
      icon = new Bitmap( ":/images/icons/select.png" );
      toolTip = "<p>Select a master dark or defect map image.</p>";
      onClick = function()
      {
         var ofd = new OpenFileDialog;
         ofd.multipleSelections = false;
         ofd.caption = "Select MasterDark";
         ofd.loadImageFilters();
         if (ofd.execute())
         {
            if (MasterDark == ofd.fileNames[0]) return;
            if (MasterDark) masterDarkImage.forceClose();
            MasterDark = ofd.fileNames[0];
            parent.MasterDark_Label.text = File.extractName(MasterDark);
            masterDarkImage = ImageWindow.open( MasterDark )[0];
            parent.controlEnable();
         }
      }
   }


   // DeadPixel sliders ------------------------------------------------------------------------
    this.DeadThresholdControl = new NumericControl( this );
    with (this.DeadThresholdControl)
    {
      label.text = "Threshold:";
      label.minWidth = labelWidth1;
      label.textAlignment = TextAlign_Right|TextAlign_VertCenter;
      toolTip = "<p>Define the dead pixel clipping value.</p>";
      setRange( 0, 1 );
      slider.setRange(0,65535);
      slider.minWidth = 200;
      setPrecision( 8 );
      setValue (DeadThreshold);
      onValueUpdated = function( value )
      {
         DeadThreshold = value;
         var count=0;
         var histogramLevel = Math.round( DeadThreshold*65535 );
         for (var c=0; c < channels; c++)
         {
            var max = Math.min( histogramLevel, maxLevel[c] );
            var min = minLevel[c];
            if ( (CFA) && (min==0) && (channels>1) ) min++; //for RGB cfa;
            for ( var i = min; i < max; i++ ) count += histogram[c][i];
         }
         dialog.DeadCountControl.setValue(count);
         DeadCount = count;
      }
    }

   this.DeadCountControl = new NumericControl( this );
   with (this.DeadCountControl)
   {
      label.text = "Count:";
      label.minWidth = labelWidth1;
      label.textAlignment = TextAlign_Right|TextAlign_VertCenter;
      toolTip = "<p>How many dead pixels get clipped ?</p>";
      setRange( 0, 10000 );
      slider.setRange(0,10000 );
      slider.minWidth = 200;
      setPrecision( 0 );
      setValue (DeadCount);
      onValueUpdated = function( value )
      {
         DeadCount = value;
         if ( channels > 1 )
         {
            var count = 0;
            var min = Math.min(minLevel[0],minLevel[1],minLevel[2]);
            if ( (CFA) && (min==0) && (channels>1) ) min++; //for RGB cfa;
            var max = Math.max(maxLevel[0],maxLevel[1],maxLevel[2])+1;
            for ( var i = min; i < max; i++)
            {
               for (var c=0; c<channels; c++) count += histogram[c][i];
               if ( count > value ) { DeadThreshold = i/65536; break; }
            }
         }
         else DeadThreshold = h.normalizedClipLow(value);
         dialog.DeadThresholdControl.setValue(DeadThreshold);
      }
   }

   this.Dead_GroupBox = new GroupBox( this );
   with (this.Dead_GroupBox)
   {
      title = "Dead Pixels";
      sizer = new VerticalSizer;
      sizer.margin = 4;
      sizer.spacing = 4;
      sizer.add( this.DeadCountControl );
      sizer.add( this.DeadThresholdControl );
   }


   // HotPixel sliders ----------------------------------------------------------------------------
    this.HotThresholdControl = new NumericControl( this );
    with (this.HotThresholdControl)
    {
      label.text = "Threshold:";
      label.minWidth = labelWidth1;
      label.textAlignment = TextAlign_Right|TextAlign_VertCenter;
      toolTip = "<p>Define hot pixels clipping value.</p>";
      setRange( 0, 1 );
      slider.setRange(0,65535 );
      slider.minWidth = 200;
      setPrecision( 8 );
      setValue (HotThreshold);
      onValueUpdated = function( value )
      {
         HotThreshold = value;
         HotCount=0;
         var histogramLevel = Math.round( HotThreshold*65535 );
         for (var c=0; c<channels; c++)
         {
            var min = Math.max(histogramLevel, minLevel[c]);
            for (var i = maxLevel[c]; i >= min; i--) HotCount += histogram[c][i];
         }
         dialog.HotCountControl.setValue(HotCount);
      }
    }

   this.HotCountControl = new NumericControl( this );
   with (this.HotCountControl)
   {
      label.text = "Count:";
      label.minWidth = labelWidth1;
      label.textAlignment = TextAlign_Right|TextAlign_VertCenter;
      toolTip = "<p>How many hot pixels get clipped ?</p>";
      setRange( 0, 10000 );
      slider.setRange(0,10000 );
      slider.minWidth = 200;
      setPrecision( 0 );
      setValue (HotCount);
      onValueUpdated = function( value )
      {
         HotCount = value;
         if ( channels > 1 )
         {
            var count = 0;
            var min = Math.min(minLevel[0],minLevel[1],minLevel[2]);
            var max = Math.max(maxLevel[0],maxLevel[1],maxLevel[2]);
            for ( var i = max; i>=min; i--)
            {
               for (var c=0; c<channels; c++) count += histogram[c][i];
               if ( count > value ) { HotThreshold = i/65536; break; }
            }
         }
         else HotThreshold = h.normalizedClipHigh(value);

         dialog.HotThresholdControl.setValue(HotThreshold);
      }
   }

   this.Hot_GroupBox = new GroupBox( this );
   with (this.Hot_GroupBox)
   {
      title = "Hot Pixels";
      sizer = new VerticalSizer;
      sizer.margin = 4;
      sizer.spacing = 4;
      sizer.add( this.HotCountControl );
      sizer.add( this.HotThresholdControl );
   }


   // Transfer function --------------------------------------------------------------------------------------
   this.TransferFunction = new NumericControl(this);
   with (this.TransferFunction)
   {
      label.text = "Amount:";
      label.minWidth = labelWidth1 + 6;
      label.textAlignment = TextAlign_Right|TextAlign_VertCenter;
      toolTip = "<p>0   = no correction.<br/>" +
                "0.5 = 50% Original + 50% corrected.<br/>" +
                "1   = 100% corrected.<p>";
      setRange( 0, 1 );
      slider.setRange(0,1000000 );
      slider.minWidth = 200;
      setPrecision( 8 );
      setValue (TransferFunction);
      onValueUpdated = function( value ) TransferFunction = value;
   }


   // NewInstance button --------------------------------------------------------------------------------------
   this.newInstance_Button = new ToolButton( this );
   with ( this.newInstance_Button )
   {
      icon = new Bitmap( ":/images/interface/dragObject.png" );
      toolTip = "New Instance";
      onMousePress = function()
      {
         this.hasFocus = true;
         parent.exportParameters();
         this.pushed = false;
         parent.newInstance();
      }
   }


   // CFA CheckBox --------------------------------------------------------------------------------------
   this.CFA_CheckBox = new CheckBox( this );
   with ( this.CFA_CheckBox )
   {
      text = "CFA";
      checked = CFA;
      toolTip = "<p>Check if the image is a CFA. Uncheck if the image comes from a monochrome imager.</p>";
      onCheck = function( checked )
      {
         CFA = checked;
         dialog.DeadCountControl.onValueUpdated(DeadCount);
      }
   }


   // Standart buttons --------------------------------------------------------------------------------------
   this.ok_Button = new PushButton( this );
   with ( this.ok_Button )
   {
      text = "OK";
      onClick = function()
      {
         if (!MasterDark) return;
         parent.apply();
         this.dialog.ok();
      }
   }


   // Sizer ---------------------------------------------------------------------------------------
   this.filesButtons_Sizer = new HorizontalSizer;
   with (this.filesButtons_Sizer)
   {
      spacing = 4;
      add( this.filesAdd_Button );
      addStretch();
      add( this.filesClear_Button );
      addStretch();
      add( this.filesInvert_Button );
      add( this.filesRemove_Button );
   }

   this.outputDir_GroupBox = new GroupBox( this );
   with (this.outputDir_GroupBox)
   {
      title = "Output";
      sizer = new HorizontalSizer;
      sizer.margin = 6;
      sizer.spacing = 4;
      sizer.add( this.outputDir_Edit, 100 );
      sizer.add( this.outputDirSelect_Button );
      sizer.add( this.postfixLabel );
      sizer.add( this.postfixEdit);

   }

   this.MasterDark_Sizer = new HorizontalSizer;
   with ( this.MasterDark_Sizer )
   {
      spacing = 4;
      add( this.MasterDark_Label );
      add( this.MasterDark_ViewList,1);
   }

   this.buttons_Sizer = new HorizontalSizer;
   with ( this.buttons_Sizer )
   {
      add( this.newInstance_Button );
      addStretch();
      add( this.CFA_CheckBox,1 );
      spacing = 92;
      add( this.ok_Button );
   }

   this.sizer = new VerticalSizer;
   with ( this.sizer )
   {
      margin = 6;
      spacing = 6;
      add( this.helpLabel );
      add( this.files_TreeBox );
      add( this.filesButtons_Sizer );
      add( this.outputDir_GroupBox );
      add( this.MasterDark_Sizer );
      add( this.Dead_GroupBox );
      add( this.Hot_GroupBox );
      add( this.TransferFunction );
      add( this.buttons_Sizer );
   }

   this.adjustToContents();
   this.setMinSize();


   this.controlEnable = function ()
   {
      var e = Boolean(MasterDark);
      with (this.dialog)
      {
         DeadThresholdControl.enabled=e;
         DeadCountControl.enabled=e;
         HotThresholdControl.enabled=e;
         HotCountControl.enabled=e;
         newInstance_Button.enabled=e;
         CFA_CheckBox.enabled=e;
         if ( inputFiles.length > 0 ) ok_Button.enabled=e;
         else ok_Button.enabled=false;
         if (e)
         {
            channels = masterDarkImage.mainView.image.numberOfChannels;

            for (var c=0; c < channels; c++)
            {
               masterDarkImage.mainView.image.selectedChannel = c;
               h.generate(masterDarkImage.mainView.image);
               histogram[c] = h.toArray();
               minLevel[c] = 0;
               for (var i=0; i < 65536; i++)
                  if (histogram[c][i] !=0) { minLevel[c] = i; break; }
               maxLevel[c] = 65535;
               for (var i=65535; i > -1 ; i--)
                  if (histogram[c][i] !=0) { maxLevel[c] = i; break; }

               //Console.show();
               //Console.writeln("minLevel[c]",minLevel[c]);
               //Console.writeln("maxLevel[c]",maxLevel[c]);
            }
            HotCountControl.onValueUpdated(HotCount);
            DeadCountControl.onValueUpdated(DeadCount);
         }
      }
   }
   this.controlEnable();

   this.engine = function(LightImage)
   {
      var img = LightImage.mainView.image;
      var TempWin = new ImageWindow( img.width, img.height, img.numberOfChannels,
                          img.bitsPerSample, img.isReal, img.isColor, "Convolved");
      var TempView = TempWin.mainView;
      with (TempView)
      {
         beginProcess( UndoFlag_NoSwapFile );
         console.writeln( "Processing CFA:", CFA );
         image.apply( img );
         if (CFA)
            image.convolve([ 1,0,1,0,1,
                             0,0,0,0,0,
                             1,0,0,0,1,
                             0,0,0,0,0,
                             1,0,1,0,1 ]);
         else
            image.convolve([ 1,1,1,
                             1,0,1,
                             1,1,1 ]);
         endProcess();
      }

      var MDWin = masterDarkImage;
      // PixelMath iif ( (MasterDark >= HotPixelThreshold) or(MasterDark < DeadPixelThreshold) , (Convolved*x+targetView*(1-x)) , targetView )
      var mergeThem = new PixelMath;
      with (mergeThem)
      {
         expression = "iif((" + MDWin.mainView.id + ">=" + HotThreshold + ") || (" + MDWin.mainView.id + "<" + DeadThreshold
                     + "),(" +TempWin.mainView.id +"*"+TransferFunction+" + " + LightImage.mainView.id +"*( 1 - " + TransferFunction + ")),"+LightImage.mainView.id+")";
         useSingleExpression = true;
         rescale = false;

         if ( LightImage.visible) // execute on Image on the screen
         {  // create new image with STF
            createNewImage = true;
            newImageId = LightImage.mainView.id + postfix;
            executeOn( LightImage.mainView, false );
            var stf = LightImage.currentView.stf;
            ImageWindow.windowById(newImageId).currentView.stf = stf ;
         }
         else
         {
            executeOn( LightImage.mainView, false ); // execute on ImageContainer or FileList
         }
      }
      TempWin.close();
   }

   this.apply = function()
   {
      console.show();
      for ( var i = 0; i < inputFiles.length; ++i )
      {
         var LightImage = ImageWindow.open( inputFiles[i] )[0];
         this.engine(LightImage);
         if (outputDirectory) var outputFilePath = outputDirectory;
         else var outputFilePath = File.extractDrive(inputFiles[i]) + File.extractDirectory(inputFiles[i]);
         outputFilePath += "/" + File.extractName(inputFiles[i]) + postfix +  File.extractExtension(inputFiles[i]);
         if ( File.exists( outputFilePath ) )
            for ( var u = 1; ; ++u )
            {
               var tryFilePath = File.appendToName( outputFilePath, u.toString() );
               if ( !File.exists( tryFilePath ) ) { outputFilePath = tryFilePath; break; }
            }
         LightImage.saveAs( outputFilePath, false, false, false, false );
         LightImage.forceClose();
      }
   }

   this.applyToVievTarget = function()
   {
      var LightImage = ImageWindow.windowById(Parameters.targetView.id);
      this.engine(LightImage);
      this.onHide();
   }

   this.onHide = function()
   {
      if (!MasterDark) return;
      if (!masterDarkImage.isNull) masterDarkImage.forceClose();
   }
}


CosmeticCorrectionDialog.prototype = new Dialog;
var dialog = new CosmeticCorrectionDialog;
if ( Parameters.isViewTarget ) dialog.applyToVievTarget();
else
{
   console.hide();
   dialog.execute();
}
« Last Edit: 2011 October 10 10:15:49 by NKV »

Offline NKV

  • PTeam Member
  • PixInsight Guru
  • ****
  • Posts: 677
Re: Cosmetic Correction script
« Reply #13 on: 2012 February 06 02:27:27 »
Cosmetic Correction v1.7.1
+Auto hot/cold pixel removal.

Best regards,
Nikolay.

Code: [Select]
#feature-id    Utilities > CosmeticCorrection

#feature-info A cosmetic correction utility.<br/>\
   <br/>\
   This script replaces bad pixel values (hot and cold pixels) with averaged values from \
   the appropriate neighbor pixels.<br/>\
   <br/>\
   The script requires a map image of defective pixels or a master dark frame. <br/>\
   <br/>\
   Also the script can detect and clean remaining hot or cold pixels.<br/>\
   <br/>\
   Copyright (C) 2009 - 2012 Nikolay Volkov

#define VERSION   1.7.1
#define TITLE     CosmeticCorrection

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


function CosmeticCorrectionDialog()
{
   this.__base__ = Dialog;
   this.__base__();
   this.windowTitle = #TITLE + " Script";

   var ColdThreshold = 0.0;
   var ColdCount = 0;
   var HotThreshold = 1;
   var HotCount = 1000;
   var TransferFunction = 1.0;
   var DetectHot_Threshold = 10;
   var DetectHot = true;
   var DetectCold_Threshold = 10;
   var DetectCold = false;
   var CFA = false;
   var MasterDark = "";
   var inputFiles = new Array();
   var outputDirectory = "";
   var postfix = "_cc";
   var masterDarkImage = ImageWindow();
   var h = new Histogram ();
   var histogram = new Array();
   var channels = 0 ;
   var minLevel = new Array();
   var maxLevel = new Array();


   if ( Parameters.isGlobalTarget || Parameters.isViewTarget )
   {
      ColdThreshold = Parameters.getReal( "ColdThreshold" );
      ColdCount = Parameters.getUInt( "ColdCount" );
      DetectCold_Threshold = Parameters.getReal( "AutoCold_Threshold" );
      DetectCold = Parameters.getBoolean("AutoCold");

      HotThreshold = Parameters.getReal( "HotThreshold" );
      HotCount = Parameters.getUInt( "HotCount" );
      DetectHot_Threshold = Parameters.getReal( "AutoHot_Threshold" );
      DetectHot = Parameters.getBoolean("AutoHot");

      TransferFunction = Parameters.getReal( "TransferFunction" );

      CFA = Parameters.getBoolean( "CFA" );
      MasterDark = Parameters.get( "MasterDark" );
      outputDirectory = Parameters.get( "outputDirectory" );
      postfix = Parameters.get( "postfix" );
      for ( var i = 0; Parameters.has( "File_"+i) ; ++i )
      {
         inputFiles[i] = Parameters.getString( "File_"+i);
         Parameters.remove( "File_"+i)
      }
      if (MasterDark) masterDarkImage = ImageWindow.open( MasterDark )[0];
   }

   this.exportParameters = function()
   {
      Parameters.set( "ColdThreshold", ColdThreshold );
      Parameters.set( "ColdCount", ColdCount );
      Parameters.set( "AutoCold_Threshold", DetectCold_Threshold );
      Parameters.set( "AutoCold", DetectCold );

      Parameters.set( "HotThreshold", HotThreshold );
      Parameters.set( "HotCount", HotCount );
      Parameters.set( "AutoHot_Threshold", DetectHot_Threshold );
      Parameters.set( "AutoHot", DetectHot );

      Parameters.set( "TransferFunction", TransferFunction );

      Parameters.set( "CFA", CFA );
      Parameters.set( "MasterDark", MasterDark );
      Parameters.set( "outputDirectory", outputDirectory );
      Parameters.set( "postfix", postfix );
      for ( var i = 0; i < inputFiles.length; ++i ) Parameters.set( "File_"+i, inputFiles[i] );
   }

   //Header --------------------------------------------------------------------------------------
   this.helpLabel = new Label( this );
   with ( this.helpLabel )
   {
      frameStyle = FrameStyle_Sunken;
      margin = 4;
      wordWrapping = true;
      useRichText = true;
      text = "<p><b>" + #TITLE + " v" + #VERSION + "</b> &mdash; " +
             "This script replaces each defective pixel with the average value of its 8 neighbor " +
             "pixels. Defective pixels are identified by values outside the range of user-defined " +
             "thresholds in the specified master dark frame.<br/>" +
             "Also the script can detect and clean remaining hot or cold pixels.<br/>" +
             "Copyright (C) 2009 - 2012 Nikolay Volkov</p>";
   }

   //
   var emWidth = this.font.width( 'M' );
   var labelWidth1 = this.font.width( "Threshold:" ) + emWidth;
   var editWidth1 = 12*this.font.width( '0' );

   // Input File List -----------------------------------------------------------------------------------
   this.files_TreeBox = new TreeBox( this );
   with ( this.files_TreeBox )
   {
      rootDecoration = false;
      numberOfColumns = 1;
      alternateRowColor = true;
      multipleSelection = true;
      headerVisible = false;
      for ( var i = 0; i < inputFiles.length; ++i )
      {
         var node = new TreeBoxNode( this.dialog.files_TreeBox );
         node.setText( 0, inputFiles[i] );
      }

      onNodeDoubleClicked = function()
      {
         var f = ImageWindow.open( currentNode.text(0) )[0];
         f.show();
      }
   }

   this.filesAdd_Button = new PushButton( this );
   this.filesAdd_Button.text = "Add";
   this.filesAdd_Button.toolTip = "<p>Add image files to the input images list.</p>";
   this.filesAdd_Button.onClick = function()
   {
      var ofd = new OpenFileDialog;
      ofd.multipleSelections = true;
      ofd.caption = "Select Images";
      ofd.loadImageFilters();
      if ( ofd.execute() )
      {
         this.dialog.files_TreeBox.canUpdate = false;
         for ( var i = 0; i < ofd.fileNames.length; ++i )
         {
            var node = new TreeBoxNode( this.dialog.files_TreeBox );
            node.setText( 0, ofd.fileNames[i] );
            inputFiles.push( ofd.fileNames[i] );
         }
         this.dialog.files_TreeBox.canUpdate = true;
         this.dialog.controlEnable();
      }
   };

   this.filesClear_Button = new PushButton( this );
   this.filesClear_Button.text = "Clear";
   this.filesClear_Button.toolTip = "<p>Clear the list of input images.</p>";
   this.filesClear_Button.onClick = function()
   {
      this.dialog.files_TreeBox.clear();
      inputFiles.length = 0;
      this.dialog.controlEnable();
   };

   this.filesInvert_Button = new PushButton( this );
   this.filesInvert_Button.text = "Invert Selection";
   this.filesInvert_Button.toolTip = "<p>Invert the current selection of input images.</p>";
   this.filesInvert_Button.onClick = function()
   {
      for ( var i = 0; i < this.dialog.files_TreeBox.numberOfChildren; ++i )
         this.dialog.files_TreeBox.child( i ).selected =
               !this.dialog.files_TreeBox.child( i ).selected;
   };

   this.filesRemove_Button = new PushButton( this );
   this.filesRemove_Button.text = "Remove Selected";
   this.filesRemove_Button.toolTip = "<p>Remove all selected images from the input images list.</p>";
   this.filesRemove_Button.onClick = function()
   {
      inputFiles.length = 0;
      for ( var i = 0; i < this.dialog.files_TreeBox.numberOfChildren; ++i )
         if ( !this.dialog.files_TreeBox.child( i ).selected )
            inputFiles.push( this.dialog.files_TreeBox.child( i ).text( 0 ) );
      for ( var i = this.dialog.files_TreeBox.numberOfChildren; --i >= 0; )
         if ( this.dialog.files_TreeBox.child( i ).selected )
            this.dialog.files_TreeBox.remove( i );
      this.dialog.controlEnable();
   };


   //Output --------------------------------------------------------------------------------------
   this.outputDir_Edit = new Edit( this );
   this.outputDir_Edit.readOnly = true;
   this.outputDir_Edit.minWidth = 32*emWidth;
   this.outputDir_Edit.text = outputDirectory;
   this.outputDir_Edit.toolTip =
      "<p>If specified, all corrected images will be written to the output directory.</p>" +
      "<p>If not specified, corrected images will be written to the same directories " +
      "of their corresponding input images.</p>";

   this.outputDirSelect_Button = new ToolButton( this );
   with ( this.outputDirSelect_Button )
   {
      icon = new Bitmap( ":/images/icons/select.png" );
      toolTip = "<p>Select the output directory.</p>";
      onClick = function()
      {
         var gdd = new GetDirectoryDialog;
         gdd.initialPath = outputDirectory;
         gdd.caption = "Output Directory";
         if ( gdd.execute() )
         {
            outputDirectory = gdd.directory;
            this.dialog.outputDir_Edit.text = outputDirectory;
         }
      }
   }

   this.postfixLabel = new Label( this );
   with ( this.postfixLabel )
   {
      text = "Postfix:";
      textAlignment = TextAlign_Right|TextAlign_VertCenter;
   }

   this.postfixEdit = new Edit( this );
   with ( this.postfixEdit )
   {
      text = postfix;
      setFixedWidth( this.font.width( "MMM" ) );
      toolTip = "<p>This is a postfix that will be appended to the file names of output corrected images.</p>";
      onEditCompleted = function() postfix = this.text;
   }


   //MasterDark image --------------------------------------------------------------------------------------

   this.MasterDark_Label = new Edit( this );
   with ( this.MasterDark_Label )
   {
      if (MasterDark) text = File.extractName(MasterDark)
      else text = "<Select MasterDark>";
      readOnly = true;
   }

   this.MasterDark_ViewList = new ToolButton( this );
   with ( this.MasterDark_ViewList )
   {
      icon = new Bitmap( ":/images/icons/select.png" );
      toolTip = "<p>Select a master dark frame or defect map image.</p>";
      onClick = function()
      {
         var ofd = new OpenFileDialog;
         ofd.multipleSelections = false;
         ofd.caption = "<Select MasterDark>";
         ofd.loadImageFilters();
         if (ofd.execute())
         {
            if (MasterDark == ofd.fileNames[0]) return;
            if (MasterDark)
            {
               masterDarkImage.forceClose();
               h = new Histogram ();
            }
            MasterDark = ofd.fileNames[0];
            parent.MasterDark_Label.text = File.extractName(MasterDark);
            Console.show();
            masterDarkImage = ImageWindow.open( MasterDark )[0];
            parent.controlEnable();
            Console.hide();
         }
      }
   }

   // ColdPixel sliders via MasterDark ------------------------------------------------------------------------
    this.ColdThresholdControl = new NumericControl( this );
    with (this.ColdThresholdControl)
    {
      label.text = "Threshold:";
      label.minWidth = labelWidth1;
      label.textAlignment = TextAlign_Right|TextAlign_VertCenter;
      toolTip = "<p>Cold pixel clipping point.</p>";
      setRange( 0, 1 );
      slider.setRange(0,65535);
      slider.minWidth = 200;
      setPrecision( 8 );
      edit.setFixedWidth( editWidth1 );
      setValue (ColdThreshold);
      onValueUpdated = function( value )
      {
         ColdThreshold = value;
         var count=0;
         var histogramLevel = Math.round( ColdThreshold*65535 );
         for (var c=0; c < channels; c++)
         {
            var max = Math.min( histogramLevel, maxLevel[c] );
            var min = minLevel[c];
            if ( (CFA) && (min==0) && (channels>1) ) min++; //for RGB cfa;
            for ( var i = min; i < max; i++ ) count += histogram[c][i];
         }
         dialog.ColdCountControl.setValue(count);
         ColdCount = count;
      }
    }

   this.ColdCountControl = new NumericControl( this );
   with (this.ColdCountControl)
   {
      label.text = "Count:";
      label.minWidth = labelWidth1;
      label.textAlignment = TextAlign_Right|TextAlign_VertCenter;
      toolTip = "<p>How many dead pixels will be clipped?</p>";
      setRange( 0, 10000 );
      slider.setRange(0,10000 );
      slider.minWidth = 200;
      setPrecision( 0 );
      edit.setFixedWidth( editWidth1 );
      setValue (ColdCount);
      onValueUpdated = function( value )
      {
         ColdCount = value;
         if ( channels > 1 )
         {
            var count = 0;
            var min = Math.min(minLevel[0],minLevel[1],minLevel[2]);
            if ( (CFA) && (min==0) && (channels>1) ) min++; //for RGB cfa;
            var max = Math.max(maxLevel[0],maxLevel[1],maxLevel[2])+1;
            for ( var i = min; i < max; i++)
            {
               for (var c=0; c<channels; c++) count += histogram[c][i];
               if ( count > value ) { ColdThreshold = i/65536; break; }
            }
         }
         else ColdThreshold = h.normalizedClipLow(value);
         dialog.ColdThresholdControl.setValue(ColdThreshold);
      }
   }

   // auto cold
   this.DetectCold_Threshold = new NumericControl(this);
   with (this.DetectCold_Threshold)
   {
      label.text = "Detection Threshold:";
      label.textAlignment = TextAlign_Right|TextAlign_VertCenter;
      toolTip = "<pre>How many times the pixel value must differ<br/>" +
                   "from the surrounding neighbors, so consider it defective?<br/>";
      setRange( 0, 100 );
      slider.setRange(0,1000000 );
      slider.minWidth = 200;
      setPrecision( 1 );
      setValue (DetectCold_Threshold);
      onValueUpdated = function( value ) DetectCold_Threshold = value;
   }

   this.DetectCold_GroupBox = new GroupBox( this );
   with (this.DetectCold_GroupBox)
   {
      title = "Auto Detect";
      titleCheckBox = true;
      checked = DetectCold;
      sizer = new VerticalSizer;
      sizer.margin = 4;
      sizer.spacing = 4;
      sizer.add( this.DetectCold_Threshold );
      onCheck = function( value )
      {
         DetectCold = value;
         this.dialog.controlEnable();
      }
   }

   this.Cold_GroupBox = new GroupBox( this );
   with (this.Cold_GroupBox)
   {
      title = "Cold Pixels";
      sizer = new VerticalSizer;
      sizer.margin = 4;
      sizer.spacing = 4;
      sizer.add( this.ColdCountControl );
      sizer.add( this.ColdThresholdControl );
      sizer.add( this.DetectCold_GroupBox );
   }


   // HotPixel sliders via MasterDark ----------------------------------------------------------------------------
    this.HotThresholdControl = new NumericControl( this );
    with (this.HotThresholdControl)
    {
      label.text = "Threshold:";
      label.minWidth = labelWidth1;
      label.textAlignment = TextAlign_Right|TextAlign_VertCenter;
      toolTip = "<p>Hot pixel clipping point.</p>";
      setRange( 0, 1 );
      slider.setRange(0,65535 );
      slider.minWidth = 200;
      setPrecision( 8 );
      edit.setFixedWidth( editWidth1 );
      setValue (HotThreshold);
      onValueUpdated = function( value )
      {
         HotThreshold = value;
         HotCount=0;
         var histogramLevel = Math.round( HotThreshold*65535 );
         for (var c=0; c<channels; c++)
         {
            var min = Math.max(histogramLevel, minLevel[c]);
            for (var i = maxLevel[c]; i >= min; i--) HotCount += histogram[c][i];
         }
         dialog.HotCountControl.setValue(HotCount);
      }
    }

   this.HotCountControl = new NumericControl( this );
   with (this.HotCountControl)
   {
      label.text = "Count:";
      label.minWidth = labelWidth1;
      label.textAlignment = TextAlign_Right|TextAlign_VertCenter;
      toolTip = "<p>How many hot pixels will be clipped?</p>";
      setRange( 0, 10000 );
      slider.setRange(0,10000 );
      slider.minWidth = 200;
      setPrecision( 0 );
      edit.setFixedWidth( editWidth1 );
      setValue (HotCount);
      onValueUpdated = function( value )
      {
         HotCount = value;
         if ( channels > 1 )
         {
            var count = 0;
            var min = Math.min(minLevel[0],minLevel[1],minLevel[2]);
            var max = Math.max(maxLevel[0],maxLevel[1],maxLevel[2]);
            for ( var i = max; i>=min; i--)
            {
               for (var c=0; c<channels; c++) count += histogram[c][i];
               if ( count > value ) { HotThreshold = i/65536; break; }
            }
         }
         else HotThreshold = h.normalizedClipHigh(value);

         dialog.HotThresholdControl.setValue(HotThreshold);
      }
   }

   // auto hot
   this.DetectHot_Threshold = new NumericControl(this);
   with (this.DetectHot_Threshold)
   {
      label.text = "Detection Threshold:";
      label.textAlignment = TextAlign_Right|TextAlign_VertCenter;
      toolTip = "<pre>How many times the pixel value must differ<br/>" +
                   "from the surrounding neighbors, so consider it defective?<br/>";
      setRange( 0, 100 );
      slider.setRange(0,1000000 );
      slider.minWidth = 200;
      setPrecision( 1 );
      setValue (DetectHot_Threshold);
      onValueUpdated = function( value ) DetectHot_Threshold = value;
   }

   this.DetectHot_GroupBox = new GroupBox( this );
   with (this.DetectHot_GroupBox)
   {
      title = "Auto Detect";
      titleCheckBox = true;
      checked = DetectHot;
      sizer = new VerticalSizer;
      sizer.margin = 4;
      sizer.spacing = 4;
      sizer.add( this.DetectHot_Threshold );
      onCheck = function( value )
      {
         DetectHot = value;
         this.dialog.controlEnable();
      }
   }

   this.Hot_GroupBox = new GroupBox( this );
   with (this.Hot_GroupBox)
   {
      title = "Hot Pixels";
      sizer = new VerticalSizer;
      sizer.margin = 4;
      sizer.spacing = 4;
      sizer.add( this.HotCountControl );
      sizer.add( this.HotThresholdControl );
      sizer.add( this.DetectHot_GroupBox );
   }


   // Transfer function --------------------------------------------------------------------------------------
   this.TransferFunction = new NumericControl(this);
   with (this.TransferFunction)
   {
      label.text = "Amount:";
      label.minWidth = labelWidth1 + 4+1; // compensate groupbox spacing+border
      label.textAlignment = TextAlign_Right|TextAlign_VertCenter;
      toolTip = "<pre>0   = no correction.<br/>" +
                   "0.5 = 50% original + 50% corrected.<br/>" +
                   "1   = 100% corrected.</pre>";
      setRange( 0, 1 );
      slider.setRange(0,1000000 );
      slider.minWidth = 200;
      setPrecision( 8 );
      edit.setFixedWidth( editWidth1 );
      setValue (TransferFunction);
      onValueUpdated = function( value ) TransferFunction = value;
   }

   // NewInstance button --------------------------------------------------------------------------------------
   this.newInstance_Button = new ToolButton( this );
   with ( this.newInstance_Button )
   {
      icon = new Bitmap( ":/images/interface/dragObject.png" );
      toolTip = "New Instance";
      onMousePress = function()
      {
         this.hasFocus = true;
         parent.exportParameters();
         this.pushed = false;
         parent.newInstance();
      }
   }


   // CFA CheckBox --------------------------------------------------------------------------------------
   this.CFA_CheckBox = new CheckBox( this );
   with ( this.CFA_CheckBox )
   {
      text = "CFA";
      checked = CFA;
      toolTip = "<p>* Check if the target frames are CFA or RGB Bayer images.<br/>" +
                   "* Uncheck if the images come from a monochrome imager.</p>";
      onCheck = function( checked )
      {
         CFA = checked;
         dialog.ColdCountControl.onValueUpdated(ColdCount);
      }
   }


   // Standart buttons --------------------------------------------------------------------------------------
   this.ok_Button = new PushButton( this );
   with ( this.ok_Button )
   {
      text = "OK";
      onClick = function()
      {
         //if (!MasterDark) return;
         parent.apply();
         this.dialog.ok();
      }
   }


   // Sizer ---------------------------------------------------------------------------------------
   this.filesButtons_Sizer = new HorizontalSizer;
   with (this.filesButtons_Sizer)
   {
      spacing = 4;
      add( this.filesAdd_Button );
      addStretch();
      add( this.filesClear_Button );
      addStretch();
      add( this.filesInvert_Button );
      add( this.filesRemove_Button );
   }

   this.outputDir_GroupBox = new GroupBox( this );
   with (this.outputDir_GroupBox)
   {
      title = "Output";
      sizer = new HorizontalSizer;
      sizer.margin = 6;
      sizer.spacing = 4;
      sizer.add( this.outputDir_Edit, 100 );
      sizer.add( this.outputDirSelect_Button );
      sizer.add( this.postfixLabel );
      sizer.add( this.postfixEdit);

   }

   this.MasterDark_Sizer = new HorizontalSizer;
   with ( this.MasterDark_Sizer )
   {
      spacing = 4;
      add( this.MasterDark_Label );
      add( this.MasterDark_ViewList,1);
   }

   this.buttons_Sizer = new HorizontalSizer;
   with ( this.buttons_Sizer )
   {
      add( this.newInstance_Button );
      addStretch();
      add( this.CFA_CheckBox,1 );
      spacing = 92;
      add( this.ok_Button );
   }

   this.sizer = new VerticalSizer;
   with ( this.sizer )
   {
      margin = 6;
      spacing = 6;
      add( this.helpLabel );
      add( this.files_TreeBox );
      add( this.filesButtons_Sizer );
      add( this.outputDir_GroupBox );
      add( this.MasterDark_Sizer );
      add( this.Cold_GroupBox );
      add( this.Hot_GroupBox );
      add( this.TransferFunction );
      add( this.buttons_Sizer );
   }

   this.adjustToContents();
   this.setMinSize();

   this.controlEnable = function ()
   {
      var e = Boolean( MasterDark );
      with (this.dialog)
      {
         ColdThresholdControl.enabled=e;
         ColdCountControl.enabled=e;
         HotThresholdControl.enabled=e;
         HotCountControl.enabled=e;

         if ( DetectHot_GroupBox.checked || DetectCold_GroupBox.checked )
            newInstance_Button.enabled = true;
         else
            newInstance_Button.enabled=e;

         CFA_CheckBox.enabled=e;
         if ( inputFiles.length > 0 )
         {
            if ( DetectHot_GroupBox.checked || DetectCold_GroupBox.checked )
               ok_Button.enabled = true;
            else
               ok_Button.enabled=e;
         }
         else ok_Button.enabled=false;
         if ( e && h.isEmpty )
         {
            channels = masterDarkImage.mainView.image.numberOfChannels;
            Console.write("Generate histogram for MasterDark.");
            Console.flush();

            for (var c=0; c < channels; c++)
            {
               masterDarkImage.mainView.image.selectedChannel = c;
               h.generate(masterDarkImage.mainView.image);
               histogram[c] = h.toArray();
               minLevel[c] = 0;
               for (var i=0; i < 65536; i++)
                  if (histogram[c][i] !=0) { minLevel[c] = i; break; }
               maxLevel[c] = 65535;
               for (var i=65535; i > -1 ; i--)
                  if (histogram[c][i] !=0) { maxLevel[c] = i; break; }

               //Console.show();
               //Console.writeln("minLevel[c]",minLevel[c]);
               //Console.writeln("maxLevel[c]",maxLevel[c]);
            }
            HotCountControl.onValueUpdated(HotCount);
            ColdCountControl.onValueUpdated(ColdCount);
            Console.writeln(" Done.");
            Console.flush();
         }
      }
   }
   this.controlEnable();

   this.engine = function(LightImage)
   {
      var img = LightImage.mainView.image;
      var TempWin = new ImageWindow( img.width, img.height, img.numberOfChannels,
                          img.bitsPerSample, img.isReal, img.isColor, "Convolved");
      var TempView = TempWin.mainView;
      with (TempView)
      {
         beginProcess( UndoFlag_NoSwapFile );
         console.writeln( "Processing CFA:", CFA );
         image.apply( img );
         if (CFA)
            image.convolve([ 1,0,1,0,1,
                             0,0,0,0,0,
                             1,0,0,0,1,
                             0,0,0,0,0,
                             1,0,1,0,1 ]);
         else
            image.convolve([ 1,1,1,
                             1,0,1,
                             1,1,1 ]);
         endProcess();
      }

      var mergeThem = new PixelMath;
      with (mergeThem)
      {
         var NegativeTF = 1 - TransferFunction;
         //start iif
         expression = "iif( ";

         if ( MasterDark ) // set if dark selected
         { // PixelMath iif ( (MasterDark >= HotPixelThreshold) or(MasterDark < ColdPixelThreshold) , (Convolved*x+targetView*(1-x)) , targetView )
            var MDWin = masterDarkImage;
            expression +=
               "( " + MDWin.mainView.id + " >= " + HotThreshold + " )" +
               "||" +
               "( " + MDWin.mainView.id + " < " + ColdThreshold + " )";
         }

         if ( DetectHot ) // add more iif($T > mean*k) DetectHot_GroupBox is checked
         {
            if ( MasterDark ) expression += "||";

            expression +=
               "( " + LightImage.mainView.id + " >= " +
               TempWin.mainView.id + " * " + DetectHot_Threshold + " )";
         }

         if ( DetectCold  ) // add more iif(mean*k < $T) DetectCold_GroupBox is checked
         {
            if ( MasterDark || DetectHot ) expression += "||";

            expression +=
               "( " + TempWin.mainView.id + " * " + DetectHot_Threshold +
               " <= " + LightImage.mainView.id + " )";
         }

         expression +=
         // do this
            ", ";
            if ( TransferFunction == 1) // just for faster calculation
            {
               expression += TempWin.mainView.id;
            }
            else if ( TransferFunction == 0)
            {
               expression += LightImage.mainView.id;
            }
            else
            {
               expression += TempWin.mainView.id+ " * " +TransferFunction+
                  " + " +LightImage.mainView.id+ " * " +NegativeTF;
            }
         // else do this:
            expression += ", " + LightImage.mainView.id

         //end iif
            + " )";

         useSingleExpression = true;
         rescale = false;

         if ( LightImage.visible) // execute on Image on the screen
         {  // create new image with STF
            createNewImage = true;
            newImageId = LightImage.mainView.id + postfix;
            executeOn( LightImage.mainView, false );
            var stf = LightImage.currentView.stf;
            ImageWindow.windowById(newImageId).currentView.stf = stf ;
         }
         else
         {
            executeOn( LightImage.mainView, false ); // execute on ImageContainer or FileList
         }
      }
      TempWin.close();
   }

   this.apply = function()
   {
      console.show();
      for ( var i = 0; i < inputFiles.length; ++i )
      {
         var LightImage = ImageWindow.open( inputFiles[i] )[0];
         this.engine(LightImage);
         if (outputDirectory) var outputFilePath = outputDirectory;
         else var outputFilePath = File.extractDrive(inputFiles[i]) + File.extractDirectory(inputFiles[i]);
         outputFilePath += "/" + File.extractName(inputFiles[i]) + postfix +  File.extractExtension(inputFiles[i]);
         if ( File.exists( outputFilePath ) )
            for ( var u = 1; ; ++u )
            {
               var tryFilePath = File.appendToName( outputFilePath, u.toString() );
               if ( !File.exists( tryFilePath ) ) { outputFilePath = tryFilePath; break; }
            }
         LightImage.saveAs( outputFilePath, false, false, false, false );
         LightImage.forceClose();
      }
   }

   this.applyToVievTarget = function()
   {
      var LightImage = ImageWindow.windowById(Parameters.targetView.id);
      this.engine(LightImage);
      this.onHide();
   }

   this.onHide = function()
   {
      if (!MasterDark) return;
      if (!masterDarkImage.isNull) masterDarkImage.forceClose();
   }
}


CosmeticCorrectionDialog.prototype = new Dialog;
var dialog = new CosmeticCorrectionDialog;
if ( Parameters.isViewTarget ) dialog.applyToVievTarget();
else
{
   console.hide();
   dialog.execute();
}

Offline NKV

  • PTeam Member
  • PixInsight Guru
  • ****
  • Posts: 677
Re: Cosmetic Correction script
« Reply #14 on: 2012 March 04 23:54:34 »
v1.7.3
+prefix
+CFA without MasterDark

v1.7.4
BugFix Thanks for bug report.
Code: [Select]
#feature-id    Utilities > CosmeticCorrection

#feature-info A cosmetic correction utility.<br/>\
   <br/>\
   This script replaces bad pixel values (hot and cold pixels) with averaged values from \
   the appropriate neighbor pixels.<br/>\
   <br/>\
   The script requires a map image of defective pixels or a master dark frame. <br/>\
   <br/>\
   Also the script can detect and clean remaining hot or cold pixels.<br/>\
   <br/>\
   Copyright (C) 2009 - 2012 Nikolay Volkov

#define VERSION   1.7.4
#define TITLE     CosmeticCorrection

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


function CosmeticCorrectionDialog()
{
   this.__base__ = Dialog;
   this.__base__();
   this.windowTitle = #TITLE + " Script";

   var ColdThreshold = 0.0;
   var ColdCount = 0;
   var HotThreshold = 1;
   var HotCount = 1000;
   var TransferFunction = 1.0;
   var DetectHot_Threshold = 3;
   var DetectHot = true;
   var DetectCold_Threshold = 10;
   var DetectCold = false;
   var CFA = false;
   var MasterDark = "";
   var inputFiles = new Array();
   var outputDirectory = "";
   var postfix = "_cc";
   var prefix = "";
   var masterDarkImage = ImageWindow();
   var h = new Histogram ();
   var histogram = new Array();
   var channels = 0 ;
   var minLevel = new Array();
   var maxLevel = new Array();


   if ( Parameters.isGlobalTarget || Parameters.isViewTarget )
   {
      ColdThreshold = Parameters.getReal( "ColdThreshold" );
      ColdCount = Parameters.getUInt( "ColdCount" );
      DetectCold_Threshold = Parameters.getReal( "AutoCold_Threshold" );
      DetectCold = Parameters.getBoolean("AutoCold");

      HotThreshold = Parameters.getReal( "HotThreshold" );
      HotCount = Parameters.getUInt( "HotCount" );
      DetectHot_Threshold = Parameters.getReal( "AutoHot_Threshold" );
      DetectHot = Parameters.getBoolean("AutoHot");

      TransferFunction = Parameters.getReal( "TransferFunction" );

      CFA = Parameters.getBoolean( "CFA" );
      MasterDark = Parameters.get( "MasterDark" );
      outputDirectory = Parameters.get( "outputDirectory" );
      prefix = Parameters.get( "prefix" );
      postfix = Parameters.get( "postfix" );
      for ( var i = 0; Parameters.has( "File_"+i) ; ++i )
      {
         inputFiles[i] = Parameters.getString( "File_"+i);
         Parameters.remove( "File_"+i)
      }
      if (MasterDark) masterDarkImage = ImageWindow.open( MasterDark )[0];
   }

   this.exportParameters = function()
   {
      Parameters.set( "ColdThreshold", ColdThreshold );
      Parameters.set( "ColdCount", ColdCount );
      Parameters.set( "AutoCold_Threshold", DetectCold_Threshold );
      Parameters.set( "AutoCold", DetectCold );

      Parameters.set( "HotThreshold", HotThreshold );
      Parameters.set( "HotCount", HotCount );
      Parameters.set( "AutoHot_Threshold", DetectHot_Threshold );
      Parameters.set( "AutoHot", DetectHot );

      Parameters.set( "TransferFunction", TransferFunction );

      Parameters.set( "CFA", CFA );
      Parameters.set( "MasterDark", MasterDark );
      Parameters.set( "outputDirectory", outputDirectory );
      Parameters.set( "postfix", postfix );
      Parameters.set( "prefix", prefix );
      for ( var i = 0; i < inputFiles.length; ++i ) Parameters.set( "File_"+i, inputFiles[i] );
   }

   //Header --------------------------------------------------------------------------------------
   this.helpLabel = new Label( this );
   with ( this.helpLabel )
   {
      frameStyle = FrameStyle_Sunken;
      margin = 4;
      wordWrapping = true;
      useRichText = true;
      text = "<p><b>" + #TITLE + " v" + #VERSION + "</b> &mdash; " +
             "This script replaces each defective pixel with the average value of its 8 neighbor " +
             "pixels. Defective pixels are identified by values outside the range of user-defined " +
             "thresholds in the specified master dark frame.<br/>" +
             "Also the script can detect and clean remaining hot or cold pixels.<br/>" +
             "Copyright (C) 2009 - 2012 Nikolay Volkov</p>";
   }

   //
   var emWidth = this.font.width( 'M' );
   var labelWidth1 = this.font.width( "Threshold:" ) + emWidth;
   var editWidth1 = 12*this.font.width( '0' );

   // Input File List -----------------------------------------------------------------------------------
   this.files_TreeBox = new TreeBox( this );
   with ( this.files_TreeBox )
   {
      rootDecoration = false;
      numberOfColumns = 1;
      alternateRowColor = true;
      multipleSelection = true;
      headerVisible = false;
      for ( var i = 0; i < inputFiles.length; ++i )
      {
         var node = new TreeBoxNode( this.dialog.files_TreeBox );
         node.setText( 0, inputFiles[i] );
      }

      onNodeDoubleClicked = function()
      {
         var f = ImageWindow.open( currentNode.text(0) )[0];
         f.show();
      }
   }

   this.filesAdd_Button = new PushButton( this );
   this.filesAdd_Button.text = "Add";
   this.filesAdd_Button.toolTip = "<p>Add image files to the input images list.</p>";
   this.filesAdd_Button.onClick = function()
   {
      var ofd = new OpenFileDialog;
      ofd.multipleSelections = true;
      ofd.caption = "Select Images";
      ofd.loadImageFilters();
      if ( ofd.execute() )
      {
         this.dialog.files_TreeBox.canUpdate = false;
         for ( var i = 0; i < ofd.fileNames.length; ++i )
         {
            var node = new TreeBoxNode( this.dialog.files_TreeBox );
            node.setText( 0, ofd.fileNames[i] );
            inputFiles.push( ofd.fileNames[i] );
         }
         this.dialog.files_TreeBox.canUpdate = true;
         this.dialog.controlEnable();
      }
   };

   this.filesClear_Button = new PushButton( this );
   this.filesClear_Button.text = "Clear";
   this.filesClear_Button.toolTip = "<p>Clear the list of input images.</p>";
   this.filesClear_Button.onClick = function()
   {
      this.dialog.files_TreeBox.clear();
      inputFiles.length = 0;
      this.dialog.controlEnable();
   };

   this.filesInvert_Button = new PushButton( this );
   this.filesInvert_Button.text = "Invert Selection";
   this.filesInvert_Button.toolTip = "<p>Invert the current selection of input images.</p>";
   this.filesInvert_Button.onClick = function()
   {
      for ( var i = 0; i < this.dialog.files_TreeBox.numberOfChildren; ++i )
         this.dialog.files_TreeBox.child( i ).selected =
               !this.dialog.files_TreeBox.child( i ).selected;
   };

   this.filesRemove_Button = new PushButton( this );
   this.filesRemove_Button.text = "Remove Selected";
   this.filesRemove_Button.toolTip = "<p>Remove all selected images from the input images list.</p>";
   this.filesRemove_Button.onClick = function()
   {
      inputFiles.length = 0;
      for ( var i = 0; i < this.dialog.files_TreeBox.numberOfChildren; ++i )
         if ( !this.dialog.files_TreeBox.child( i ).selected )
            inputFiles.push( this.dialog.files_TreeBox.child( i ).text( 0 ) );
      for ( var i = this.dialog.files_TreeBox.numberOfChildren; --i >= 0; )
         if ( this.dialog.files_TreeBox.child( i ).selected )
            this.dialog.files_TreeBox.remove( i );
      this.dialog.controlEnable();
   };


   //Output --------------------------------------------------------------------------------------
   this.outputDir_Edit = new Edit( this );
   this.outputDir_Edit.readOnly = true;
   this.outputDir_Edit.minWidth = 32*emWidth;
   this.outputDir_Edit.text = outputDirectory;
   this.outputDir_Edit.toolTip =
      "<p>If specified, all corrected images will be written to the output directory.</p>" +
      "<p>If not specified, corrected images will be written to the same directories " +
      "of their corresponding input images.</p>";

   this.outputDirSelect_Button = new ToolButton( this );
   with ( this.outputDirSelect_Button )
   {
      icon = new Bitmap( ":/images/icons/select.png" );
      toolTip = "<p>Select the output directory.</p>";
      onClick = function()
      {
         var gdd = new GetDirectoryDialog;
         gdd.initialPath = outputDirectory;
         gdd.caption = "Output Directory";
         if ( gdd.execute() )
         {
            outputDirectory = gdd.directory;
            this.dialog.outputDir_Edit.text = outputDirectory;
         }
      }
   }


   //---------------------------------------
   this.prefixLabel = new Label( this );
   with ( this.prefixLabel )
   {
      text = "Prefix:";
      textAlignment = TextAlign_Right|TextAlign_VertCenter;
   }

   this.prefixEdit = new Edit( this );
   with ( this.prefixEdit )
   {
      text = prefix;
      setFixedWidth( this.font.width( "MMM" ) );
      toolTip = "<p>This is a prefix that will be appended to the file names of output corrected images.</p>";
      onEditCompleted = function() prefix = this.text;
   }
   //---------------------------------------

   this.postfixLabel = new Label( this );
   with ( this.postfixLabel )
   {
      text = "Postfix:";
      textAlignment = TextAlign_Right|TextAlign_VertCenter;
   }

   this.postfixEdit = new Edit( this );
   with ( this.postfixEdit )
   {
      text = postfix;
      setFixedWidth( this.font.width( "MMM" ) );
      toolTip = "<p>This is a postfix that will be appended to the file names of output corrected images.</p>";
      onEditCompleted = function() postfix = this.text;
   }


   //MasterDark image --------------------------------------------------------------------------------------

   this.MasterDark_Label = new Edit( this );
   with ( this.MasterDark_Label )
   {
      if (MasterDark) text = File.extractName(MasterDark)
      else text = "<Select MasterDark>";
      readOnly = true;
   }

   this.MasterDark_ViewList = new ToolButton( this );
   with ( this.MasterDark_ViewList )
   {
      icon = new Bitmap( ":/images/icons/select.png" );
      toolTip = "<p>Select a master dark frame or defect map image.</p>";
      onClick = function()
      {
         var ofd = new OpenFileDialog;
         ofd.multipleSelections = false;
         ofd.caption = "<Select MasterDark>";
         ofd.loadImageFilters();
         if (ofd.execute())
         {
            if (MasterDark == ofd.fileNames[0]) return;
            if (MasterDark)
            {
               masterDarkImage.forceClose();
               h = new Histogram ();
            }
            MasterDark = ofd.fileNames[0];
            parent.MasterDark_Label.text = File.extractName(MasterDark);
            Console.show();
            masterDarkImage = ImageWindow.open( MasterDark )[0];
            parent.controlEnable();
            Console.hide();
         }
      }
   }

   // ColdPixel sliders via MasterDark ------------------------------------------------------------------------
    this.ColdThresholdControl = new NumericControl( this );
    with (this.ColdThresholdControl)
    {
      label.text = "Threshold:";
      label.minWidth = labelWidth1;
      label.textAlignment = TextAlign_Right|TextAlign_VertCenter;
      toolTip = "<p>Cold pixel clipping point.</p>";
      setRange( 0, 1 );
      slider.setRange(0,65535);
      slider.minWidth = 200;
      setPrecision( 8 );
      edit.setFixedWidth( editWidth1 );
      setValue (ColdThreshold);
      onValueUpdated = function( value )
      {
         ColdThreshold = value;
         var count=0;
         var histogramLevel = Math.round( ColdThreshold*65535 );
         for (var c=0; c < channels; c++)
         {
            var max = Math.min( histogramLevel, maxLevel[c] );
            var min = minLevel[c];
            if ( (CFA) && (min==0) && (channels>1) ) min++; //for RGB cfa;
            for ( var i = min; i < max; i++ ) count += histogram[c][i];
         }
         dialog.ColdCountControl.setValue(count);
         ColdCount = count;
      }
    }

   this.ColdCountControl = new NumericControl( this );
   with (this.ColdCountControl)
   {
      label.text = "Count:";
      label.minWidth = labelWidth1;
      label.textAlignment = TextAlign_Right|TextAlign_VertCenter;
      toolTip = "<p>How many dead pixels will be clipped?</p>";
      setRange( 0, 10000 );
      slider.setRange(0,10000 );
      slider.minWidth = 200;
      setPrecision( 0 );
      edit.setFixedWidth( editWidth1 );
      setValue (ColdCount);
      onValueUpdated = function( value )
      {
         ColdCount = value;
         if ( channels > 1 )
         {
            var count = 0;
            var min = Math.min(minLevel[0],minLevel[1],minLevel[2]);
            if ( (CFA) && (min==0) && (channels>1) ) min++; //for RGB cfa;
            var max = Math.max(maxLevel[0],maxLevel[1],maxLevel[2])+1;
            for ( var i = min; i < max; i++)
            {
               for (var c=0; c<channels; c++) count += histogram[c][i];
               if ( count > value ) { ColdThreshold = i/65536; break; }
            }
         }
         else ColdThreshold = h.normalizedClipLow(value);
         dialog.ColdThresholdControl.setValue(ColdThreshold);
      }
   }

   // auto cold
   this.DetectCold_Threshold = new NumericControl(this);
   with (this.DetectCold_Threshold)
   {
      label.text = "Detection Threshold:";
      label.textAlignment = TextAlign_Right|TextAlign_VertCenter;
      toolTip = "<pre>How many times the pixel value must differ<br/>" +
                   "from the surrounding neighbors, so consider it defective?<br/>";
      setRange( 0, 100 );
      slider.setRange(0,1000000 );
      slider.minWidth = 200;
      setPrecision( 1 );
      setValue (DetectCold_Threshold);
      onValueUpdated = function( value ) DetectCold_Threshold = value;
   }

   this.DetectCold_GroupBox = new GroupBox( this );
   with (this.DetectCold_GroupBox)
   {
      title = "Auto Detect";
      titleCheckBox = true;
      checked = DetectCold;
      sizer = new VerticalSizer;
      sizer.margin = 4;
      sizer.spacing = 4;
      sizer.add( this.DetectCold_Threshold );
      onCheck = function( value )
      {
         DetectCold = value;
         this.dialog.controlEnable();
      }
   }

   this.Cold_GroupBox = new GroupBox( this );
   with (this.Cold_GroupBox)
   {
      title = "Cold Pixels";
      sizer = new VerticalSizer;
      sizer.margin = 4;
      sizer.spacing = 4;
      sizer.add( this.ColdCountControl );
      sizer.add( this.ColdThresholdControl );
      sizer.add( this.DetectCold_GroupBox );
   }


   // HotPixel sliders via MasterDark ----------------------------------------------------------------------------
    this.HotThresholdControl = new NumericControl( this );
    with (this.HotThresholdControl)
    {
      label.text = "Threshold:";
      label.minWidth = labelWidth1;
      label.textAlignment = TextAlign_Right|TextAlign_VertCenter;
      toolTip = "<p>Hot pixel clipping point.</p>";
      setRange( 0, 1 );
      slider.setRange(0,65535 );
      slider.minWidth = 200;
      setPrecision( 8 );
      edit.setFixedWidth( editWidth1 );
      setValue (HotThreshold);
      onValueUpdated = function( value )
      {
         HotThreshold = value;
         HotCount=0;
         var histogramLevel = Math.round( HotThreshold*65535 );
         for (var c=0; c<channels; c++)
         {
            var min = Math.max(histogramLevel, minLevel[c]);
            for (var i = maxLevel[c]; i >= min; i--) HotCount += histogram[c][i];
         }
         dialog.HotCountControl.setValue(HotCount);
      }
    }

   this.HotCountControl = new NumericControl( this );
   with (this.HotCountControl)
   {
      label.text = "Count:";
      label.minWidth = labelWidth1;
      label.textAlignment = TextAlign_Right|TextAlign_VertCenter;
      toolTip = "<p>How many hot pixels will be clipped?</p>";
      setRange( 0, 10000 );
      slider.setRange(0,10000 );
      slider.minWidth = 200;
      setPrecision( 0 );
      edit.setFixedWidth( editWidth1 );
      setValue (HotCount);
      onValueUpdated = function( value )
      {
         HotCount = value;
         if ( channels > 1 )
         {
            var count = 0;
            var min = Math.min(minLevel[0],minLevel[1],minLevel[2]);
            var max = Math.max(maxLevel[0],maxLevel[1],maxLevel[2]);
            for ( var i = max; i>=min; i--)
            {
               for (var c=0; c<channels; c++) count += histogram[c][i];
               if ( count > value ) { HotThreshold = i/65536; break; }
            }
         }
         else HotThreshold = h.normalizedClipHigh(value);

         dialog.HotThresholdControl.setValue(HotThreshold);
      }
   }

   // auto hot
   this.DetectHot_Threshold = new NumericControl(this);
   with (this.DetectHot_Threshold)
   {
      label.text = "Detection Threshold:";
      label.textAlignment = TextAlign_Right|TextAlign_VertCenter;
      toolTip = "<pre>How many times the pixel value must differ<br/>" +
                   "from the surrounding neighbors, so consider it defective?<br/>";
      setRange( 0, 100 );
      slider.setRange(0,1000000 );
      slider.minWidth = 200;
      setPrecision( 1 );
      setValue (DetectHot_Threshold);
      onValueUpdated = function( value ) DetectHot_Threshold = value;
   }

   this.DetectHot_GroupBox = new GroupBox( this );
   with (this.DetectHot_GroupBox)
   {
      title = "Auto Detect";
      titleCheckBox = true;
      checked = DetectHot;
      sizer = new VerticalSizer;
      sizer.margin = 4;
      sizer.spacing = 4;
      sizer.add( this.DetectHot_Threshold );
      onCheck = function( value )
      {
         DetectHot = value;
         this.dialog.controlEnable();
      }
   }

   this.Hot_GroupBox = new GroupBox( this );
   with (this.Hot_GroupBox)
   {
      title = "Hot Pixels";
      sizer = new VerticalSizer;
      sizer.margin = 4;
      sizer.spacing = 4;
      sizer.add( this.HotCountControl );
      sizer.add( this.HotThresholdControl );
      sizer.add( this.DetectHot_GroupBox );
   }


   // Transfer function --------------------------------------------------------------------------------------
   this.TransferFunction = new NumericControl(this);
   with (this.TransferFunction)
   {
      label.text = "Amount:";
      label.minWidth = labelWidth1 + 4+1; // compensate groupbox spacing+border
      label.textAlignment = TextAlign_Right|TextAlign_VertCenter;
      toolTip = "<pre>0   = no correction.<br/>" +
                   "0.5 = 50% original + 50% corrected.<br/>" +
                   "1   = 100% corrected.</pre>";
      setRange( 0, 1 );
      slider.setRange(0,1000000 );
      slider.minWidth = 200;
      setPrecision( 8 );
      edit.setFixedWidth( editWidth1 );
      setValue (TransferFunction);
      onValueUpdated = function( value ) TransferFunction = value;
   }

   // NewInstance button --------------------------------------------------------------------------------------
   this.newInstance_Button = new ToolButton( this );
   with ( this.newInstance_Button )
   {
      icon = new Bitmap( ":/images/interface/dragObject.png" );
      toolTip = "New Instance";
      onMousePress = function()
      {
         this.hasFocus = true;
         parent.exportParameters();
         this.pushed = false;
         parent.newInstance();
      }
   }


   // CFA CheckBox --------------------------------------------------------------------------------------
   this.CFA_CheckBox = new CheckBox( this );
   with ( this.CFA_CheckBox )
   {
      text = "CFA";
      checked = CFA;
      toolTip = "<p>* Check if the target frames are CFA or RGB Bayer images.<br/>" +
                   "* Uncheck if the images come from a monochrome imager.</p>";
      onCheck = function( checked )
      {
         CFA = checked;
         dialog.ColdCountControl.onValueUpdated(ColdCount);
      }
   }


   // Standart buttons --------------------------------------------------------------------------------------
   this.ok_Button = new PushButton( this );
   with ( this.ok_Button )
   {
      text = "OK";
      onClick = function()
      {
         //if (!MasterDark) return;
         parent.apply();
         this.dialog.ok();
      }
   }


   // Sizer ---------------------------------------------------------------------------------------
   this.filesButtons_Sizer = new HorizontalSizer;
   with (this.filesButtons_Sizer)
   {
      spacing = 4;
      add( this.filesAdd_Button );
      addStretch();
      add( this.filesClear_Button );
      addStretch();
      add( this.filesInvert_Button );
      add( this.filesRemove_Button );
   }

   this.outputDir_GroupBox = new GroupBox( this );
   with (this.outputDir_GroupBox)
   {
      title = "Output";
      sizer = new HorizontalSizer;
      sizer.margin = 6;
      sizer.spacing = 4;
      sizer.add( this.outputDir_Edit, 100 );
      sizer.add( this.outputDirSelect_Button );
      sizer.add( this.prefixLabel );
      sizer.add( this.prefixEdit);
      sizer.add( this.postfixLabel );
      sizer.add( this.postfixEdit);

   }

   this.MasterDark_Sizer = new HorizontalSizer;
   with ( this.MasterDark_Sizer )
   {
      spacing = 4;
      add( this.MasterDark_Label );
      add( this.MasterDark_ViewList,1);
   }

   this.buttons_Sizer = new HorizontalSizer;
   with ( this.buttons_Sizer )
   {
      add( this.newInstance_Button );
      addStretch();
      add( this.CFA_CheckBox,1 );
      spacing = 92;
      add( this.ok_Button );
   }

   this.sizer = new VerticalSizer;
   with ( this.sizer )
   {
      margin = 6;
      spacing = 6;
      add( this.helpLabel );
      add( this.files_TreeBox );
      add( this.filesButtons_Sizer );
      add( this.outputDir_GroupBox );
      add( this.MasterDark_Sizer );
      add( this.Cold_GroupBox );
      add( this.Hot_GroupBox );
      add( this.TransferFunction );
      add( this.buttons_Sizer );
   }

   this.adjustToContents();
   this.setMinSize();

   this.controlEnable = function ()
   {
      var e = Boolean( MasterDark );
      with (this.dialog)
      {
         ColdThresholdControl.enabled=e;
         ColdCountControl.enabled=e;
         HotThresholdControl.enabled=e;
         HotCountControl.enabled=e;

         if ( DetectHot_GroupBox.checked || DetectCold_GroupBox.checked )
            newInstance_Button.enabled = true;
         else
            newInstance_Button.enabled=e;

         //CFA_CheckBox.enabled=e;
         if ( inputFiles.length > 0 )
         {
            if ( DetectHot_GroupBox.checked || DetectCold_GroupBox.checked )
               ok_Button.enabled = true;
            else
               ok_Button.enabled=e;
         }
         else ok_Button.enabled=false;
         if ( e && h.isEmpty )
         {
            channels = masterDarkImage.mainView.image.numberOfChannels;
            Console.write("Generate histogram for MasterDark.");
            Console.flush();

            for (var c=0; c < channels; c++)
            {
               masterDarkImage.mainView.image.selectedChannel = c;
               h.generate(masterDarkImage.mainView.image);
               histogram[c] = h.toArray();
               minLevel[c] = 0;
               for (var i=0; i < 65536; i++)
                  if (histogram[c][i] !=0) { minLevel[c] = i; break; }
               maxLevel[c] = 65535;
               for (var i=65535; i > -1 ; i--)
                  if (histogram[c][i] !=0) { maxLevel[c] = i; break; }

               //Console.show();
               //Console.writeln("minLevel[c]",minLevel[c]);
               //Console.writeln("maxLevel[c]",maxLevel[c]);
            }
            HotThresholdControl.onValueUpdated(HotThreshold);
            ColdThresholdControl.onValueUpdated(ColdThreshold);
           
            //HotCountControl.onValueUpdated(HotCount);
            //ColdCountControl.onValueUpdated(ColdCount);
            Console.writeln(" Done.");
            Console.flush();
         }
      }
   }
   
   //if ( !Parameters.isGlobalTarget && !Parameters.isViewTarget )
      this.controlEnable();

   this.engine = function(LightImage)
   {
      var img = LightImage.mainView.image;
      var TempWin = new ImageWindow( img.width, img.height, img.numberOfChannels,
                          img.bitsPerSample, img.isReal, img.isColor, "Convolved");
      var TempView = TempWin.mainView;
      with (TempView)
      {
         beginProcess( UndoFlag_NoSwapFile );
         console.writeln( "Processing CFA:", CFA );
         image.apply( img );
         if (CFA)
            image.convolve([ 1,0,1,0,1,
                             0,0,0,0,0,
                             1,0,0,0,1,
                             0,0,0,0,0,
                             1,0,1,0,1 ]);
         else
            image.convolve([ 1,1,1,
                             1,0,1,
                             1,1,1 ]);
         endProcess();
      }

      var mergeThem = new PixelMath;
      with (mergeThem)
      {
         var NegativeTF = 1 - TransferFunction;
         //start iif
         expression = "iif( ";

         if ( MasterDark ) // set if dark selected
         { // PixelMath iif ( (MasterDark >= HotPixelThreshold) or(MasterDark < ColdPixelThreshold) , (Convolved*x+targetView*(1-x)) , targetView )
            var MDWin = masterDarkImage;
            expression +=
               "( " + MDWin.mainView.id + " >= " + HotThreshold + " )" +
               "||" +
               "( " + MDWin.mainView.id + " < " + ColdThreshold + " )";
         }

         if ( DetectHot ) // add more iif($T > mean*k) DetectHot_GroupBox is checked
         {
            if ( MasterDark ) expression += "||";

            expression +=
               "( " + LightImage.mainView.id + " >= " +
               TempWin.mainView.id + " * " + DetectHot_Threshold + " )";
         }

         if ( DetectCold  ) // add more iif(mean*k < $T) DetectCold_GroupBox is checked
         {
            if ( MasterDark || DetectHot ) expression += "||";

            expression +=
               "( " + TempWin.mainView.id + " * " + DetectHot_Threshold +
               " <= " + LightImage.mainView.id + " )";
         }

         expression +=
         // do this
            ", ";
            if ( TransferFunction == 1) // just for faster calculation
            {
               expression += TempWin.mainView.id;
            }
            else if ( TransferFunction == 0)
            {
               expression += LightImage.mainView.id;
            }
            else
            {
               expression += TempWin.mainView.id+ " * " +TransferFunction+
                  " + " +LightImage.mainView.id+ " * " +NegativeTF;
            }
         // else do this:
            expression += ", " + LightImage.mainView.id

         //end iif
            + " )";

         useSingleExpression = true;
         rescale = false;

         if ( LightImage.visible) // execute on Image on the screen
         {  // create new image with STF
            createNewImage = true;
            newImageId = prefix + LightImage.mainView.id + postfix;
            executeOn( LightImage.mainView, false );
            var stf = LightImage.currentView.stf;
            ImageWindow.windowById(newImageId).currentView.stf = stf ;
         }
         else
         {
            executeOn( LightImage.mainView, false ); // execute on ImageContainer or FileList
         }
      }
      TempWin.close();
   }

   this.apply = function()
   {
      console.show();
      for ( var i = 0; i < inputFiles.length; ++i )
      {
         var LightImage = ImageWindow.open( inputFiles[i] )[0];
         this.engine(LightImage);
         if (outputDirectory) var outputFilePath = outputDirectory;
         else var outputFilePath = File.extractDrive(inputFiles[i]) + File.extractDirectory(inputFiles[i]);
         outputFilePath += "/" + prefix + File.extractName(inputFiles[i]) + postfix +  File.extractExtension(inputFiles[i]);
         if ( File.exists( outputFilePath ) )
            for ( var u = 1; ; ++u )
            {
               var tryFilePath = File.appendToName( outputFilePath, u.toString() );
               if ( !File.exists( tryFilePath ) ) { outputFilePath = tryFilePath; break; }
            }
         LightImage.saveAs( outputFilePath, false, false, false, false );
         LightImage.forceClose();
      }
   }

   this.applyToVievTarget = function()
   {
      var LightImage = ImageWindow.windowById(Parameters.targetView.id);
      this.engine(LightImage);
      this.onHide();
   }

   this.onHide = function()
   {
      if (!MasterDark) return;
      if (!masterDarkImage.isNull) masterDarkImage.forceClose();
   }
}


CosmeticCorrectionDialog.prototype = new Dialog;
var dialog = new CosmeticCorrectionDialog;
if ( Parameters.isViewTarget ) dialog.applyToVievTarget();
else
{
   console.hide();
   dialog.execute();
}
« Last Edit: 2012 March 18 08:46:41 by NKV »