y además no es necesario imponer un número máximo de iteraciones, porque la rutina itera hasta que se satisface un error máximo de convergencia.
Pero este hilo existe precisamente porque es mejor ajustar el histograma varias veces con una máscara distinta cada vez. Si no me equivoco, tu función calcula el valor necesario para alcanzar un valor de mediana deseado
con una sola pasada de MTF. Sin embargo, una de mis premisas básicas de diseño es que el usuario debe elegir el número de pasadas. Y claro, cuantas más pasadas, más cercano a 0.5 tiene que ser el valor de MTF a usar en cada una de ellas.
En mi código, que ya tengo listo, he cogido un poco del pseudocódigo de Carlos y un poco de tu función. Aquí va:
/*
masked-stretch-transform.js v0.2 (formerly auto-star-profile-transform.js)
Iteratively stretch histogram with an appropriate luminance mask each time.
Copyright (C) 2007 David Serrano
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/>.
*/
/*
Special thanks go to Carlos Sonnenstein and Carlos Milovic for
their (in)valuable contributions.
Changelog:
0.2: Some code reorganization.
Now deduces the MTF value from given values: iterations and target median.
0.1.1: Bug fix: masked was not being inverted.
0.1: Initial version.
*/
// TODO: make sure get_median does the right thing (obtain luma, then get its median).
// TODO: devise a way for not generating swapfiles for the histogram loop. It's sooo slooow...
#include <pjsr/ColorSpace.jsh>
#include <pjsr/SampleType.jsh>
#include <pjsr/UndoFlag.jsh>
#include <pjsr/MTF.jsh>
#feature-id AutoStarProfileTransform
#feature-info At the time of stretching the histogram of an image, \
it's better to do it several times changing the mask used every \
time - a long, boring task that is perfect for script automation. \
This script does precisely that.
//#feature-icon masked-stretch-transform.xpm
#define VERSION "0.2"
#define E 1e-5
function get_luma (view) {
view.image.extractLuminance (luma = new Image);
return luma;
}
{
var stats = new ImageStatistics; // closure
function get_median (view) {
var l = get_luma (view);
stats.generate (l);
return stats.median;
}
}
// from orig median "om" to target median "tm" in "it" iters
function calc_mtf (om, tm, it) {
var tmp;
var dark = 0;
// this is 1 instead of 0.5 because we should't assume that the
// user is always going to lighten the image (ie keep the mtf
// value < 0.5). Users are always cleverer and we shouldn't take
// decisions on their behalf. As the saying goes, "If something
// prevents you from doing stupid things, it also prevents you
// from doing clever things".
var light = 1;
// this initial value could depend on the number of iterations.
//
// Since in the majority of cases the user will want to lighten
// the image, it's better to set this value a bit higher, so the
// loop following will end up with a image too light, and then
// the first variable to be adjusted will be "light", not "dark".
// Therefore, in the majority of cases, the second pass of the
// loop will search in [0, 0.35] instead of [0.35, 1].
var mtf = 0.35;
while (1) {
tmp = om;
for (var i = 0; i < it; i++)
tmp = MidtonesTransferFunction (mtf, tmp) * (1-tmp) + tmp*tmp;
if (Math.abs (tmp - tm) < E)
return mtf;
if (tmp > tm) { // where's Perl...
dark = mtf;
} else {
light = mtf;
}
mtf = (dark + light) / 2;
}
}
//these will be specified by the user
var iter = 3;
var target_median = 0.04;
var aw = ImageWindow.activeWindow;
if (aw.isNull)
throw Error ("No active image window.");
var v = ImageWindow.activeWindow.mainView;
var mtf = calc_mtf (get_median (v), target_median, iter);
console.writeln (format ("Using MTF of %6.04f", mtf));
var hist = new HistogramTransform;
hist.H = [
// shadows, midtones, highlights, ext_shadows, ext_hilights
[0, 0.5, 1, 0, 1], // R
[0, 0.5, 1, 0, 1], // G
[0, 0.5, 1, 0, 1], // B
[0, mtf, 1, 0, 1], // RGB
[0, 0.5, 1, 0, 1], // Alpha
];
// window that will hold the luminance for masking
var luma_w = new ImageWindow (1, 1, 1, v.image.bitsPerSample, false, false, "MST_luma");
for (var i = 0; i < iter; i++) {
var luma = get_luma (v);
// transfer image to window and apply the window as mask
with (luma_w.mainView) {
// Inform the core application that we are going to modify this view's image
beginProcess (UndoFlag_NoSwapFile);
// ### Note that after this transfer, luma is no longer a reference to a valid Image object
image.transfer (luma);
endProcess();
}
//luma_w.show();
aw.maskVisible = false;
aw.maskInverted = true;
aw.mask = luma_w;
// stretch
hist.executeOn (v);
// clean up
luma_w.removeMaskReferences(); // automatically removes mask from aw
gc();
}
luma_w.close();
// no need to set luma to null, since .transfer removed the reference to the Image object
luma_w = null;
gc();
- El programa necesita un archivo MTF.jsh en el directorio donde están todos los *.jsh (include/pjsr). Ese archivo es el trozo de código Javascript que Juan puso un par de mensajes más arriba. Felicidades Juan, tu traducción al vuelo funciona a la primera
.
- Los parámetros configurables no están tan visibles como antes, están en las líneas 101 y 102. Ya va haciendo falta un interface porque esto difícilmente va a cambiar.
- Tal como dice uno de los TODO's, el programa crea un archivo de swap en cada pasada de HistogramTransform, lo cual nos echa el rendimiento abajo. He probado, a ciegas, a poner esto de "beginProcess (UndoFlag_NoSwapFile)" y "endProcess" alrededor de la llamada a "hist.executeOn (v)" pero me salió un error tal que la imagen ya estaba siendo procesada o algo así. Esta es la salida de la consola, donde se ve lo del swap:
HistogramTransform: Processing view: Image01
Writing swap file...
Processing channel #0: LUT-based histogram transform: 100%
Processing channel #1: LUT-based histogram transform: 100%
Processing channel #2: LUT-based histogram transform: 100%
0.597 s
MST_luma: Masking from swap file...
Calculating view statistics...
Lo de statistics también molaba quitarlo.
- Y tal como muestra el otro TODO, no sé si estoy haciendo bien el cálculo de la mediana original. Al principio lo hacía a pelo, es decir, "stats.generate (image); stats.median;". Sin embargo me fijé que el valor obtenido me coincidía con el valor que el proceso Statistics me daba para el canal rojo. Y entonces decidí, para calcular la mediana, hacer primero una luminancia y luego sacar su mediana. Esto se ve en la función get_median(), línea 55.
El resultado es aparentemente satisfactorio, pero nuevamente me encuentro con el problema de comprobar si la mediana de la imagen resultante es la que yo quería... si hago dos procesos PixelMath sobre la imagen ya estirada, uno con "Lum($target)" y otro con "Med($target)", obtengo una imagen gris (lógicamente) en la que el valor de todos los píxels (0.0428) tiene mucho que ver con la mediana deseada (0.0400) (error del 7%) pero no sé si esto es coincidencia o no!
. Si pido otros parámetros (4 iteraciones y mediana objetivo 0.09) y realizo estos dos PixelMath, me queda una imagen gris cuyos pixels valen 0.126; el error ya alcanza el 33%...
Bueno, sea como sea, a ver qué tal os va esta versión. No olvidéis crear el MTF.jsh!