diff --git a/src/lens_map.c b/src/lens_map.c new file mode 100644 index 0000000..fea9b73 --- /dev/null +++ b/src/lens_map.c @@ -0,0 +1,174 @@ +#include "lens_map.h" + +#include +#include +#include +#include +#include +#include + +/* All scalar fields are explicitly little-endian; never serialize C structs + * because their padding and size_t width are ABI-dependent. */ +static const unsigned char lens_map_magic[8] = {'G', 'R', 'L', 'E', 'N', 'S', 1, 0}; +enum { LENS_MAP_VERSION = 1, LENS_MAP_ENDIAN = 0x01020304u }; + +static uint32_t crc32_update(uint32_t crc, const void *data, size_t size) { + const unsigned char *bytes = data; + for (size_t i = 0; i < size; ++i) { + crc ^= bytes[i]; + for (int bit = 0; bit < 8; ++bit) + crc = (crc >> 1) ^ (0xedb88320u & (uint32_t)-(int)(crc & 1)); + } + return crc; +} + +static int write_bytes(FILE *file, const void *data, size_t size, uint32_t *crc) { + if (fwrite(data, 1, size, file) != size) return -1; + if (crc != NULL) *crc = crc32_update(*crc, data, size); + return 0; +} +static int read_bytes(FILE *file, void *data, size_t size, uint32_t *crc) { + if (fread(data, 1, size, file) != size) return -1; + if (crc != NULL) *crc = crc32_update(*crc, data, size); + return 0; +} +static int write_u32(FILE *f, uint32_t v, uint32_t *c) { + unsigned char b[4] = {(unsigned char)v, (unsigned char)(v >> 8), + (unsigned char)(v >> 16), (unsigned char)(v >> 24)}; + return write_bytes(f, b, sizeof b, c); +} +static int read_u32(FILE *f, uint32_t *v, uint32_t *c) { + unsigned char b[4]; if (read_bytes(f, b, sizeof b, c)) return -1; + *v = (uint32_t)b[0] | ((uint32_t)b[1] << 8) | ((uint32_t)b[2] << 16) | + ((uint32_t)b[3] << 24); return 0; +} +static int write_u64(FILE *f, uint64_t v, uint32_t *c) { + unsigned char b[8]; for (int i = 0; i < 8; ++i) b[i] = (unsigned char)(v >> (8*i)); + return write_bytes(f, b, sizeof b, c); +} +static int read_u64(FILE *f, uint64_t *v, uint32_t *c) { + unsigned char b[8]; if (read_bytes(f, b, sizeof b, c)) return -1; + *v = 0; for (int i = 0; i < 8; ++i) *v |= (uint64_t)b[i] << (8*i); return 0; +} +static int write_double(FILE *f, double v, uint32_t *c) { + uint64_t bits; memcpy(&bits, &v, sizeof bits); return write_u64(f, bits, c); +} +static int read_double(FILE *f, double *v, uint32_t *c) { + uint64_t bits; if (read_u64(f, &bits, c)) return -1; memcpy(v, &bits, sizeof bits); return 0; +} +static int unit_vector(const double v[3]) { + const double n2 = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + return isfinite(n2) && fabs(n2 - 1.0) <= 1e-9; +} +static int valid_mesh(const FrameLensMesh *m) { + if (m == NULL || m->vertex_count == 0 || m->triangle_count == 0) return 0; + for (size_t i = 0; i < m->vertex_count; ++i) { + const LensVertex *v = &m->vertices[i]; + if (!v->traced || v->status < RAY_ENDPOINT_ESCAPED || + v->status > RAY_ENDPOINT_INTEGRATION_FAILURE || !isfinite(v->image_x) || + !isfinite(v->image_y) || !isfinite(v->log_frequency_ratio) || + !unit_vector(v->camera_direction) || + (v->status == RAY_ENDPOINT_ESCAPED && !unit_vector(v->n_infinity))) return 0; + } + for (size_t i = 0; i < m->triangle_count; ++i) + for (int j = 0; j < 3; ++j) + if (m->triangles[i].vertex[j] >= m->vertex_count) return 0; + return 1; +} + +int lens_map_write(const char *path, int width, int height, double fov, + const LensMapFrame *frames, size_t frame_count) { + if (path == NULL || frames == NULL || width <= 0 || height <= 0 || + !isfinite(fov) || fov <= 0.0 || fov >= 179.0 || frame_count == 0 || + frame_count > UINT64_MAX) return -1; + for (size_t i = 0; i < frame_count; ++i) if (!valid_mesh(&frames[i].mesh)) return -1; + FILE *file = fopen(path, "wb"); if (file == NULL) return -1; + int failed = write_bytes(file, lens_map_magic, sizeof lens_map_magic, NULL) || + write_u32(file, LENS_MAP_VERSION, NULL) || write_u32(file, LENS_MAP_ENDIAN, NULL) || + write_u32(file, (uint32_t)width, NULL) || write_u32(file, (uint32_t)height, NULL) || + write_double(file, fov, NULL) || write_u64(file, (uint64_t)frame_count, NULL); + for (size_t f = 0; !failed && f < frame_count; ++f) { + const FrameLensMesh *m = &frames[f].mesh; uint32_t crc = UINT32_MAX; + failed = write_u64(file, frames[f].frame_id, NULL) || + write_double(file, frames[f].coordinate_time, NULL) || + write_double(file, frames[f].proper_time, NULL) || + write_u64(file, (uint64_t)m->vertex_count, NULL) || + write_u64(file, (uint64_t)m->triangle_count, NULL); + for (size_t i = 0; !failed && i < m->vertex_count; ++i) { + const LensVertex *v = &m->vertices[i]; + failed = write_double(file, v->image_x, &crc) || write_double(file, v->image_y, &crc); + for (int j = 0; !failed && j < 3; ++j) failed = write_double(file, v->camera_direction[j], &crc); + for (int j = 0; !failed && j < 3; ++j) failed = write_double(file, v->n_infinity[j], &crc); + failed = failed || write_double(file, v->log_frequency_ratio, &crc) || + write_u32(file, (uint32_t)v->status, &crc); + } + for (size_t i = 0; !failed && i < m->triangle_count; ++i) { + for (int j = 0; j < 3; ++j) failed = failed || write_u64(file, m->triangles[i].vertex[j], &crc); + failed = failed || write_u32(file, m->triangles[i].level, &crc); + } + failed = failed || write_u32(file, crc ^ UINT32_MAX, NULL); + } + if (fclose(file)) failed = 1; + return failed ? -1 : 0; +} + +void lens_map_destroy(LensMap *map) { + if (map == NULL) return; + for (size_t i = 0; i < map->frame_count; ++i) frame_lens_mesh_destroy(&map->frames[i].mesh); + free(map->frames); *map = (LensMap){0}; +} + +int lens_map_read(const char *path, LensMap *map) { + if (path == NULL || map == NULL) return -1; + *map = (LensMap){0}; FILE *file = fopen(path, "rb"); if (file == NULL) return -1; + unsigned char magic[8]; uint32_t version, endian, width, height; uint64_t count; + int failed = read_bytes(file, magic, sizeof magic, NULL) || memcmp(magic, lens_map_magic, sizeof magic) || + read_u32(file, &version, NULL) || read_u32(file, &endian, NULL) || + read_u32(file, &width, NULL) || read_u32(file, &height, NULL) || + read_double(file, &map->horizontal_fov_deg, NULL) || read_u64(file, &count, NULL) || + version != LENS_MAP_VERSION || endian != LENS_MAP_ENDIAN || width == 0 || height == 0 || + width > INT32_MAX || height > INT32_MAX || !isfinite(map->horizontal_fov_deg) || + map->horizontal_fov_deg <= 0.0 || map->horizontal_fov_deg >= 179.0 || count == 0 || + count > SIZE_MAX / sizeof *map->frames; + if (failed) goto done; + map->width = (int)width; map->height = (int)height; map->frame_count = (size_t)count; + map->frames = calloc(map->frame_count, sizeof *map->frames); if (map->frames == NULL) { failed = 1; goto done; } + for (size_t f = 0; !failed && f < map->frame_count; ++f) { + LensMapFrame *frame = &map->frames[f]; uint64_t vertices, triangles; uint32_t stored_crc, crc = UINT32_MAX; + failed = read_u64(file, &frame->frame_id, NULL) || read_double(file, &frame->coordinate_time, NULL) || + read_double(file, &frame->proper_time, NULL) || read_u64(file, &vertices, NULL) || read_u64(file, &triangles, NULL) || + !isfinite(frame->coordinate_time) || !isfinite(frame->proper_time) || vertices == 0 || triangles == 0 || + vertices > SIZE_MAX / sizeof *frame->mesh.vertices || triangles > SIZE_MAX / sizeof *frame->mesh.triangles; + if (failed) break; + frame->mesh.vertices = calloc((size_t)vertices, sizeof *frame->mesh.vertices); + frame->mesh.triangles = calloc((size_t)triangles, sizeof *frame->mesh.triangles); + if (frame->mesh.vertices == NULL || frame->mesh.triangles == NULL) { failed = 1; break; } + frame->mesh.vertex_count = frame->mesh.vertex_capacity = (size_t)vertices; + frame->mesh.triangle_count = frame->mesh.triangle_capacity = (size_t)triangles; + for (size_t i = 0; !failed && i < frame->mesh.vertex_count; ++i) { + LensVertex *v = &frame->mesh.vertices[i]; uint32_t status; + failed = read_double(file, &v->image_x, &crc) || read_double(file, &v->image_y, &crc); + for (int j = 0; !failed && j < 3; ++j) failed = read_double(file, &v->camera_direction[j], &crc); + for (int j = 0; !failed && j < 3; ++j) failed = read_double(file, &v->n_infinity[j], &crc); + failed = failed || read_double(file, &v->log_frequency_ratio, &crc) || read_u32(file, &status, &crc) || + status > RAY_ENDPOINT_INTEGRATION_FAILURE; + v->status = (RayEndpointStatus)status; v->traced = 1; + } + for (size_t i = 0; !failed && i < frame->mesh.triangle_count; ++i) { + for (int j = 0; j < 3; ++j) { + uint64_t index = 0; + if (read_u64(file, &index, &crc) || index > SIZE_MAX) { + failed = 1; + break; + } + frame->mesh.triangles[i].vertex[j] = (size_t)index; + } + failed = failed || read_u32(file, &frame->mesh.triangles[i].level, &crc); frame->mesh.triangles[i].evaluated = 1; + } + failed = failed || read_u32(file, &stored_crc, NULL) || stored_crc != (crc ^ UINT32_MAX) || !valid_mesh(&frame->mesh); + } +done: + if (fclose(file)) failed = 1; + if (failed) { lens_map_destroy(map); return -1; } + return 0; +} diff --git a/src/lens_map.h b/src/lens_map.h new file mode 100644 index 0000000..e269922 --- /dev/null +++ b/src/lens_map.h @@ -0,0 +1,32 @@ +#ifndef LENS_MAP_H +#define LENS_MAP_H + +#include "frame.h" + +#include +#include + +/* A finalized, render-only lens map. It deliberately contains no adaptive + * refinement work queues: imported maps may be splatted or drawn, but cannot + * be refined without tracing new rays. */ +typedef struct { + uint64_t frame_id; + double coordinate_time; + double proper_time; + FrameLensMesh mesh; +} LensMapFrame; + +typedef struct { + int width, height; + double horizontal_fov_deg; + LensMapFrame *frames; + size_t frame_count; +} LensMap; + +int lens_map_write(const char *path, int width, int height, + double horizontal_fov_deg, const LensMapFrame *frames, + size_t frame_count); +int lens_map_read(const char *path, LensMap *map); +void lens_map_destroy(LensMap *map); + +#endif diff --git a/src/main.c b/src/main.c index f04c986..a8e349b 100644 --- a/src/main.c +++ b/src/main.c @@ -1,5 +1,6 @@ #include "catalog.h" #include "frame.h" +#include "lens_map.h" #include "movie.h" #include "observer_track.h" #include "optics.h" @@ -33,6 +34,9 @@ typedef struct { const char *catalog_path; const char *all_sky_catalog_path; const char *output_path; + const char *lens_map_input_path; + const char *lens_map_output_path; + int width_specified, height_specified, fov_specified; #ifdef ENABLE_HDR_OUTPUT int write_hdr_output; char hdr_output_path[PATH_MAX]; @@ -216,14 +220,20 @@ static int parse_args(int argc, char **argv, Settings *s, s->all_sky_catalog_path = argv[++i]; else if (!strcmp(argv[i], "--output") && i + 1 < argc) s->output_path = argv[++i]; + else if (!strcmp(argv[i], "--lens-map-input") && i + 1 < argc) + s->lens_map_input_path = argv[++i]; + else if (!strcmp(argv[i], "--lens-map-output") && i + 1 < argc) + s->lens_map_output_path = argv[++i]; #ifdef ENABLE_HDR_OUTPUT else if (!strcmp(argv[i], "--hdr-output")) s->write_hdr_output = 1; #endif else if (!strcmp(argv[i], "--width") && i + 1 < argc && !parse_int(argv[++i], &s->width)) { + s->width_specified = 1; } else if (!strcmp(argv[i], "--height") && i + 1 < argc && !parse_int(argv[++i], &s->height)) { + s->height_specified = 1; } else if (!strcmp(argv[i], "--coarse-cell-pixels") && i + 1 < argc && !parse_int(argv[++i], &s->coarse_cell_pixels)) { } else if (!strcmp(argv[i], "--refine-max-level") && i + 1 < argc && @@ -243,6 +253,7 @@ static int parse_args(int argc, char **argv, Settings *s, s->draw_mesh = 1; } else if (!strcmp(argv[i], "--fov-deg") && i + 1 < argc && !parse_double(argv[++i], &s->horizontal_fov_deg)) { + s->fov_specified = 1; } else if (!strcmp(argv[i], "--look-ra-deg") && i + 1 < argc && !parse_ra_deg(argv[++i], &s->look_ra_deg)) { } else if (!strcmp(argv[i], "--look-dec-deg") && i + 1 < argc && @@ -522,6 +533,21 @@ static int render_observer_frame(const Settings *s, StarCatalog *catalog, "%zu vertices, %zu triangles.\n", omp_get_wtime() - refinement_start, mesh.vertex_count, mesh.triangle_count); + if (s->lens_map_output_path != NULL) { + const LensMapFrame map_frame = {.frame_id = 0, + .coordinate_time = 0.0, + .proper_time = 0.0, + .mesh = mesh}; + if (lens_map_write(s->lens_map_output_path, s->width, s->height, + s->horizontal_fov_deg, &map_frame, 1)) { + fprintf(stderr, "Failed to write lens map: %s\n", s->lens_map_output_path); + frame_lens_mesh_destroy(&mesh); + free(hdr); + return -1; + } + fprintf(stderr, "Wrote lens map: %s (%zu vertices, %zu triangles)\n", + s->lens_map_output_path, mesh.vertex_count, mesh.triangle_count); + } if (s->verbose) fprintf(stderr, "Frame 0: traced %zu lens vertices; starting catalog render.\n", mesh.vertex_count); @@ -697,6 +723,25 @@ static int render_movie(const Settings *s, StarCatalog *catalog, if (traced == 0) break; } + if (s->lens_map_output_path != NULL) { + LensMapFrame *map_frames = calloc(movie.frame_count, sizeof *map_frames); + if (map_frames == NULL) goto done; + for (size_t i = 0; i < movie.frame_count; ++i) + map_frames[i] = (LensMapFrame){.frame_id = movie.frames[i].frame_id, + .coordinate_time = movie.frames[i].coordinate_time, + .proper_time = movie.frames[i].proper_time, + .mesh = movie.frames[i].mesh}; + const int write_failed = lens_map_write(s->lens_map_output_path, s->width, + s->height, s->horizontal_fov_deg, + map_frames, movie.frame_count); + free(map_frames); + if (write_failed) { + fprintf(stderr, "Failed to write lens map: %s\n", s->lens_map_output_path); + goto done; + } + fprintf(stderr, "Wrote lens-map movie: %s (%zu frames)\n", + s->lens_map_output_path, movie.frame_count); + } for (size_t i = 0; i < movie.frame_count; ++i) { char output_path[PATH_MAX]; double *hdr = calloc((size_t)s->width * s->height * 3, sizeof *hdr); @@ -739,6 +784,74 @@ done: return result; } +static int render_lens_map(const Settings *s, StarCatalog *catalog) { + LensMap map = {0}; + if (lens_map_read(s->lens_map_input_path, &map)) { + fprintf(stderr, "Failed to read or validate lens map: %s\n", s->lens_map_input_path); + return -1; + } + if ((s->width_specified && s->width != map.width) || + (s->height_specified && s->height != map.height) || + (s->fov_specified && fabs(s->horizontal_fov_deg - map.horizontal_fov_deg) > 1e-12)) { + fputs("Imported lens-map geometry conflicts with --width, --height, or --fov-deg.\n", stderr); + lens_map_destroy(&map); + return -1; + } + if ((map.frame_count == 1 && s->frames_dir != NULL) || + (map.frame_count > 1 && s->frames_dir == NULL)) { + fputs("A single-frame lens map uses --output; a multi-frame lens map requires --frames-dir.\n", stderr); + lens_map_destroy(&map); + return -1; + } + int result = 0; + for (size_t i = 0; i < map.frame_count; ++i) { + const char *output_path = s->output_path; + char movie_path[PATH_MAX]; + if (map.frame_count > 1) { + if (frame_output_path(movie_path, s, (size_t)map.frames[i].frame_id)) { + fputs("Could not construct imported-map movie output path.\n", stderr); + result = -1; break; + } + output_path = movie_path; + } + double *hdr = calloc((size_t)map.width * map.height * 3, sizeof *hdr); + if (hdr == NULL) { result = -1; break; } + CatalogPrefetchStats prefetch = {0}; + PsfSplatStats psf_stats = {0}; + RenderProgress progress = {.verbose = s->verbose, + .frame_id = (size_t)map.frames[i].frame_id, + .prefetch = &prefetch, + .catalog_load_workers = s->catalog_load_workers, + .all_sky_catalog = catalog->kind == STAR_CATALOG_ALL_SKY}; + const size_t images = frame_splat_catalog( + &map.frames[i].mesh, catalog, hdr, map.width, map.height, s->exposure, + &s->psf, &s->psf_cache, s->max_magnification, s->max_cache_psf_flux, + s->psf_relative_tail, s->psf_min_y, 0, s->catalog_load_workers, + &prefetch, &psf_stats, + &(FrameSplatProgress){report_splat_progress, + s->verbose ? report_splat_worker_progress : NULL, + &progress}); + if (s->draw_mesh) + frame_draw_mesh(&map.frames[i].mesh, hdr, map.width, map.height, 0.5, 0.5); +#ifdef ENABLE_HDR_OUTPUT + if (s->write_hdr_output && + write_hdr_fits(s->hdr_output_path, hdr, map.width, map.height, + map.horizontal_fov_deg)) { + fprintf(stderr, "Failed to write HDR FITS image: %s\n", s->hdr_output_path); + free(hdr); result = -1; break; + } +#endif + const int write_result = write_tonemapped_image(output_path, hdr, map.width, map.height); + free(hdr); + fprintf(stderr, "Rendered %zu images from %zu catalog stars to %s (%s; imported lens map)\n", + images, catalog->count, output_path, write_result == 0 ? "ok" : "write failed"); + psf_kernel_cache_report(&s->psf_cache, &psf_stats, stderr); + if (write_result) { result = -1; break; } + } + lens_map_destroy(&map); + return result; +} + static int write_minkowski_accel_track(const Settings *s) { ObserverTrack track = {0}; const int result = observer_track_generate_minkowski_acceleration( @@ -763,6 +876,7 @@ int main(int argc, char **argv) { fprintf(stderr, "Usage: %s [--catalog PATH | --all-sky-catalog DIR] [--output PATH] [--width N] [--height " "N] [--fov-deg D] [--look-ra-deg D] [--look-dec-deg D] " + "[--lens-map-input FILE | --lens-map-output FILE] " "[--exposure E] [--observer-radius R] [--observer-inward-speed V] " "[--psf-fwhm-pixels N] [--psf-moffat-beta N] " "[--max-magnification M] [--max-cache-psf-flux F] " @@ -794,6 +908,10 @@ int main(int argc, char **argv) { return write_minkowski_accel_track(&settings) == 0 ? 0 : (perror(settings.write_minkowski_accel_track_path), 1); + if (settings.lens_map_input_path != NULL && settings.lens_map_output_path != NULL) { + fputs("--lens-map-input and --lens-map-output are mutually exclusive.\n", stderr); + return 2; + } #ifdef ENABLE_HDR_OUTPUT if (settings.frames_dir != NULL && settings.write_hdr_output) { fputs("--hdr-output is available only for a single-frame render.\n", stderr); @@ -824,16 +942,23 @@ int main(int argc, char **argv) { } fprintf(stderr, "Created test catalog: %s\n", settings.catalog_path); } - SpacetimeSource spacetime = {0}; - if (spacetime_create_default(&spacetime)) { - fputs("Could not create spacetime source\n", stderr); - catalog_destroy(&catalog); - return 1; - } if (!settings.psf_direct && psf_kernel_cache_init(&settings.psf_cache, &settings.psf, settings.psf_relative_tail)) fputs("PSF cache construction failed; using direct evaluator.\n", stderr); psf_kernel_cache_report_ready(&settings.psf_cache, stderr); + if (settings.lens_map_input_path != NULL) { + const int result = render_lens_map(&settings, &catalog); + catalog_destroy(&catalog); + psf_kernel_cache_destroy(&settings.psf_cache); + return result == 0 ? 0 : 1; + } + SpacetimeSource spacetime = {0}; + if (spacetime_create_default(&spacetime)) { + fputs("Could not create spacetime source\n", stderr); + catalog_destroy(&catalog); + psf_kernel_cache_destroy(&settings.psf_cache); + return 1; + } int result = settings.frames_dir != NULL ? render_movie(&settings, &catalog, &spacetime) : render_frame(&settings, &catalog, &spacetime); diff --git a/tests/test_frame.c b/tests/test_frame.c index 6b2f5d3..b4b052c 100644 --- a/tests/test_frame.c +++ b/tests/test_frame.c @@ -1,4 +1,5 @@ #include "frame.h" +#include "lens_map.h" #include "optics.h" #include @@ -6,6 +7,7 @@ #include #include #include +#include static int mesh_has_hanging_vertex(const FrameLensMesh *mesh) { for (size_t triangle = 0; triangle < mesh->triangle_count; ++triangle) @@ -84,6 +86,55 @@ int main(void) { fputs("flat-space inverse lens-map regression failed\n", stderr); goto done; } + /* A finalized mesh can be persisted independently of spacetime and then + * drive the exact same catalog inverse-map and PSF pass. */ + const char *lens_map_path = "/tmp/gr_lens_map_test.grlens"; + const LensMapFrame saved_frame = {.frame_id = 7, + .coordinate_time = 3.0, + .proper_time = 2.0, + .mesh = mesh}; + LensMap loaded_map = {0}; + double *roundtrip_hdr = calloc((size_t)width * height * 3, sizeof *roundtrip_hdr); + if (roundtrip_hdr == NULL || + lens_map_write(lens_map_path, width, height, 30.0, &saved_frame, 1) || + lens_map_read(lens_map_path, &loaded_map) || loaded_map.frame_count != 1 || + loaded_map.frames[0].frame_id != 7 || loaded_map.width != width || + loaded_map.height != height || + loaded_map.frames[0].mesh.vertex_count != mesh.vertex_count || + frame_splat_catalog(&loaded_map.frames[0].mesh, &catalog, roundtrip_hdr, + width, height, test_exposure, &psf, NULL, INFINITY, + 1.0, psf_relative_tail, 0.0, 0, 1, NULL, NULL, NULL) != images) { + fputs("lens-map round-trip regression failed\n", stderr); + free(roundtrip_hdr); lens_map_destroy(&loaded_map); unlink(lens_map_path); + goto done; + } + for (int value = 0; value < width * height * 3; ++value) + if (hdr[value] != roundtrip_hdr[value]) { + fputs("lens-map round-trip HDR regression failed\n", stderr); + free(roundtrip_hdr); lens_map_destroy(&loaded_map); unlink(lens_map_path); + goto done; + } + free(roundtrip_hdr); + lens_map_destroy(&loaded_map); + /* A damaged payload must not be mistaken for a reusable physical map. */ + FILE *damaged = fopen(lens_map_path, "r+b"); + int damage_failed = damaged == NULL; + if (!damage_failed) { + if (fseek(damaged, -5L, SEEK_END)) + damage_failed = 1; + const int original = damage_failed ? EOF : fgetc(damaged); + if (damage_failed || fseek(damaged, -5L, SEEK_END) || original == EOF || + fputc(original ^ 0xff, damaged) == EOF) + damage_failed = 1; + } + if (damaged != NULL && fclose(damaged)) + damage_failed = 1; + if (damage_failed || !lens_map_read(lens_map_path, &loaded_map)) { + fputs("lens-map corruption rejection regression failed\n", stderr); + lens_map_destroy(&loaded_map); unlink(lens_map_path); + goto done; + } + unlink(lens_map_path); memset(hdr, 0, (size_t)width * height * 3 * sizeof *hdr); PsfSplatStats min_y_stats = {0}; if (frame_splat_catalog(&mesh, &catalog, hdr, width, height, test_exposure,