One of my cameras appears to have a number of defective pixels which are
clearly visible on dark backgrounds. Initially I thought these "hot"
pixels are saturated (close) to rgbMax but apparently pixels can be
"half-broken". The patch below implements a "shave" algorithm to eliminate
these hot pixels. It has been designed to minimize visible damage on
falsely detected hot pixels. The number of hot pixels is reported and they
can be marked in the preview window in case one wonders where they are.
I know this patch may not (yet) be going in because of an upcoming
release but except for the manual page it is finished and may be useful.
diff --git a/ufraw.h b/ufraw.h
index 2876626..ea88336 100644
--- a/ufraw.h
+++ b/ufraw.h
@@ -166,6 +166,7 @@ typedef struct {
double temperature, green;
double chanMul[4];
double threshold;
+ double hotpixel;
#ifdef UFRAW_CONTRAST
double contrast;
#endif
@@ -283,6 +284,8 @@ typedef struct ufraw_struct {
int postproc_ops; /* postprocessing operations (LF_MODIFY_XXX) */
lfModifier *modifier;
#endif /* HAVE_LENSFUN */
+ int hotpixels;
+ gboolean mark_hotpixels;
} ufraw_data;
extern const conf_data conf_default;
diff --git a/ufraw_conf.c b/ufraw_conf.c
index dc05739..522e3ff 100644
--- a/ufraw_conf.c
+++ b/ufraw_conf.c
@@ -36,6 +36,7 @@ const conf_data conf_default = {
6500, 1.0, /* temperature, green */
{ -1.0, -1.0, -1.0, -1.0 }, /* chanMul[] */
0.0, /* wavelet denoising threshold */
+ 0.0, /* hotpixel sensitivity */
#ifdef UFRAW_CONTRAST
1.0, /* global contrast */
#endif
@@ -624,6 +625,8 @@ static void conf_parse_text(GMarkupParseContext *context,
const gchar *text,
}
if (!strcmp("WaveletDenoisingThreshold", element))
sscanf(temp, "%lf", &c->threshold);
+ if (!strcmp("HotpixelSensitivity", element))
+ sscanf(temp, "%lf", &c->hotpixel);
#ifdef UFRAW_CONTRAST
if (!strcmp("Contrast", element))
sscanf(temp, "%lf", &c->contrast);
@@ -938,6 +941,10 @@ int conf_save(conf_data *c, char *IDFilename, char
**confBuffer)
buf = uf_markup_buf(buf,
"<WaveletDenoisingThreshold>%d</WaveletDenoisingThreshold>\n",
(int)floor(c->threshold));
+ if (c->hotpixel!=conf_default.hotpixel)
+ buf = uf_markup_buf(buf,
+ "<HotpixelSensitivity>%f</HotpixelSensitivity>\n",
+ c->hotpixel);
#ifdef UFRAW_CONTRAST
if (c->contrast!=conf_default.contrast)
buf = uf_markup_buf(buf,
@@ -1208,6 +1215,7 @@ void conf_copy_image(conf_data *dst, const conf_data *src)
g_strlcpy(dst->make, src->make, max_name);
g_strlcpy(dst->model, src->model, max_name);
dst->threshold = src->threshold;
+ dst->hotpixel = src->hotpixel;
dst->exposure = src->exposure;
#ifdef UFRAW_CONTRAST
dst->contrast = src->contrast;
@@ -1385,6 +1393,7 @@ int conf_set_cmd(conf_data *conf, const conf_data *cmd)
conf->autoExposure = cmd->autoExposure;
}
if (cmd->threshold!=NULLF) conf->threshold = cmd->threshold;
+ if (cmd->hotpixel!=NULLF) conf->hotpixel = cmd->hotpixel;
#ifdef UFRAW_CONTRAST
if (cmd->contrast!=NULLF) conf->contrast = cmd->contrast;
#endif
@@ -1515,6 +1524,8 @@ N_("--contrast=CONT Contrast adjustment (default
1.0).\n"),
N_("--saturation=SAT Saturation adjustment (default 1.0, 0 for B&W
output).\n"),
N_("--wavelet-denoising-threshold=THRESHOLD\n"
" Wavelet denoising threshold (default 0.0).\n"),
+N_("--hotpixel-sensitivity=VALUE\n"
+" Sensitivity for detecting and shaving hot pixels
(default 0.0).\n"),
N_("--exposure=auto|EXPOSURE\n"
" Auto exposure or exposure correction in EV (default
0).\n"),
N_("--black-point=auto|BLACK\n"
@@ -1632,6 +1643,7 @@ int ufraw_process_args(int *argc, char ***argv, conf_data
*cmd, conf_data *rc)
{ "contrast", 1, 0, 'y'},
#endif
{ "wavelet-denoising-threshold", 1, 0, 'n'},
+ { "hotpixel-sensitivity", 1, 0, 'H'},
{ "exposure", 1, 0, 'e'},
{ "black-point", 1, 0, 'k'},
{ "interpolation", 1, 0, 'i'},
@@ -1676,6 +1688,7 @@ int ufraw_process_args(int *argc, char ***argv, conf_data
*cmd, conf_data *rc)
&cmd->contrast,
#endif
&cmd->threshold,
+ &cmd->hotpixel,
&cmd->exposure, &cmd->black, &interpolationName, &grayscaleName,
&cmd->shrink, &cmd->size, &cmd->compression,
&outTypeName, &cmd->profile[1][0].BitDepth, &rotateName,
@@ -1699,6 +1712,7 @@ int ufraw_process_args(int *argc, char ***argv, conf_data
*cmd, conf_data *rc)
cmd->saturation=NULLF;
cmd->black=NULLF;
cmd->threshold=NULLF;
+ cmd->hotpixel=NULLF;
cmd->exposure=NULLF;
cmd->temperature=NULLF;
cmd->green=NULLF;
@@ -1734,6 +1748,7 @@ int ufraw_process_args(int *argc, char ***argv, conf_data
*cmd, conf_data *rc)
case 's':
case 'y':
case 'n':
+ case 'H':
if (sscanf(optarg, "%lf", (double *)optPointer[index])==0) {
ufraw_message(UFRAW_ERROR,
_("'%s' is not a valid value for the --%s option."),
diff --git a/ufraw_preview.c b/ufraw_preview.c
index a15bc18..1ae4bca 100644
--- a/ufraw_preview.c
+++ b/ufraw_preview.c
@@ -1418,6 +1418,7 @@ static void update_scales(preview_data *data)
gtk_adjustment_set_value(data->GreenAdjustment, CFG->green);
gtk_adjustment_set_value(data->ExposureAdjustment, CFG->exposure);
gtk_adjustment_set_value(data->ThresholdAdjustment, CFG->threshold);
+ gtk_adjustment_set_value(data->HotpixelAdjustment, CFG->hotpixel);
#ifdef UFRAW_CONTRAST
gtk_adjustment_set_value(data->ContrastAdjustment, CFG->contrast);
#endif
@@ -1456,6 +1457,8 @@ static void update_scales(preview_data *data)
fabs( conf_default.exposure - CFG->exposure) > 0.001);
gtk_widget_set_sensitive(data->ResetThresholdButton,
fabs( conf_default.threshold - CFG->threshold) > 1);
+ gtk_widget_set_sensitive(data->ResetHotpixelButton,
+ fabs( conf_default.hotpixel - CFG->hotpixel) > 0);
#ifdef UFRAW_CONTRAST
gtk_widget_set_sensitive(data->ResetContrastButton,
fabs( conf_default.contrast - CFG->contrast) > 0.001);
@@ -1864,6 +1867,7 @@ static gboolean preview_scroll_event(GtkWidget *widget,
GdkEventScroll *event)
static void create_base_image(preview_data *data)
{
+ gchar buf[20];
int shrinkSave = CFG->shrink;
int sizeSave = CFG->size;
if (CFG->Scale==0) {
@@ -1884,6 +1888,8 @@ static void create_base_image(preview_data *data)
data->UF->conf->rotationAngle);
CFG->shrink = shrinkSave;
CFG->size = sizeSave;
+ g_snprintf(buf, sizeof (buf), "%d", data->UF->hotpixels);
+ gtk_label_set_text(data->HotpixelCount, buf);
}
static void update_shrink_ranges(preview_data *data)
@@ -2601,6 +2607,10 @@ static void button_update(GtkWidget *button, gpointer
user_data)
CFG->threshold = conf_default.threshold;
preview_invalidate_layer(data, ufraw_denoise_phase);
}
+ if (button==data->ResetHotpixelButton) {
+ CFG->hotpixel = conf_default.hotpixel;
+ preview_invalidate_layer(data, ufraw_first_phase);
+ }
#ifdef UFRAW_CONTRAST
if (button==data->ResetContrastButton) {
CFG->contrast = conf_default.contrast;
@@ -2756,6 +2766,11 @@ static void toggle_button_update(GtkToggleButton
*button, gboolean *valuep)
if (!Developer->doWB) // !doWB means do interpolate
preview_invalidate_layer(data, ufraw_first_phase);
render_preview(data);
+ } else if ( valuep==&data->UF->mark_hotpixels ) {
+ if (data->UF->hotpixels) {
+ preview_invalidate_layer(data, ufraw_first_phase);
+ render_preview(data);
+ }
}
}
}
@@ -2835,6 +2850,8 @@ static void adjustment_update(GtkAdjustment *adj, double
*valuep)
preview_invalidate_layer(data, ufraw_first_phase);
} else if (valuep==&CFG->threshold) {
preview_invalidate_layer(data, ufraw_denoise_phase);
+ } else if (valuep==&CFG->hotpixel) {
+ preview_invalidate_layer(data, ufraw_first_phase);
} else {
if (CFG->autoExposure==enabled_state) CFG->autoExposure = apply_state;
if (CFG->autoBlack==enabled_state) CFG->autoBlack = apply_state;
@@ -3871,6 +3888,49 @@ static void rawhistogram_fill_interface(preview_data
*data,
gtk_widget_show_all(menu);
}
+static void hotpixel_fill_interface(preview_data *data, GtkWidget *page)
+{
+ GtkWidget *button;
+ GtkWidget *label;
+ GtkBox *box;
+ GtkWidget *frame;
+
+ frame = gtk_frame_new(NULL);
+ gtk_box_pack_start(GTK_BOX(page), frame, FALSE, FALSE, 0);
+ box = GTK_BOX(gtk_hbox_new(FALSE, 0));
+ gtk_container_add(GTK_CONTAINER(frame), GTK_WIDGET(box));
+ label = gtk_label_new(_("Hot pixels: "));
+ gtk_box_pack_start(box, label, FALSE, FALSE, 0);
+
+ data->HotpixelCount = GTK_LABEL(gtk_label_new(NULL));
+ gtk_box_pack_start(box, GTK_WIDGET(data->HotpixelCount), FALSE, FALSE, 0);
+ button = gtk_check_button_new_with_label("mark");
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button),
+ data->UF->mark_hotpixels);
+ g_signal_connect(G_OBJECT(button), "toggled",
+ G_CALLBACK(toggle_button_update), &data->UF->mark_hotpixels);
+ gtk_box_pack_start(box, button, FALSE, FALSE, 3);
+ gtk_box_pack_start(box, gtk_label_new(NULL), TRUE, TRUE, 0);
+
+ data->HotpixelAdjustment = GTK_ADJUSTMENT(gtk_adjustment_new(
+ CFG->hotpixel, 0.0, 999.99, 0.1, 0.5, 0));
+ g_object_set_data(G_OBJECT(data->HotpixelAdjustment),
+ "Adjustment-Accuracy", (gpointer)3);
+ button = gtk_spin_button_new(data->HotpixelAdjustment, 0.001, 3);
+ g_object_set_data(G_OBJECT(data->HotpixelAdjustment),
+ "Parent-Widget", button);
+ g_signal_connect(G_OBJECT(data->HotpixelAdjustment), "value-changed",
+ G_CALLBACK(adjustment_update), &CFG->hotpixel);
+ uf_widget_set_tooltip(button, _("Hot pixel sensitivity"));
+ gtk_box_pack_start(box, button, FALSE, FALSE, 0);
+
+ button = reset_button(
+ _("Reset hot pixel sensitivity"), G_CALLBACK(button_update), NULL);
+ gtk_box_pack_end(box, button, FALSE, FALSE, 0);
+ gtk_widget_set_sensitive(button, FALSE);
+ data->ResetHotpixelButton = button;
+}
+
static void livehistogram_fill_interface(preview_data *data,
GtkTable *table)
{
@@ -4203,6 +4263,9 @@ static void whitebalance_fill_interface(preview_data
*data,
&data->ResetThresholdButton,
_("Reset denoise threshold to default"), G_CALLBACK(button_update));
+ /* Hot pixel shaving */
+ hotpixel_fill_interface(data, page);
+
// Dark frame controls:
box = GTK_BOX(gtk_hbox_new(FALSE, 0));
frame = gtk_frame_new(NULL);
diff --git a/ufraw_ufraw.c b/ufraw_ufraw.c
index 6852621..e7583cf 100644
--- a/ufraw_ufraw.c
+++ b/ufraw_ufraw.c
@@ -734,6 +734,7 @@ int ufraw_convert_image_init(ufraw_data *uf)
int ufraw_convert_image(ufraw_data *uf)
{
+ uf->mark_hotpixels = FALSE;
ufraw_developer_prepare(uf, file_developer);
ufraw_convert_image_init(uf);
ufraw_convert_image_first_phase(uf, TRUE);
@@ -925,12 +926,76 @@ no_distortion:
#endif /* HAVE_LENSFUN */
+/*
+ * A pixel with a significantly larger value than all of its four direct
+ * neighbours is considered "hot". It will be replaced by the maximum value
+ * of its neighbours. For simplicity border pixels are not considered.
+ *
+ * Reasonable values for uf->conf->hotpixel are in the range 0.5-10.
+ */
+static void ufraw_shave_hotpixels(ufraw_data *uf, dcraw_image_type *img,
+ int width, int height, int colors, unsigned rgbMax)
+{
+ int w, h, c, i;
+ unsigned delta, t, v, hi;
+ dcraw_image_type *p;
+
+ uf->hotpixels = 0;
+ if (uf->conf->hotpixel <= 0.0)
+ return;
+ delta = rgbMax / (uf->conf->hotpixel + 1.0);
+ for (h = 1; h < height - 1; ++h) {
+ p = img + 1 + h * width;
+ for (w = 1; w < width - 1; ++w, ++p) {
+ for (c = 0; c < colors; ++c) {
+ t = p[0][c];
+ if (t <= delta)
+ continue;
+ t -= delta;
+ v = p[-1][c];
+ if (v > t)
+ continue;
+ hi = v;
+ v = p[1][c];
+ if (v > t)
+ continue;
+ if (v > hi)
+ hi = v;
+ v = p[-w][c];
+ if (v > t)
+ continue;
+ if (v > hi)
+ hi = v;
+ v = p[w][c];
+ if (v > t)
+ continue;
+ if (v > hi)
+ hi = v;
+#if 0
+ if (uf->hotpixels < 100)
+ printf("%u %u %u: %u->%u\t\n", w, h, c, p[0][c], hi);
+#endif
+ /* mark the pixel using the original hot value */
+ if (uf->mark_hotpixels) {
+ for (i = -10; i >= -20 && w + i >= 0; --i)
+ memcpy(p[i], p[0], sizeof (p[i]));
+ for (i = 10; i <= 20 && w + i < width; ++i)
+ memcpy(p[i], p[0], sizeof (p[i]));
+ }
+ p[0][c] = hi;
+ ++uf->hotpixels;
+ }
+ }
+ }
+}
+
/* This is the part of the conversion which is not supported by
* ufraw_convert_image_area() */
int ufraw_convert_image_first_phase(ufraw_data *uf, gboolean lensfix)
{
ufraw_image_data *FirstImage = &uf->Images[ufraw_first_phase];
dcraw_data *raw = uf->raw;
+ dcraw_image_type *rawimage;
// final->image memory will be realloc'd as needed
dcraw_image_data final;
final.image = (image_type *)FirstImage->buffer;
@@ -938,27 +1003,23 @@ int ufraw_convert_image_first_phase(ufraw_data *uf,
gboolean lensfix)
final.height = FirstImage->height;
dcraw_data *dark = uf->conf->darkframe ? uf->conf->darkframe->raw : NULL;
+ rawimage = raw->raw.image;
+ raw->raw.image = g_memdup(rawimage, raw->raw.height * raw->raw.width *
+ sizeof (dcraw_image_type));
+ ufraw_shave_hotpixels(uf, raw->raw.image, raw->raw.width, raw->raw.height,
+ raw->raw.colors, raw->rgbMax);
if ( uf->ConvertShrink>1 || !uf->HaveFilters ) {
dcraw_finalize_shrink(&final, raw, dark, uf->ConvertShrink);
uf->developer->doWB = 1;
} else {
- if (uf->conf->threshold>0) {
- dcraw_image_type *tmp = raw->raw.image;
- raw->raw.image = g_memdup(tmp, raw->raw.height * raw->raw.width *
- sizeof (dcraw_image_type));
- dcraw_wavelet_denoise(raw, uf->conf->threshold);
- dcraw_finalize_interpolate(&final, raw, dark,
- uf->conf->interpolation, uf->conf->smoothing,
- uf->developer->rgbWB);
- g_free(raw->raw.image);
- raw->raw.image = tmp;
- } else {
- dcraw_finalize_interpolate(&final, raw, dark,
- uf->conf->interpolation, uf->conf->smoothing,
- uf->developer->rgbWB);
- }
+ dcraw_wavelet_denoise(raw, uf->conf->threshold);
+ dcraw_finalize_interpolate(&final, raw, dark,
+ uf->conf->interpolation, uf->conf->smoothing,
+ uf->developer->rgbWB);
uf->developer->doWB = 0;
}
+ g_free(raw->raw.image);
+ raw->raw.image = rawimage;
dcraw_image_stretch(&final, raw->pixel_aspect);
if (uf->conf->size==0 && uf->conf->shrink>1) {
diff --git a/ufraw_ui.h b/ufraw_ui.h
index 3b6016b..689820a 100644
--- a/ufraw_ui.h
+++ b/ufraw_ui.h
@@ -61,12 +61,14 @@ typedef struct {
GtkTable *GrayscaleMixerTable;
GtkLabel *GrayscaleMixerColor;
GtkLabel *SpotPatch;
+ GtkLabel *HotpixelCount;
colorLabels *SpotLabels, *AvrLabels, *DevLabels, *OverLabels, *UnderLabels;
GtkToggleButton *AutoExposureButton, *AutoBlackButton, *LockAspectButton;
GtkWidget *AutoCurveButton;
GtkWidget *ResetWBButton, *ResetGammaButton, *ResetLinearButton;
GtkWidget *ResetExposureButton, *ResetSaturationButton;
GtkWidget *ResetThresholdButton;
+ GtkWidget *ResetHotpixelButton;
#ifdef UFRAW_CONTRAST
GtkWidget *ResetContrastButton;
#endif
@@ -92,6 +94,7 @@ typedef struct {
GtkAdjustment *LinearAdjustment;
GtkAdjustment *ExposureAdjustment;
GtkAdjustment *ThresholdAdjustment;
+ GtkAdjustment *HotpixelAdjustment;
GtkAdjustment *SaturationAdjustment;
#ifdef UFRAW_CONTRAST
GtkAdjustment *ContrastAdjustment;
--
Frank
------------------------------------------------------------------------------
Come build with us! The BlackBerry® Developer Conference in SF, CA
is the only developer event you need to attend this year. Jumpstart your
developing skills, take BlackBerry mobile applications to market and stay
ahead of the curve. Join us from November 9-12, 2009. Register now!
http://p.sf.net/sfu/devconf
_______________________________________________
ufraw-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/ufraw-devel