// // 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 // // 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 #if LIBRAW_VERSION < LIBRAW_MAKE_VERSION(0, 21, 0) #error LibRaw 0.21.0 or newer is required. #endif #include #include #include #include #include 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; }