Removing Canon Banding with PJSR

georg.viehoever

Well-known member
Hi,

quite a while ago there was a short thread on the horizontal banding issue that appears to plague certain Canon DSLRs http://pixinsight.com/forum/index.php?topic=898.0. I now aim to reduce it with a PJSR script . The basic idea is to adjust each line of the image based on a the difference between the global background and the line background.

I currently have the problem that setting a new pixel value in the adjusted image appears not to work as expected. I only get some red pixels in the processed rows as a result. I am probably doing somthing wrong with Image.setSample() and Image.sample() here.  And surely there must be a faster way to do the adjustment then setting each pixel, maybe with PixelMath? Advice is welcome.

Georg

Code:
function BandingEngine() {

  // Populates Params
   this.load = function(data) {
   };  // method load()
   this.doit = function(data) {
      var targetImage=data.targetImage;
      var targetHeight=20; //targetImage.height;
      var targetWidth=targetImage.width;
      //rescale each row to median
      // this represents the average background value
      var median=targetImage.median();
      console.writeln("Median=",data.targetImage.median());
      targetImage.initializeStatus("Processing rows", targetHeight);
      var lineRect=new Rect(targetWidth,1);
      // Tell the core application that we are going to change this view.
      // Without doing this, we'd have just read-only access to the view's image.
      data.targetView.beginProcess();

      for (var row=0; row<targetHeight;++row) {
         lineRect.moveTo(0,row);
         targetImage.selectedRect=lineRect;
         var lineMedian=targetImage.median();
         var fixFactor=median-lineMedian;
         console.writeln("Row, Median, fixFactor=",row,",",lineMedian,",",fixFactor);
         for (var col=0;col<targetWidth;++col){
            var sample=targetImage.sample(col,row);
            var fixedSample=sample+fixFactor;
            console.writeln("col, sample, fixed=",col,",",sample,",",fixedSample);
            targetImage.setSample(fixedSample,col,row)
         }   
         targetImage.advanceStatus(1);
      }
      // Done with view.
      data.targetView.endProcess();
   };  //method doit()
}
 
Hello,

I just replaced the inner loop by an apply in the hope to get a working (and faster) solution. But now PI always crashes. Help!!  :'(
Georg

Code:
         var fixFactor=median-lineMedian;
         console.writeln("Row, Median, fixFactor=",row,",",lineMedian,",",fixFactor,",",ImageOp_Add);
         targetImage.apply(fixFactor,ImageOp_Add); //<---crashing here....
 
Hi Georg,

It seems you've found a regression in version 1.5. Indeed the core application crashes without palliatives upon executing the Image.apply method in this case. Let me take a deep look at this and I'll get back with (I hope) a workaround.

Nice script and initiative, by the way. And welcome to JavaScript programming on PixInsight! :D
 
Thanks!

In the meantime, a dear friend gave me a hint to a German astrophotography book ?Digitale Astrophotografie? by Axel Martin, Bernd Koch. On page 302 it states that a free program called "Fitswork" has a remedy for the Canon banding problem http://freenet-homepage.de/JDierks/softw.htm. It has a functionality called "Edit/Flatten/Rows equal brighness" (in its german menus: "Bearbeiten/Ebnen/Zeilen gleichhell"). It did wonders for one of my banded images, see
fitworkFlattenRows.jpg

http://cid-c56e60845cfa6790.skydrive.live.com/self.aspx/Fitswork/fitworkFlattenRows.jpg.

I was able to make contact with Mr. Dierks, and he actually had had an idea similar to the one I was following. He told me some details of how he did it, and gave the permission to implement it as an open source script. That's what I intent to do for Pixinsight.

Georg
 
I have good news :)

I have identified and fixed the bug without problems (it was an "optimization" I made to some PCL routines).

This is the script I have used to test your routine:

Code:
#include <pjsr/ImageOp.jsh>
#include <pjsr/SampleType.jsh>

function BandingEngine() {

   if ( !ImageWindow.activeWindow.isNull )
      this.targetView = ImageWindow.activeWindow.currentView;

   this.processImage = function( img )
   {
      var height = img.height;
      var width = img.width;

      // rescale each row to median
      // this represents the average background value
      var median = img.median();
      console.writeln("Median=",median);

      img.initializeStatus("Processing rows", height);
      var lineRect=new Rect(width,1);

      for (var row=0; row<height; ++row) {
         lineRect.moveTo(0,row);
         img.selectedRect=lineRect;
         var lineMedian=img.median();
         var fixFactor=median-lineMedian;
         console.writeln("Row, Median, fixFactor=",row,",",lineMedian,",",fixFactor);

         /* ### This crashes in PI version 1.5 (and probably in 1.4.5, too - not tested)
         img.apply(fixFactor,ImageOp_Add);
         */

         // ### Until PI 1.5.1 is released, this is the (slow!) workaround.
         for (var col=0;col<width;++col){
            var sample=img.sample(col,row);
            var fixedSample=sample+fixFactor;
            img.setSample(fixedSample,col,row);
         }

         img.advanceStatus(1);
      }

      img.resetSelections();
      img.truncate();
      img.normalize();
   };

   this.doit = function()
   {
      with ( this.targetView )
      {
         beginProcess();

         if ( image.sampleType == SampleType_Integer )
         {
            var wrk = new Image( image.width, image.height,
                                 image.numberOfChannels, image.colorSpace,
                                 (image.bitsPerSample < 32) ? 32 : 64, SampleType_Real );
            wrk.assign( image );
            this.processImage( wrk );
            image.assign( wrk );
         }
         else
            this.processImage( image );

         endProcess();
      }
   };  //method doit()
}

var engine = new BandingEngine;

engine.doit();

It improves but still doesn't fix banding completely. Note that this routine must be applied to floating point pixel data, since it generates values outside the native ranges of integer images (e.g., negative values). For this reason I create a working floating point duplicate of the target image if it is integer.

As indicated by the comments starting with ###, while I release PI 1.5.1 you'll have to use the slow pixel-by-pixel workaround. It's pretty fast anyway ;)
 
Thank you!

I would like to combine this with a slider and a preview. Can you point me to an example that illustrates how to use previews with PJSR scripts (if possible at all)? I did not find a Preview class, but noticed that Views has a preview attribute.

Georg
 
Hi Georg,
If you can wait until this evening, I am going to upload my 'fully documented' PJSR script that I have written to allow me to investigate the deBayering of RAW CMYK images. Writing this script also gave me my 'start' as far as the world of PJSR is concerned.

Hopefully some of my commented routines will be of use to you as well as to others.

Cheers,
 
Niall,
Yes, I can wait (if absolutely unavoidable  :) ). Feel free to send me your code via private message if you feel it is not yet ready for the big show. I just need an example that gives me an idea how to do it.
Georg
 
Hello,

so here is my first try at a complete script. Thanks to all those who supported me.

I still consider the script and method in alpha state, so don't expect it to be perfect. There are quite a number of things that I would like to try, but I need some more time and support. But first, a result:

pixinsightFlattenRowsAlpha.jpg

http://cid-c56e60845cfa6790.skydrive.live.com/self.aspx/Pixinsight/pixinsightFlattenRowsAlpha.jpg

The right image is the processed one, and shows much less banding already. For those who want to give it a try at this stage: The code is attached below.

Issues:
- I would very much like to have an interactive preview when the user adjusts the amount slider. However, all my tries failed:
- if you define "#define HAS_PREVIEW true", the GUI gets a new check item. This item can be toggled a couple of times, after which neither "OK" nor "Cancel" work any longer. No idea why this is the case, maybe a bug in my code that I don't see, maybe something else.
-I did not find a way to create a preview that changes with the slider, preferably the Pixinsight Preview Object.

Your ideas and idvice are welcome!

Enjoy,

Georg

Code:
/*
   CanonBanding.js v0.1.0
   Joins previews into a new image.
   Copyright (C) 2009 Georg Viehoever

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

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

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

/*
   Changelog:
   0.2.0: Working processing as shown by Juan Conejero. Introduced
          elements from Nial Saunder's Debayer script (beta version)
   0.1.0: Initial version, modelled after Preview Allocator script provided with PixInsight
*/

// ======== #features ==============================================================
#feature-id    Utilities > Canon Banding Reducton

#feature-info  Attempts to reduce the horizontal banding plaguing some Canon DSLRs
 \
               This script allows you reduce the banding by equalizing horizontal background \
               brightness

// #feature-icon  Batch_CanonBandingReduction.xpm


// ========= # defines =============================================================

/// define for debugging output
#define DEBUGGING_MODE_ON false
/// define true if img.apply(number, op) does not work, as in PI1.5
#define HAS_APPLY_BUG true
/// somehow a true does not do any good
#define HAS_PREVIEW false

#define TITLE  "CanonBanding"
#define VERSION "0.1.0"

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

// include constants
#include <pjsr/ImageOp.jsh>
#include <pjsr/SampleType.jsh>



/// Does the work using the params in data
function BandingEngine() {

   // process the data
   this.processImage = function(targetImage, amountValue) {
      var targetHeight=targetImage.height;
      var targetWidth=targetImage.width;
      //rescale each row to median
      // this represents the average background value
      var median=targetImage.median();
         console.writeln("AmountValue=",amountValue);
      if ( DEBUGGING_MODE_ON ){
         console.writeln("Median=",targetImage.median());
      }  // if debugging
      targetImage.initializeStatus("Processing rows", targetHeight);
      var lineRect=new Rect(targetWidth,1);

      for (var row=0; row<targetHeight;++row) {
         lineRect.moveTo(0,row);
         targetImage.selectedRect=lineRect;
         var lineMedian=targetImage.median();
         var fixFactor=(median-lineMedian)*amountValue;
         if ( DEBUGGING_MODE_ON ){
            console.writeln("Row, Median, fixFactor=",row,",",lineMedian,",",fixFactor,",",ImageOp_Add);
         }  // if debugging
         if (! HAS_APPLY_BUG){
            // This crashes in PI version 1.5 (and probably in 1.4.5, too - not tested)
            targetImage.apply(fixFactor,ImageOp_Add);
         }else{
            // Workaround
            for (var col=0;col<targetWidth;++col){
               var sample=targetImage.sample(col,row);
               var fixedSample=sample+fixFactor;
               targetImage.setSample(fixedSample,col,row);
            }  //for col
         }  // if HAS_APPLY_BUG
         targetImage.advanceStatus(1);
      }  //for row
      targetImage.resetSelections();
      targetImage.truncate();
      targetImage.normalize();

   };  //method processImage()


   // do the actual work
   this.doit=function(targetView,amountValue){
      // Tell the core application that we are going to change this view.
      // Without doing this, we'd have just read-only access to the view's image.
      targetView.beginProcess();
      var targetImage=targetView.image;
      // convert image to floating point format, since int would not handle negatives or overflows
      if ( targetImage.sampleType == SampleType_Integer ){
         var wrk = new Image( targetImage.width, targetImage.height,
                                 targetImage.numberOfChannels, targetImage.colorSpace,
                                 (targetImage.bitsPerSample < 32) ? 32 : 64, SampleType_Real );
            wrk.assign( targetImage );
            this.processImage( wrk, amountValue );
            targetImage.assign( wrk );
      }else{
         // work directly on targetImage
         this.processImage(targetImage, amountValue);
      }  // if integer image
      // end transaction
      targetView.endProcess();
    };   //function doit
}

/// dialog for getting params and starting process
function BandingDialog() {
   this.__base__ = Dialog;
   this.__base__();

   //init values
   this.previewWindow=null;
   //default value fof slider
   this.amountValue=1.0;

   // ----- HELP LABEL

   this.helpLabel = new Label (this);
   this.helpLabel.frameStyle = FrameStyle_Box;
   this.helpLabel.margin = 4;
   this.helpLabel.wordWrapping = true;
   this.helpLabel.useRichText = true;
   this.helpLabel.text = "<b>" + TITLE + " v"+VERSION+"</b> &mdash; A script to remove " +
      "Canon Banding from View.";

   // ------ CHECKBOX
   if ( HAS_PREVIEW){
      this.previewCheckBox = new CheckBox( this );
      this.previewCheckBox.text = "Display Preview";
      this.previewCheckBox.toolTip ="<p>If this option is selected, the will display a preview</p>";
      this.previewCheckBox.onCheck = function(checked){
         if ( DEBUGGING_MODE_ON ){
            console.writeln("previewCheckBox.onCheck()",checked);
         }  // if debugging
         var dialog=this.dialog;
         if(checked){
            var window = ImageWindow.activeWindow;
            if ( !window.isNull ){
               if ( DEBUGGING_MODE_ON ){
                  console.writeln("previewCheckBox.onCheck(): Creating Preview");
               }  // if debugging
               //dialog.previewWindow=new ImageWindow(window);
               //this.previewWindow.bringToFront();
               // this.previewWindow.show();
            }  // if active window exists 
         }else{   //checked
            // FIXME somehow this just would not work...
            if ( DEBUGGING_MODE_ON ){
               console.writeln("previewCheckBox.onCheck(): dialog.previewWindow=",dialog.previewWindow);
            }  // if debugging
            if (!dialog.previewWindow.isNull){
               if ( DEBUGGING_MODE_ON ){
                  console.writeln("previewCheckBox.onCheck(): Deleting Preview");
             }  // if debugging
//            dialog.previewWindow.forceClose();
//            dialog.previewWindow=null;
         }  // if preview window exists           
       } // if checked
      }; //onCheck()
   }  // if HAS_PREVIEW

   // ----- SLIDER
   var labelWidth1 = this.font.width( 'T' + "" );
   this.amountControl=new NumericControl(this);
    with (this.amountControl) {
      label.text = "Amount:";
      label.minWidth = labelWidth1;
      setRange( 0, 4.0 );
      slider.setRange( 0, 1000 );
      slider.minWidth = 250;
      setPrecision( 2 );
      setValue( this.amountValue );
      toolTip = "Define amount of correction";  // ### TODO: write tooltip info
      onValueUpdated = function( value ) {
         this.dialog.amountValue=value;
         //FIXME
         //data.imHh = value;
         //data.combineDone = false;
         }; //function onValueUpdated();
      }  //with amountControl   

   // ----- BUTTONS

   this.ok_Button = new PushButton (this);
   this.ok_Button.text = "OK";
   // Do it
   this.ok_Button.onClick = function() {
      if ( DEBUGGING_MODE_ON ){
         console.writeln("ok_Button.onClick()");
      }  // if debugging
      this.dialog.ok();
   };

   this.cancel_Button = new PushButton (this);
   this.cancel_Button.text = "Cancel";
   this.cancel_Button.onClick = function() {
      if ( DEBUGGING_MODE_ON ){
         console.writeln("cancel_Button.onClick()");
      }  // if debugging
      this.dialog.cancel();
   };

   // ----- PACK EVERYTHING INTO LAYOUT

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


   this.sizer = new VerticalSizer;
   this.sizer.margin = 6;
   this.sizer.spacing = 6;
   this.sizer.add (this.helpLabel);
   if ( HAS_PREVIEW){
      this.sizer.addSpacing (4);
      this.sizer.add (this.previewCheckBox);
   }  // if HAS_PREVIEW
   this.sizer.addSpacing (4);
   this.sizer.add (this.amountControl);   
   this.sizer.addSpacing (4);
   this.sizer.add (this.buttons_Sizer);

   this.windowTitle = TITLE + " Script v" + VERSION;
   this.adjustToContents();
}  //class BandingDialog
BandingDialog.prototype = new Dialog;


function main() {

   // to display progress
   console.show();
   // allow stop
   console.abortEnabled = true;

   if ( DEBUGGING_MODE_ON ){
      console.writeln ("Dialog opening");
   }  // if debug mode

   // Dialog
   var dialog = new BandingDialog();
   // Global parameters.

   for (;;) {
      if (!dialog.execute()){
         // cancel...
         break;
      }
      var amountValue=dialog.amountValue;
      if ( DEBUGGING_MODE_ON ){
         console.writeln("main(): amountValue=",amountValue);
      }  // if debugging
      var window = ImageWindow.activeWindow;
      if ( window.isNull )
         throw new Error( "No active image" );
      if ( !window.currentView.isNull ){
         var engine = new BandingEngine();
         engine.doit(window.currentView, amountValue);
         // Quit after successful execution./
         break;
      }else{
         var msg = new MessageBox(
               "<p>No action was selected. Do you want to continue?</p>",
               TITLE, StdIcon_Warning, StdButton_Yes, StdButton_No );
         if ( msg.execute() != StdButton_Yes )
            break;
      }  //if !view.isNull
   }  //for
   console.hide();
}  //main()

main();
 
Juan,

in addition to the issues with the preview detailed in the last message:

Code:
var sample=targetImage.sample(col,row);
var fixedSample=sample+fixFactor;
targetImage.setSample(fixedSample,col,row);

appears to process the red channel only. What I really would like to do is to apply a fix factor to each RGB channel. How can I do this? Is the median calculation also only done on the red channel? Is is not really clear to me how channels are handled in this context.

Kind regards,
Georg
 
Hi Georg,

I'm working with your script because it exposes some problems of the PJSR that I want to fix in 1.5.x. One of them is that

Code:
ImageWindow( ImageWindow )

is not creating a new image window, but just an alias to an existing object. This emulates PCL behavior, but is incorrect in the context of the JavaScript runtime. I'm fixing this issue as well as others, and I'll post an updated version later this afternoon.

Regarding your question, you must select the desired channel with setSample:

Code:
void Image.setSample( Number v, Point p[, int c] )
void Image.setSample( Number v, int x, int y[, int c] )

Note that the last (optional) argument allows you to specify an existing channel, as in:

Code:
var sample=targetImage.sample(col,row,2);
var fixedSample=sample+fixFactor;
targetImage.setSample(fixedSample,col,row,2);

which would work with channel #2 (blue). If you don't specify a channel, channel #0 (red) is assumed, again to emulate PCL behavior.

Hope this helps.
 
Great, just what I need! Waiting for the fixed release.

Remaining questions:

- how to I calculate the median per channel?
-is it possible to use the preview window from PJRS (like for example done in histogram transform)?

Georg
 
Hi Georg,

Have a look a my CMYG deBayer script again - around line 600, or so (I don't have the script with me at the moment, but I seem to remember that line number from 'late' last night !).

I use the 'median' of each channel to line up my histogram peak after deBayering. Maybe the commented code sample is what you are after?

Cheers,
 
Niall,

your hint to line 600 indeed resolved the median-mystery. Thank you. So now only the preview-question remains...

Georg
 
Hi,

the script is getter better, slowly. I now fix all 3 channels, and use a separate median for each channel. The results are even better now. I appear to have problems with bright parts of the picture that are over-corrected (become too dark). I have some ideas how to adjust for that. But not today. Below is the script applied to 2 images that were so much hurt by banding that I did not fully process them 9 months ago. The shown version are from images stacked with DSS, and then just applied a quick DBE and STF to see what is in them, followed by the current source code. Still "alpha" status.

Georg

pixinsightFlattenRowsAlpha2.jpg

http://cid-c56e60845cfa6790.skydrive.live.com/self.aspx/Pixinsight/pixinsightFlattenRowsAlpha2.jpg

Code:
/*
   CanonBanding.js v0.3.0
   Joins previews into a new image.
   Copyright (C) 2009 Georg Viehoever

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

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

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

/*
   Changelog:
   0.3.0: Fixing all channels, automatic workaround for PI1.5 and earlier. First tested with 1.5.0
   0.2.0: Working processing as shown by Juan Conejero. Introduced
          elements from Nial Saunder's Debayer script (beta version)
   0.1.0: Initial version, modelled after Preview Allocator script provided with PixInsight
*/

// ======== #features ==============================================================
#feature-id    Utilities > Canon Banding Reducton

#feature-info  Attempts to reduce the horizontal banding plaguing some Canon DSLRs
 \
               This script allows you reduce the banding by equalizing horizontal background \
               brightness

// #feature-icon  Batch_CanonBandingReduction.xpm


// ========= # defines =============================================================

/// define for debugging output
#define DEBUGGING_MODE_ON false

/// somehow a true does not do any good
#define HAS_PREVIEW false

#define TITLE  "CanonBanding"
#define VERSION "0.1.0"

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

// include constants
#include <pjsr/ImageOp.jsh>
#include <pjsr/SampleType.jsh>



/// Does the work using the params in data
function BandingEngine() {

   // process the data
   this.processImage = function(targetImage, amountValue) {
      var targetHeight=targetImage.height;
      var targetWidth=targetImage.width;
      //rescale each row to median
      // this represents the average background value
      var medians=new Array;
      /// true if img.apply(number, op) does not work, as in PI1.5.0 (build 492)
      var hasApplyBug=(coreVersionBuild<=492)
      for (var chan=0; chan<targetImage.numberOfChannels;++chan){
         targetImage.selectedChannel=chan;
         medians[chan]=targetImage.median();
      }  //for channel
      if ( DEBUGGING_MODE_ON ){
         console.writeln("Median=",medians);
      }  // if debugging
      targetImage.initializeStatus("Processing rows", targetHeight);
      var lineRect=new Rect(targetWidth,1);

      for (var chan=0; chan<targetImage.numberOfChannels;++chan){
         for (var row=0; row<targetHeight;++row) {
            targetImage.selectedChannel=chan;
            lineRect.moveTo(0,row);
            targetImage.selectedRect=lineRect;
            var lineMedian=targetImage.median();
            var fixFactor=(medians[chan]-lineMedian)*amountValue;
            if ( DEBUGGING_MODE_ON ){
               console.writeln("Row, Channel, Median, fixFactor=",row,",",chan,",",lineMedian,",",fixFactor,",",ImageOp_Add);
            }  // if debugging
            if (! hasApplyBug){
               // This crashes in PI version 1.5 (and probably in 1.4.5, too - not tested)
               targetImage.apply(fixFactor,ImageOp_Add);
            }else{
               // Workaround
               for (var col=0;col<targetWidth;++col){
                  var sample=targetImage.sample(col,row,chan);
                  var fixedSample=sample+fixFactor;
                  targetImage.setSample(fixedSample,col,row,chan);
               }  //for col
            }  // if HAS_APPLY_BUG
            targetImage.advanceStatus(1);
         }  //for row
      }  //for channel
      
      targetImage.resetSelections();
      targetImage.truncate();
      targetImage.normalize();

   };  //method processImage()


   // do the actual work
   this.doit=function(targetView,amountValue){
      // Tell the core application that we are going to change this view.
      // Without doing this, we'd have just read-only access to the view's image.
      targetView.beginProcess();
      var targetImage=targetView.image;
      // convert image to floating point format, since int would not handle negatives or overflows
      if ( targetImage.sampleType == SampleType_Integer ){
         var wrk = new Image( targetImage.width, targetImage.height,
                                 targetImage.numberOfChannels, targetImage.colorSpace,
                                 (targetImage.bitsPerSample < 32) ? 32 : 64, SampleType_Real );
            wrk.assign( targetImage );
            this.processImage( wrk, amountValue );
            targetImage.assign( wrk );
      }else{
         // work directly on targetImage
         this.processImage(targetImage, amountValue);
      }  // if integer image
      // end transaction
      targetView.endProcess();
    };   //function doit
}  //class BandingEngine

/// dialog for getting params and starting process
function BandingDialog() {
   this.__base__ = Dialog;
   this.__base__();

   //init values
   this.previewWindow=null;
   //default value fof slider
   this.amountValue=1.0;
   this.imageProcessed=false;

   // ----- HELP LABEL

   this.helpLabel = new Label (this);
   this.helpLabel.frameStyle = FrameStyle_Box;
   this.helpLabel.margin = 4;
   this.helpLabel.wordWrapping = true;
   this.helpLabel.useRichText = true;
   this.helpLabel.text = "<b>" + TITLE + " v"+VERSION+"</b> &mdash; A script to remove " +
      "Canon Banding from View.";

   // ------ CHECKBOX
   if ( HAS_PREVIEW){
      this.previewCheckBox = new CheckBox( this );
      this.previewCheckBox.text = "Display Preview";
      this.previewCheckBox.toolTip ="<p>If this option is selected, the will display a preview</p>";
      this.previewCheckBox.onCheck = function(checked){
         if ( DEBUGGING_MODE_ON ){
            console.writeln("previewCheckBox.onCheck()",checked);
         }  // if debugging
         var dialog=this.dialog;
         if(checked){
            var window = ImageWindow.activeWindow;
            if ( !window.isNull ){
               if ( DEBUGGING_MODE_ON ){
                  console.writeln("previewCheckBox.onCheck(): Creating Preview");
               }  // if debugging
               //dialog.previewWindow=new ImageWindow(window);
               //this.previewWindow.bringToFront();
               // this.previewWindow.show();
            }  // if active window exists 
         }else{   //checked
            // FIXME somehow this just would not work...
            if ( DEBUGGING_MODE_ON ){
               console.writeln("previewCheckBox.onCheck(): dialog.previewWindow=",dialog.previewWindow);
            }  // if debugging
            if (!dialog.previewWindow.isNull){
               if ( DEBUGGING_MODE_ON ){
                  console.writeln("previewCheckBox.onCheck(): Deleting Preview");
             }  // if debugging
//            dialog.previewWindow.forceClose();
//            dialog.previewWindow=null;
         }  // if preview window exists           
       } // if checked
      }; //onCheck()
   }  // if HAS_PREVIEW

   // ----- SLIDER
   var labelWidth1 = this.font.width( 'T' + "" );
   this.amountControl=new NumericControl(this);
    with (this.amountControl) {
      label.text = "Amount:";
      label.minWidth = labelWidth1;
      setRange( 0, 4.0 );
      slider.setRange( 0, 1000 );
      slider.minWidth = 250;
      setPrecision( 2 );
      setValue( this.amountValue );
      toolTip = "Define amount of correction";
      onValueUpdated = function( value ) {
         //console.writeln("processing in slider, amount=",value);
         this.dialog.amountValue=value;
         return;
         var currentWindow = ImageWindow.activeWindow;
         if (!currentWindow.isNull){
            var currentView=currentWindow.currentView;
            if (!currentView.isNull){
               if (this.dialog.isImageProcessed){
                  // roll back
                  currentWindow.undo();
               }  // if roll back
               var engine = new BandingEngine();
               engine.doit(currentView, value);
               this.dialog.isImageProcessed=true;
               console.writeln("processed in slider");
            }  // if currentview
         }  //if currentWindow
      }; //function onValueUpdated();
   }  //with amountControl

   // ----- BUTTONS

   this.ok_Button = new PushButton (this);
   this.ok_Button.text = "OK";
   // Do it
   this.ok_Button.onClick = function() {
      if ( DEBUGGING_MODE_ON ){
         console.writeln("ok_Button.onClick()");
      }  // if debugging
      this.dialog.ok();
   };

   this.cancel_Button = new PushButton (this);
   this.cancel_Button.text = "Cancel";
   this.cancel_Button.onClick = function() {
      if ( DEBUGGING_MODE_ON ){
         console.writeln("cancel_Button.onClick()");
      }  // if debugging
      this.dialog.cancel();
   };

   // ----- PACK EVERYTHING INTO LAYOUT

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


   this.sizer = new VerticalSizer;
   this.sizer.margin = 6;
   this.sizer.spacing = 6;
   this.sizer.add (this.helpLabel);
   if ( HAS_PREVIEW){
      this.sizer.addSpacing (4);
      this.sizer.add (this.previewCheckBox);
   }  // if HAS_PREVIEW
   this.sizer.addSpacing (4);
   this.sizer.add (this.amountControl);   
   this.sizer.addSpacing (4);
   this.sizer.add (this.buttons_Sizer);

   this.windowTitle = TITLE + " Script v" + VERSION;
   this.adjustToContents();
}  //class BandingDialog
BandingDialog.prototype = new Dialog;


function main() {

   // to display progress
   console.show();
   // allow stop
   console.abortEnabled = true;

   if ( DEBUGGING_MODE_ON ){
      console.writeln("Version Info:", coreId,",",coreVersionBuild,",",coreVersionMajor,
                      ",", coreVersionMinor,",",coreVersionRelease);
      console.writeln ("Dialog opening");
   }  // if debug mode

   // Dialog
   var dialog = new BandingDialog();
   // Global parameters.

   for (;;) {
      if (!dialog.execute()){
         // cancel...
         break;
      }
      var amountValue=dialog.amountValue;
      if ( DEBUGGING_MODE_ON ){
         console.writeln("main(): amountValue=",amountValue);
      }  // if debugging
      var window = ImageWindow.activeWindow;
      if ( window.isNull )
         throw new Error( "No active image" );
      if ( !window.currentView.isNull ){
         var engine = new BandingEngine();
         engine.doit(window.currentView, amountValue);
         // Quit after successful execution./
         break;
      }else{
         var msg = new MessageBox(
               "<p>No action was selected. Do you want to continue?</p>",
               TITLE, StdIcon_Warning, StdButton_Yes, StdButton_No );
         if ( msg.execute() != StdButton_Yes )
            break;
      }  //if !view.isNull
   }  //for
   console.hide();
}  //main()

main();
 
Hi,

here is another iteration of the script. I have added a mechanism ("Highlight Protection") to reduce the overcorrection caused by large bright areas in the image, based on an idea by Jens Dierks, the author of Fitswork. In essence, it uses AvgDev() (or MAD, as some call it) to estimate the noise sigma, and to reject pixels that are outside of a range defined by [0, median+sigma*sigmaFactor). I am using MAD() instead of StdDev, because it is a better estimator for sigma in the type of images we handle here. Have a look at the statistics window and the histogram in the screenshot. StdDev() clearly does not reflect the fact that the red distribution is much broader than green and blue.

The screenshot shows (from top left to bottom right, all with the same STF):
- Original image (DBEed, no noise reduction etc.)
- Fixed image without Highlight Protection. You clearly see the darker region left of m101
- Fixed image with Highlight Protection, SigmaFactor=1.0. Dark region is clearly reduced.
- Fixed image with Highlight Protection, SigmaFactor=0.3. Unfortunately, the banding begins to reappear...
- Statistics Window, focused on the Original Image
- Histogram Window

pixinsightFlattenRowsAlpha3.jpg

http://cid-c56e60845cfa6790.skydrive.live.com/self.aspx/Pixinsight/pixinsightFlattenRowsAlpha3.jpg

Finding the right SigmaFactor is a bit difficult, and some preview functionality would certainly help when fiddling with it. I am waiting/hoping for release 1.5.x (x>0) to implement this.
The code is attached. Please let me know how it works for you. Feel free to improve the tool.

Georg
 

Attachments

  • CanonBanding.js
    15.3 KB · Views: 56
Hi Georg,

Excellent work! I agree completely with everything you say about the average absolute deviation (or mean absolute deviation = MAD). It is a much more robust estimator of the true distribution variability in images than the standard deviation. I use AvgDev all the time, and many PI tools perform much better thanks to using it instead of StdDev.

Take a look at this screenshot:

ScrollBoxTest.jpg


It is a ScrollBox object showing a generated image (with the TranslucentPlanets routine). I'm making a lot of (internal) changes to PJSR in version 1.5.2. One of them is that ScrollBox now works nicely (unfortunately, that isn't true in versions <= 1.5.0). With ScrollBox, you'll be able to render and navigate an image inside a JavaScript dialog with just a few lines of code. This is a much more convenient way to provide an image preview than creating "satellite" image windows, as you tried to do in your script.

Just a bit of patience is what I ask :) Version 1.5.2 is almost ready; only a few more bug fixes and optimizations, and I'll release it. My intention is that 1.5.2 will last at least for the whole Summer, so I can concentrate on making videos and assisting all of you in your numerous scripts :laugh:

By the way, as I see that you know the author of FITSwork personally (or at least it seems you are in contact with him), feel free to invite him here. I'd be glad if he'd like to implement something with the PJSR or PCL frameworks!
 
Hi Georg,

Congratulations on a great script. It looks like it is getting better with each version. The images speak for themselves. This is going to be very useful....I see the same banding on my Canon 400D images. I'm not a programmer myself, but really glad that people like you can contribute these new processes into PI.

Now for the very basic question to you and/or Juan.....Can I copy this script into PI and run it on one of my images? And if the answer is 'yes'....then the next question is How? I've seen the menu list for SCRIPT in PI...and I've run the various Utilities and Sample Scripts....but that's as far as my knowledge goes. Any help would be greatfully recieved.

Cheers
        Simon
 
Back
Top