Hi all,
In response to many user requests, here is a simple script that applies automatic STFs to all open images (main views only). Tired of clicking the "A" button? Just open your raw images and run the script. Automatic screen stretch made easy!
At the beginning of the script you can find three macro definitions: SHADOWS_CLIP, TARGET_BKG and RGB_LINK, whose meanings are explained in code comments. These define the script's operating parameters. Their values are the default values applied by the standard ScreenTransferFunction tool, so you normally won't need to change them.
Just a word of caution. If your machine is on the slow side and you have a lot of images open, please be patient as the script can't be interrupted. However this shouldn't cause any practical problems unless you open really
many images, say 100 or so.
Enjoy!
EDIT (2013 August 12): Two years ago I discovered that the MTF function is nicely invertible. An MTF function with midtones balance parameter m is given by:
The above equation can be derived from the Bulirsch-Stoer algorithm (see for example R. Bulirsch, J. Stoer et al.,
Introduction to Numerical Analysis, Third Edition, Springer, 2010, sect. 2.2) for evaluation of the diagonal rational function, given the three fixed data points of the MTF function.
The inverse problem to solve in this script is as follows: Given input and output background values b
0 and b
1, respectively, find the required midtones balance m
b for the MTF function such that:
b1 = MTF( b0, mb )
It can be shown that the answer is:
mb = MTF( b1, b0 )
Before discovering this nice property, we were using a binary search algorithm to find the value of m
b. I have updated the script below accordingly (sorry for not having done this before, but I overlooked this post). The original binary search routine is left commented for "historical reasons"
/*
* AutoSTF
*
* A simple script to apply automatic screen transfer functions (STF) to all
* open images.
*
* Copyright (C) 2010-2013, Pleiades Astrophoto S.L.
* Written by Juan Conejero (PTeam)
*/
#feature-id Utilities > AutoSTF
#feature-info A script that applies automatic screen transfer functions (STF) \
to all open images.<br/>\
<br/>\
Written by Juan Conejero (PTeam)<br/>\
Copyright (C) 2010-2013 Pleiades Astrophoto
/*
* Default STF Parameters
*/
// Shadows clipping point measured in sigma units from the main histogram peak.
#define SHADOWS_CLIP -1.25
// Target background in the [0,1] range.
#define TARGET_BKG 0.25
// Whether to apply the same STF to all channels, or treat each channel separately.
#define RGB_LINK true
/*
* Find a midtones balance value that transforms v1 into v0 through a midtones
* transfer function (MTF), within the specified tolerance eps.
*
* ### This routine is no longer used - See the following forum thread for more
* information:
* http://pixinsight.com/forum/index.php?topic=2116
*/
/*
function findMidtonesBalance( v0, v1, eps )
{
if ( v1 <= 0 )
return 0;
if ( v1 >= 1 )
return 1;
v0 = Math.range( v0, 0.0, 1.0 );
if ( eps )
eps = Math.max( 1.0e-15, eps );
else
eps = 5.0e-05;
var m0, m1;
if ( v1 < v0 )
{
m0 = 0;
m1 = 0.5;
}
else
{
m0 = 0.5;
m1 = 1;
}
for ( ;; )
{
var m = (m0 + m1)/2;
var v = Math.mtf( m, v1 );
if ( Math.abs( v - v0 ) < eps )
return m;
if ( v < v0 )
m1 = m;
else
m0 = m;
}
}
*/
/*
* STF Auto Stretch routine
*/
function ApplyAutoSTF( view, shadowsClipping, targetBackground, rgbLinked )
{
var stf = new ScreenTransferFunction;
var n = view.image.isColor ? 3 : 1;
if ( rgbLinked )
{
/*
* Try to find how many channels look as channels of an inverted image.
* We know a channel has been inverted because the main histogram peak is
* located over the right-hand half of the histogram. Seems simplistic
* but this is consistent with astronomical images.
*/
var invertedChannels = 0;
for ( var c = 0; c < n; ++c )
{
view.image.selectedChannel = c;
if ( view.image.median() > 0.5 )
++invertedChannels;
}
view.image.resetSelections();
if ( invertedChannels < n )
{
// Noninverted image
var c0 = 0;
var m = 0;
for ( var c = 0; c < n; ++c )
{
view.image.selectedChannel = c;
var median = view.image.median();
var avgDev = view.image.avgDev();
c0 += median + shadowsClipping*avgDev;
m += median;
}
view.image.resetSelections();
c0 = Math.range( c0/n, 0.0, 1.0 );
m = Math.mtf( targetBackground, m/n - c0 );
stf.STF = [ // c0, c1, m, r0, r1
[c0, 1, m, 0, 1],
[c0, 1, m, 0, 1],
[c0, 1, m, 0, 1],
[0, 1, 0.5, 0, 1] ];
}
else
{
// Inverted image
var c1 = 0;
var m = 0;
for ( var c = 0; c < n; ++c )
{
view.image.selectedChannel = c;
var median = view.image.median();
var avgDev = view.image.avgDev();
m += median;
c1 += median - shadowsClipping*avgDev;
}
view.image.resetSelections();
c1 = Math.range( c1/n, 0.0, 1.0 );
m = 1 - Math.mtf( targetBackground, c1 - m/n );
stf.STF = [ // c0, c1, m, r0, r1
[0, c1, m, 0, 1],
[0, c1, m, 0, 1],
[0, c1, m, 0, 1],
[0, 1, 0.5, 0, 1] ];
}
}
else
{
var A = [ // c0, c1, m, r0, r1
[0, 1, 0.5, 0, 1],
[0, 1, 0.5, 0, 1],
[0, 1, 0.5, 0, 1],
[0, 1, 0.5, 0, 1] ];
for ( var c = 0; c < n; ++c )
{
view.image.selectedChannel = c;
var median = view.image.median();
var avgDev = view.image.avgDev();
if ( median < 0.5 )
{
// Noninverted channel
var c0 = Math.range( median + shadowsClipping*avgDev, 0.0, 1.0 );
var m = Math.mtf( targetBackground, median - c0 );
A[c] = [c0, 1, m, 0, 1];
}
else
{
// Inverted channel
var c1 = Math.range( median - shadowsClipping*avgDev, 0.0, 1.0 );
var m = 1 - Math.mtf( targetBackground, c1 - median() );
A[c] = [0, c1, m, 0, 1];
}
}
stf.STF = A;
view.image.resetSelections();
}
console.writeln( "<end><cbr/><br/><b>", view.fullId, "</b>:" );
for ( var c = 0; c < n; ++c )
{
console.writeln( "channel #", c );
console.writeln( format( "c0 = %.6f", stf.STF[c][0] ) );
console.writeln( format( "m = %.6f", stf.STF[c][2] ) );
console.writeln( format( "c1 = %.6f", stf.STF[c][1] ) );
}
stf.executeOn( view );
console.writeln( "<end><cbr/><br/>" );
}
function main()
{
console.show();
var images = ImageWindow.windows;
for ( var i in images )
{
processEvents();
ApplyAutoSTF( images[i].mainView, SHADOWS_CLIP, TARGET_BKG, RGB_LINK );
processEvents();
}
}
main();