Saturday, September 26, 2020

Equalizing the ML50100 images

The ML50100 is basically two chips that are just next to each other. Each side has its own readout - and with that its own characteristics.

Here is a 1x1 bias frame:

The different levels are clearly visible. But even more, if zooming into the Master Bias:

There is even different patterns on each side. Again, it's basically two separate chips...

Now, one question was: Can we calibrate these differences out? By taking very good dark and flat frames, would the resulting light frames be clean (i.e. don't have different levels on the left and right hand side?)

My answer is: no! I tried everything (using darks with vs. without bias, correcting flats with darks with / without bias...) I always ended up with different levels. Now, taking very high quality frames can probably reduce that. So, I went back to Dark Skies New Mexico to cover the scope and take dark and bias frames making sure that absolutely no light comes in:
(I know, it looks horrible - but it did the trick).

It's easy to imagine a PixelMath expression that equalizes both halfs: Get the mean of a background part on the left and right side and multiple one side by the quotient of the two. You can get the mean by creating a preview of an area without stars using the Statistics process.

The problem is that you need to do this for each frame!!! If you do this on the stacked image, you end up with a seam in the middle when the various frames were first aligned and then stacked. And doing this for each frame individually is a royal PITA.

So, I wrote a simple Pixinsight script that does this across many individual images. First, you load one image (after calibration and applying the DefectMap) and put two previews on it - near the center and both covering mostly background:


The script uses these two previews, applies them to all other images, measures the means on both sides and corrects the right-hand side:

Good thing is: this made me learn the Javascript-based Pixinsight scripting (bad thing: there is no real reference or tutorial!!!)

Two things to remember:
  1. I have to choose previews that have some empty space around them. Because we are dithering our images, they will all be offset a little into any direction. I.e. the preview will shift around.
  2. It's better to equalize images from each side of the meridian separately as they will be rotated by 180 degrees.
With this, I can easily equalize all frames before stacking!!!!

Here is the code for the script (I know it's not the cleanest ...):

#include <pjsr/TextAlign.jsh>

#include <pjsr/Sizer.jsh>

#include <pjsr/StdIcon.jsh>

#include <pjsr/StdButton.jsh>


function equalize_images(LeftPreview, RightPreview, FileList) {

Console.writeln("Equalizing " + (FileList.length+1) + " images against Reference Image " +

LeftPreview.fullId.substr(0, LeftPreview.fullId.lastIndexOf("->")));

var ReferenceImageWindow = LeftPreview.window;

Console.writeln("Dimension of Reference Image: Height=" + ReferenceImageWindow.mainView.image.height +

" Width=" + ReferenceImageWindow.mainView.image.width);

var success = true;

for ( var i = 0; i < FileList.length; i++) {

var CurrentImage = ImageWindow.open(FileList[i]);

if (CurrentImage.length < 1) {

(new MessageBox("Unable to open file: " + FileList[i], "Error",

StdIcon_Error, StdButton_Ok

)).execute();

throw new Error("Unable to open file: " + FileList[i]);

success = false;

} else if (CurrentImage.length > 1) {

(new MessageBox("Multi-Image files are not supported (" + FileList[i] + ")", "Error",

StdIcon_Error, StdButton_Ok

)).execute();

throw new Error("Multi-Image files are not supported (" + FileList[i] + ")");

success = false;

} else if (CurrentImage[0].mainView.image.height != ReferenceImageWindow.mainView.image.height ||

CurrentImage[0].mainView.image.width != ReferenceImageWindow.mainView.image.width) {

(new MessageBox("Image " + FileList[i] +

" does not have same dimension as reference image.", "Error",

StdIcon_Error, StdButton_Ok

)).execute();

throw new Error("Image " + FileList[i] +

" does not have same dimension as reference image.");

success = false;

} else {

Console.writeln("Processing " + (i+1) + " of " + FileList.length +

" image: " + FileList[i]);

var CurrentImageLeftPreview =

CurrentImage[0].createPreview(ReferenceImageWindow.previewRect(LeftPreview));

var CurrentImageLeftMedian = CurrentImageLeftPreview.image.median();//computeProperty("Median").median();

var CurrentImageRightPreview =

CurrentImage[0].createPreview(ReferenceImageWindow.previewRect(RightPreview));

var CurrentImageRightMedian = CurrentImageRightPreview.image.median();//computeProperty("Median").median();

var Correction = CurrentImageRightMedian / CurrentImageLeftMedian;

Console.writeln(" Median of left Preview= " + CurrentImageLeftMedian);

Console.writeln(" Median of right Preview=" + CurrentImageRightMedian);

Console.writeln(" Correction= " + Correction);

var PixelMathProcess = new PixelMath();

PixelMathProcess.expression = "iif(x()>=w()/2,$T[0]/" + Correction + ",$T[0])";

PixelMathProcess.executeOn(CurrentImage[0].mainView);

var DotPosition = CurrentImage[0].filePath.lastIndexOf(".");

var PathWithoutExtension = CurrentImage[0].filePath.substring(0, DotPosition);

var Extension = CurrentImage[0].filePath.substring(DotPosition);

var NewFilePath = PathWithoutExtension + "_equalized" + Extension;

Console.writeln("Saving equalized image as " + NewFilePath);

CurrentImage[0].saveAs(NewFilePath, false, false, false, false);

CurrentImage[0].close();

Console.writeln();

}

}

if (success) {

(new MessageBox("Done!", "Done",

StdIcon_Information, StdButton_Ok

)).execute();

}

}


function apply_previews(LeftPreview, RightPreview, FileList) {

var image_list = ImageWindow.windows;

var active_window = ImageWindow.activeWindow;


Console.writeln("Found " + image_list.length + " images.");


for ( var i = 0; i < image_list.length; i++ ) {

console.write("" + i + " Image " + image_list[i].filePath);

var file_path = image_list[i].filePath;


if ((image_list[i].height != active_window.height) ||

(image_list[i].width != active_window.width)) {

console.writeln(" - has different resolution than active window - skipping!");

} else {

//if (file_path == active_window.filePath) {

// console.writeln(" - is active window - skipping!");

//} else {

console.writeln(" - creating previews");

var new_left_preview = image_list[i].createPreview(LeftPreview);

var left_median = new_left_preview.computeProperty("Median").median();

var new_right_preview = image_list[i].createPreview(RightPreview);

var right_median = new_right_preview.computeProperty("Median").median();

var correction = right_median / left_median;

Console.writeln("Left=" + left_median + " Right=" + right_median +

" correction=" + correction);

var pixelMath = new PixelMath();

pixelMath.createNewImage = true;

var s = "iif(x()>=w()/2,$T[0]/" + correction + ",$T[0])";

pixelMath.expression = s;

pixelMath.executeOn(image_list[i].mainView);

var dot_pos = file_path.lastIndexOf(".");

var all_but_extension = file_path.substring(0, dot_pos);

var extension = file_path.substring(dot_pos);

var new_file_path = all_but_extension + "_equalized" + extension;

var new_image = ImageWindow.activeWindow;

Console.writeln("Saving as " + new_file_path);

new_image.saveAs(new_file_path, false, false, false, false);

new_image.close();

//}

}

}

}


function equalize_dialog() {

this.__base__ = Dialog;

this.__base__();


this.LeftPreviewLabel = new Label();

this.LeftPreviewLabel.text = "Left Preview";

this.LeftPreviewLabel.minWidth = 30;

this.LeftPreviewViewList = new ViewList();

this.LeftPreviewViewList.getPreviews();

this.LeftPreviewSizer = new HorizontalSizer();

this.LeftPreviewSizer.add(this.LeftPreviewLabel);

this.LeftPreviewSizer.add(this.LeftPreviewViewList);


this.RightPreviewLabel = new Label();

this.RightPreviewLabel.text = "Right Preview";

this.RightPreviewLabel.minWidth = 30;

this.RightPreviewViewList = new ViewList();

this.RightPreviewViewList.getPreviews();

this.RightPreviewSizer = new HorizontalSizer();

this.RightPreviewSizer.add(this.RightPreviewLabel);

this.RightPreviewSizer.add(this.RightPreviewViewList);


this.inputFiles = new Array;


this.files_Label = new Label(this);

this.files_Label.text = "Images to equalize";

this.files_Label.minWidth = 100;


this.files_TreeBox = new TreeBox( this );

this.files_TreeBox.multipleSelection = true;

this.files_TreeBox.rootDecoration = false;

this.files_TreeBox.alternateRowColor = true;

this.files_TreeBox.setScaledMinSize( 500, 200 );

this.files_TreeBox.numberOfColumns = 1;

this.files_TreeBox.headerVisible = false;


for ( let i = 0; i < this.inputFiles.length; ++i )

{

let node = new TreeBoxNode( this.files_TreeBox );

node.setText( 0, this.inputFiles[i] );

}


this.filesAdd_Button = new PushButton( this );

this.filesAdd_Button.text = "Add";

this.filesAdd_Button.icon = this.scaledResource( ":/icons/add.png" );

this.filesAdd_Button.toolTip = "<p>Add image files to the input images list.</p>";

this.filesAdd_Button.onClick = function()

{

let ofd = new OpenFileDialog;

ofd.multipleSelections = true;

ofd.caption = "Select Images";

ofd.loadImageFilters();


if ( ofd.execute() )

{

this.dialog.files_TreeBox.canUpdate = false;

this.dialog.inputFiles.length = 0;

for ( let i = 0; i < ofd.fileNames.length; ++i )

{

let node = new TreeBoxNode( this.dialog.files_TreeBox );

node.setText( 0, ofd.fileNames[i] );

this.dialog.inputFiles.push( ofd.fileNames[i] );

}

this.dialog.files_TreeBox.canUpdate = true;

}

};


this.filesClear_Button = new PushButton( this );

this.filesClear_Button.text = "Clear";

this.filesClear_Button.icon = this.scaledResource( ":/icons/clear.png" );

this.filesClear_Button.toolTip = "<p>Clear the list of input images.</p>";

this.filesClear_Button.onClick = function()

{

this.dialog.files_TreeBox.clear();

this.dialog.inputFiles.length = 0;

};


this.filesButtons_Sizer = new HorizontalSizer;

this.filesButtons_Sizer.spacing = 4;

this.filesButtons_Sizer.add( this.filesAdd_Button );

this.filesButtons_Sizer.addStretch();

this.filesButtons_Sizer.add( this.filesClear_Button );

this.filesButtons_Sizer.addStretch();


var ReadPreviewsButton = new PushButton();

ReadPreviewsButton.text = "Equalize";

ReadPreviewsButton.onClick = function() {

var LeftPreview = this.dialog.LeftPreviewViewList.currentView;

var RightPreview = this.dialog.RightPreviewViewList.currentView;

if (LeftPreview == "") {

(new MessageBox("Must set Left Preview", "Error",

StdIcon_Error, StdButton_Ok

)).execute();

} else if (RightPreview == "") {

(new MessageBox("Must set Right Preview", "Error",

StdIcon_Error, StdButton_Ok

)).execute();

} else if (LeftPreview.fullId.substr(0, LeftPreview.fullId.lastIndexOf("->")) !=

RightPreview.fullId.substr(0, RightPreview.fullId.lastIndexOf("->"))) {

(new MessageBox("Select both Previews from same Image.", "Error",

StdIcon_Error, StdButton_Ok

)).execute();

} else if (this.dialog.inputFiles.length == 0) {

(new MessageBox("Must select at least one image to equalize.", "Error",

StdIcon_Error, StdButton_Ok

)).execute();

} else {

var ReferenceImageWindow = LeftPreview.window;//.mainView.image;

equalize_images(LeftPreview,

RightPreview,

this.dialog.inputFiles);

}

}


this.sizer = new VerticalSizer();

this.sizer.add(this.LeftPreviewSizer);

this.sizer.add(this.RightPreviewSizer);

this.sizer.add(this.files_Label);

this.sizer.add(this.files_TreeBox);

this.sizer.add(this.filesButtons_Sizer);

this.sizer.add(ReadPreviewsButton);


this.adjustToContents();

}


equalize_dialog.prototype = new Dialog();


function main() {

var dialog = new equalize_dialog();

dialog.execute();

}


main();


No comments:

Post a Comment