Hi Mark,
I have completed an interesting test with your original image. Besides demonstrating that my implementation of drizzle is correct (and for that matter, of Bayer drizzle), the procedures and results of this test are very useful to understand how the drizzle algorithm works in practice, so I'm going to detail them here.
The test begins with a little script, whose source code can be seen here:
#define ORIGINAL_IMAGE_ID "original"
#define NUMBER_OF_IMAGES 100
#define MAX_DELTA_PX 25
#define OUTPUT_BASE_DIR "/home/juan/tmp/bayer-drizzle-test"
#define BAYER_PATTERN "RGGB"
#include <pjsr/Interpolation.jsh>
#include <pjsr/UndoFlag.jsh>
function translateImageByIntegerPixels( image, dx, dy )
{
image.shiftBy( Math.round( dx ), Math.round( dy ) );
}
function translateImageByFractionalPixels( image, dx, dy )
{
image.translate( -dx, -dy );
}
function convertRGBImageToBayerRGB( view, bayerPattern )
{
let Rx, Ry, Gx0, Gx1, Bx, By;
switch ( bayerPattern )
{
// R G
// G B
default:
case "RGGB":
Rx = 0; Ry = 0;
Gx0 = 1; Gx1 = 0;
Bx = 1; By = 1;
break;
// B G
// G R
case "BGGR":
Rx = 1; Ry = 1;
Gx0 = 1; Gx1 = 0;
Bx = 0; By = 0;
break;
// G R
// B G
case "GRBG":
Rx = 1; Ry = 0;
Gx0 = 0; Gx1 = 1;
Bx = 0; By = 1;
break;
// G B
// R G
case "GBRG":
Rx = 0; Ry = 1;
Gx0 = 0; Gx1 = 1;
Bx = 1; By = 0;
break;
}
let P = new PixelMath;
P.expression0 = "iif( x()%2 == " + Rx.toString() + " && y()%2 == " + Ry.toString() + ", $T, 0 )";
P.expression1 = "iif( iif( y()%2 == 0, x()%2 == " + Gx0.toString() + ", x()%2 == " + Gx1.toString() + " ), $T, 0 )";
P.expression2 = "iif( x()%2 == " + Bx.toString() + " && y()%2 == " + By.toString() + ", $T, 0 )";
P.useSingleExpression = false;
P.createNewImage = false;
P.showNewImage = false;
P.newImageWidth = 0;
P.newImageHeight = 0;
P.newImageColorSpace = PixelMath.prototype.RGB;
P.newImageSampleFormat = PixelMath.prototype.f32;
P.executeOn( view, false/*swapFile*/ );
}
function main()
{
let originalWindow = ImageWindow.windowById( ORIGINAL_IMAGE_ID );
for ( let i = 0; i < NUMBER_OF_IMAGES; ++i )
{
let window = new ImageWindow( 1, 1, 1, 16/*bitsPerSample*/, false/*floatSample*/ );
let view = window.mainView;
let dx = 2*MAX_DELTA_PX*Math.random();
if ( dx > MAX_DELTA_PX )
dx = MAX_DELTA_PX - dx;
let dy = 2*MAX_DELTA_PX*Math.random();
if ( dy > MAX_DELTA_PX )
dy = MAX_DELTA_PX - dy;
view.beginProcess( UndoFlag_NoSwapFile );
view.image.assign( originalWindow.mainView.image );
translateImageByIntegerPixels( view.image, dx, dy );
view.endProcess();
convertRGBImageToBayerRGB( view, BAYER_PATTERN );
window.saveAs( OUTPUT_BASE_DIR + format( "/integer/bayer/test%04d.xisf", i+1 ),
false/*queryOptions*/, false/*allowMessages*/, true/*strict*/, false/*verifyOverwrite*/ );
view.beginProcess( UndoFlag_NoSwapFile );
view.image.assign( originalWindow.mainView.image );
view.image.interpolation = Interpolation_Bilinear;
translateImageByFractionalPixels( view.image, dx, dy );
view.endProcess();
convertRGBImageToBayerRGB( view, BAYER_PATTERN );
window.saveAs( OUTPUT_BASE_DIR + format( "/fractional/bayer/test%04d.xisf", i+1 ),
false/*queryOptions*/, false/*allowMessages*/, true/*strict*/, false/*verifyOverwrite*/ );
window.forceClose();
console.noteln( format( "* %4d : dx=%+6.2lf dy=%+6.2lf", i+1, dx, dy ) );
}
}
main();
This script generates two synthetic data sets from your original image. Both sets consist of copies of your original RGB image, translated randomly in both plane directions, and sampled as Bayer RGB data. The images in one of the sets are translated by integer distances in pixels, while the images in the other set are translated by fractional pixels. To run this script, first you must create two directories on your filesystem, namely:
OUTPUT_BASE_DIR/integer/bayer
OUTPUT_BASE_DIR/fractional/bayer
where OUTPUT_BASE_DIR must be defined appropriately in the script (see line 5). Run PixInsight, load your original image, and set its identifier equal to "original" (or alternatively, change the value of ORIGINAL_IMAGE_ID in line 2 of the script). Load the script in Script Editor and execute it. By default the script generates 100 images in each set. You can redefine this number in line 3, by changing the value of NUMBER_OF_IMAGES. Finally, you can also change the macros MAX_DELTA_PX, which is the maximum displacement applied in pixels (in absolute value), and BAYER_PATTERN to one of "RGGB", "BGGR", "GRBG", and "GBRG".
As I have said the script generates Bayer RGB images. With a simple change we could generate Bayer CFA monochrome images. Besides some disk space savings, the result would be exactly the same. I prefer Bayer RGB images because they are much easier to inspect visually.
After running the script, I have also generated fake master bias, dark and flat frames using the NewImage tool (bias and dark as pure black images of 512x512 pixels, and the master flat with a constant value of 0.5 and also 512x512 pixels). Then I have executed the BatchPreprocessing script on each data set. In BPP, I have selected the appropriate light frames (either on integer/bayer of fractional/bayer), the fake master calibration frames, and have enabled the
CFA images and
Bayer drizzle options. I have disabled image integration because I have performed this task manually after BPP. Here is a screenshot of BPP's dialog window just before running it:
After running BPP, we have a set of calibrated/debayered/registered images, a set of Bayer drizzle files, and a set of Bayer RGB frames, which are redundant in this case since we already have them after running our synthetic data generation script.
With these images, I have generated four test results:
1. ImageIntegration: Standard integrated image using synthetic frames with integer translations, debayered, and registered:
2. DrizzleIntegration: Bayer drizzle integrated image using synthetic frames with integer translations and drizzle files.
3. ImageIntegration: Standard integrated image using synthetic frames with fractional translations, debayered, and registered.
4. DrizzleIntegration: Bayer drizzle integrated image using synthetic frames with fractional translations and drizzle files.
Here is your original image for comparison:
In all cases the images are shown enlarged 6:1. Only your original image is shown with an active STF, since enabling it for the rest would make evaluation of results impossible. The result images are available for download here:
http://forum-images.pixinsight.com/20161019/drizzle/original.xisfhttp://forum-images.pixinsight.com/20161019/drizzle/integration-integer.xisfhttp://forum-images.pixinsight.com/20161019/drizzle/drizzle-integer.xisfhttp://forum-images.pixinsight.com/20161019/drizzle/integration-fractional.xisfhttp://forum-images.pixinsight.com/20161019/drizzle/drizzle-fractional.xisfHere are screenshots of the ImageIntegration and DrizzleIntegration tools, as used during the test:
As you can see, for simplicity I have disabled image weighting and rejection in all test cases. This is a screenshot of the workspace with the original and test result images for a better comparison:
For DrizzleIntegration I have used a 1:1 drizzle scale and no drop shrink. It would be interesting to repeat the same test with different scale and drop shrink parameters.
How does Bayer drizzle perform as a function of the number of input images? Here are some additional results with only the first 10, 20 and 50 synthetic frames, respectively:
Evaluation of ResultsIn (1) we see how a standard image integration has produced a reasonably good result for images translated by integer pixels. However, two problems become evident: color fringing (the white features are now greenish with strong color variations), and aliasing (the red and blue features at the top right corner of your artificial test pattern have not been resolved at the one-pixel scale).
In (3), which is the standard integration of images translated by fractional displacements, the result is so bad that it is, in fact, an excellent demonstration of why Bayer drizzle should always be used, when possible, instead of the traditional deBayer/registration/integration procedure. We can see strong aliasing and color fringing artifacts that simply destroy the original test pattern.
In (2), the result of Bayer drizzle for images translated by integer pixel displacements, there are no color fringing artifacts, as expected. However, and also as expected,
the drizzle algorithm cannot recover structures at the one-pixel scale when the source images are not dithered by subpixel displacements. This is true because of
the way drizzle works, and
is true irrespective of the number of source frames.
Finally, the resulting image in (4), which is the Bayer drizzled image for frames translated by fractional pixel displacements, demonstrates that my implementation of the drizzle algorithm works well. It has worked remarkably well, in fact, to reconstruct your artificial test pattern with no color fringing artifacts and all structures quite correctly represented at the one-pixel scale.
This test is particularly good to show three important facts about drizzle:
- Drizzle requires
correctly dithered images. Dithering must be performed by non-integer pixel displacements in both directions, ideally following a random pattern.
- Drizzle wants
many images. The typical "the more, the better" recommendation is particularly true for drizzle.
- When the data set allows implementing it, the Bayer drizzle technique is
always superior to a standard deBayer/register/integrate procedure for mosaiced data. The benefits of drizzle in this case are minimal color artifacts, absence of aliasing artifacts, and better reconstruction of small-scale image structures.
I want to point out an important fact about this test, which should not pass unnoticed for a complete evaluation of the results. In the data generation script I have used bilinear interpolation (see script lines 98 and 99) to translate the images by fractional pixel amounts. Actually, this does not reproduce the quality of a true data set acquired with real dithering, since pixel interpolation necessarily acts as a low-pass filtering process, degrading the data at small scales. I have used bilinear interpolation because it produces no ringing artifacts, but it is also one of the worst choices in terms of aliasing. With real high-quality data, the result in (4) should be significantly better without this limitation. We could try to overcome this problem in the test by not interpolating the data, which would require an inverse drizzling of the original image to generate the synthetic frames, but this can be somewhat complex to implement.