Add a tool to find hot pixels

It works well for my Nikon.

Note that hot pixels can be eliminated in the camera itself,
when you run sensor cleaning immediately after a very long exposure
of darkness.
This commit is contained in:
Přemysl Eric Janouch 2023-10-10 09:31:13 +02:00
parent e79574fd56
commit 72bf913f3d
Signed by: p
GPG Key ID: A0420B94F92B9493
2 changed files with 211 additions and 1 deletions

View File

@ -192,7 +192,7 @@ if get_option('tools').enabled()
cc.find_library('jq'), dependency('libpng'), dependency('libraw')]
tools_c_args = cc.get_supported_arguments(
'-Wno-unused-function', '-Wno-unused-parameter')
foreach tool : ['info', 'pnginfo', 'rawinfo']
foreach tool : ['info', 'pnginfo', 'rawinfo', 'hotpixels']
executable(tool, 'tools/' + tool + '.c', tiff_tables,
dependencies : tools_dependencies,
c_args: tools_c_args)

210
tools/hotpixels.c Normal file
View File

@ -0,0 +1,210 @@
//
// hotpixels.c: look for hot pixels in raw image files
//
// Usage: pass a bunch of raw photo images taken with the lens cap on at,
// e.g., ISO 8000-12800 @ 1/20-1/60, and store the resulting file as,
// e.g., Nikon D7500.badpixels, which can then be directly used by Rawtherapee.
//
// Copyright (c) 2023, Přemysl Eric Janouch <p@janouch.name>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
#include <libraw.h>
#if LIBRAW_VERSION < LIBRAW_MAKE_VERSION(0, 21, 0)
#error LibRaw 0.21.0 or newer is required.
#endif
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void *
xreallocarray(void *o, size_t n, size_t m)
{
if (m && n > SIZE_MAX / m) {
fprintf(stderr, "xreallocarray: %s\n", strerror(ENOMEM));
exit(EXIT_FAILURE);
}
void *p = realloc(o, n * m);
if (!p && n && m) {
fprintf(stderr, "xreallocarray: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
return p;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
struct coord { ushort x, y; };
static bool
coord_equals(struct coord a, struct coord b)
{
return a.x == b.x && a.y == b.y;
}
static int
coord_cmp(const void *a, const void *b)
{
const struct coord *ca = (const struct coord *) a;
const struct coord *cb = (const struct coord *) b;
return ca->y != cb->y
? (int) ca->y - (int) cb->y
: (int) ca->x - (int) cb->x;
}
struct candidates {
struct coord *xy;
size_t len;
size_t alloc;
};
static void
candidates_add(struct candidates *c, ushort x, ushort y)
{
if (c->len == c->alloc) {
c->alloc += 64;
c->xy = xreallocarray(c->xy, sizeof *c->xy, c->alloc);
}
c->xy[c->len++] = (struct coord) {x, y};
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// A stretch of zeroes that is assumed to mean start of outliers.
#define SPAN 10
static const char *
process_raw(struct candidates *c, const uint8_t *p, size_t len)
{
libraw_data_t *iprc = libraw_init(LIBRAW_OPIONS_NO_DATAERR_CALLBACK);
if (!iprc)
return "failed to obtain a LibRaw handle";
int err = 0;
if ((err = libraw_open_buffer(iprc, p, len)) ||
(err = libraw_unpack(iprc))) {
libraw_close(iprc);
return libraw_strerror(err);
}
if (!iprc->rawdata.raw_image) {
libraw_close(iprc);
return "only Bayer raws are supported, not Foveon";
}
// Make a histogram.
uint64_t bins[USHRT_MAX] = {};
for (ushort yy = 0; yy < iprc->sizes.height; yy++) {
for (ushort xx = 0; xx < iprc->sizes.width; xx++) {
ushort y = iprc->sizes.top_margin + yy;
ushort x = iprc->sizes.left_margin + xx;
bins[iprc->rawdata.raw_image[y * iprc->sizes.raw_width + x]]++;
}
}
// Detecting outliers is not completely straight-forward,
// it may help to see the histogram.
if (getenv("HOTPIXELS_HISTOGRAM")) {
for (ushort i = 0; i < USHRT_MAX; i++)
fprintf(stderr, "%u ", (unsigned) bins[i]);
fputc('\n', stderr);
}
// Go to the first non-zero pixel value.
size_t last = 0;
for (; last < USHRT_MAX; last++)
if (bins[last])
break;
// Find the last pixel value we assume to not be hot.
for (; last < USHRT_MAX - SPAN - 1; last++) {
uint64_t nonzero = 0;
for (int i = 1; i <= SPAN; i++)
nonzero += bins[last + i];
if (!nonzero)
break;
}
// Store coordinates for all pixels above that value.
for (ushort yy = 0; yy < iprc->sizes.height; yy++) {
for (ushort xx = 0; xx < iprc->sizes.width; xx++) {
ushort y = iprc->sizes.top_margin + yy;
ushort x = iprc->sizes.left_margin + xx;
if (iprc->rawdata.raw_image[y * iprc->sizes.raw_width + x] > last)
candidates_add(c, xx, yy);
}
}
libraw_close(iprc);
return NULL;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static const char *
do_file(struct candidates *c, const char *filename)
{
FILE *fp = fopen(filename, "rb");
if (!fp)
return strerror(errno);
uint8_t *data = NULL, buf[256 << 10];
size_t n, len = 0;
while ((n = fread(buf, sizeof *buf, sizeof buf / sizeof *buf, fp))) {
data = xreallocarray(data, len + n, 1);
memcpy(data + len, buf, n);
len += n;
}
const char *err = ferror(fp)
? strerror(errno)
: process_raw(c, data, len);
fclose(fp);
free(data);
return err;
}
int
main(int argc, char *argv[])
{
struct candidates c = {};
for (int i = 1; i < argc; i++) {
const char *filename = argv[i], *err = do_file(&c, filename);
if (err) {
fprintf(stderr, "%s: %s\n", filename, err);
return EXIT_FAILURE;
}
}
qsort(c.xy, c.len, sizeof *c.xy, coord_cmp);
// If it is detected in all passed photos, it is probably indeed bad.
int count = 1;
for (size_t i = 1; i <= c.len; i++) {
if (i != c.len && coord_equals(c.xy[i - 1], c.xy[i])) {
count++;
continue;
}
if (count == argc - 1)
printf("%u %u\n", c.xy[i - 1].x, c.xy[i - 1].y);
count = 1;
}
return 0;
}