Optics: parameterize PSF relative tail

This commit is contained in:
wyj committed 2026-08-30 15:05:40 -04:00
1 parent ff2ba43333
commit 8662064044
7 files changed
+111 -47

No files matched your search

+5
View File
@@ -234,6 +234,11 @@ cached/direct-fallback image counts. Pass `--psf-direct` to use the slower
whose required HDR-tail support exceeds the cache radius selects that reference
path automatically.
`--psf-relative-tail R` controls the maximum omitted PSF tail fraction per
image; it defaults to `1e-8`. Larger values intentionally shorten the Moffat
support and rebuild the immutable cache at the corresponding radius, which is
useful when a faster, lower-fidelity render is acceptable.
For bounded preview renders, `--max-cache-psf-flux F` (default `1`) allows
images with `1 < flux <= F` to use the existing cache instead of the direct
evaluator. This does not rebuild or enlarge the cache: the image retains its
+24 -11
View File
@@ -850,6 +850,7 @@ typedef struct {
const PointSpreadFunction *psf;
const PsfKernelCache *psf_cache;
double max_cache_psf_flux;
double psf_relative_tail;
size_t images;
size_t direct_fallbacks;
size_t cached_wing_clipped;
@@ -917,7 +918,8 @@ static int splat_catalog_tile(const Star *stars, size_t count,
const int direct_fallback = splat_moffat_cached(
context->hdr, context->width, context->height, image_x, image_y, color,
flux,
context->psf, context->psf_cache, context->max_cache_psf_flux);
context->psf, context->psf_cache, context->max_cache_psf_flux,
context->psf_relative_tail);
context->direct_fallbacks += direct_fallback == 1;
context->cached_wing_clipped += direct_fallback == 2;
#ifdef GR_DEBUG
@@ -941,7 +943,8 @@ static CatalogSplatStats splat_catalog_triangles(
const FrameLensMesh *mesh, StarCatalog *catalog, double *hdr, int width,
int height, double exposure, const PointSpreadFunction *psf,
const PsfKernelCache *psf_cache, double max_magnification,
double max_cache_psf_flux, size_t first_triangle, size_t last_triangle) {
double max_cache_psf_flux, double psf_relative_tail,
size_t first_triangle, size_t last_triangle) {
CatalogSplatStats stats = {0};
for (size_t t = first_triangle; t < last_triangle; ++t) {
const LensVertex *vertex[3];
@@ -974,7 +977,8 @@ static CatalogSplatStats splat_catalog_triangles(
.width = width, .height = height,
.exposure = exposure, .magnification = magnification,
.psf = psf, .psf_cache = psf_cache,
.max_cache_psf_flux = max_cache_psf_flux};
.max_cache_psf_flux = max_cache_psf_flux,
.psf_relative_tail = psf_relative_tail};
if (catalog_visit_source_triangle(catalog, direction, 0, splat_catalog_tile,
&context) == 0)
stats.images += context.images;
@@ -1011,6 +1015,7 @@ size_t frame_splat_catalog(const FrameLensMesh *mesh,
const PsfKernelCache *psf_cache,
double max_magnification,
double max_cache_psf_flux,
double psf_relative_tail,
int limit_workers_by_memory,
int catalog_load_workers,
CatalogPrefetchStats *prefetch_stats,
@@ -1019,7 +1024,9 @@ size_t frame_splat_catalog(const FrameLensMesh *mesh,
if (mesh == NULL || catalog == NULL || hdr == NULL || exposure <= 0.0 ||
psf == NULL || width <= 0 || height <= 0 || catalog_load_workers <= 0 ||
isnan(max_magnification) || max_magnification <= 0.0 ||
!isfinite(max_cache_psf_flux) || max_cache_psf_flux < 1.0)
!isfinite(max_cache_psf_flux) || max_cache_psf_flux < 1.0 ||
!isfinite(psf_relative_tail) || psf_relative_tail <= 0.0 ||
psf_relative_tail >= 1.0)
return 0;
/* A bounded parallel read phase completes before splatting. Its serial cache
@@ -1043,7 +1050,8 @@ size_t frame_splat_catalog(const FrameLensMesh *mesh,
{
const CatalogSplatStats stats = splat_catalog_triangles(
mesh, catalog, hdr, width, height, exposure, psf, psf_cache,
max_magnification, max_cache_psf_flux, 0, mesh->triangle_count);
max_magnification, max_cache_psf_flux, psf_relative_tail,
0, mesh->triangle_count);
copy_psf_splat_stats(psf_stats, stats);
return stats.images;
}
@@ -1059,7 +1067,8 @@ size_t frame_splat_catalog(const FrameLensMesh *mesh,
{
const CatalogSplatStats stats = splat_catalog_triangles(
mesh, catalog, hdr, width, height, exposure, psf, psf_cache,
max_magnification, max_cache_psf_flux, 0, mesh->triangle_count);
max_magnification, max_cache_psf_flux, psf_relative_tail,
0, mesh->triangle_count);
copy_psf_splat_stats(psf_stats, stats);
return stats.images;
}
@@ -1069,7 +1078,8 @@ size_t frame_splat_catalog(const FrameLensMesh *mesh,
{
const CatalogSplatStats stats = splat_catalog_triangles(
mesh, catalog, hdr, width, height, exposure, psf, psf_cache,
max_magnification, max_cache_psf_flux, 0, mesh->triangle_count);
max_magnification, max_cache_psf_flux, psf_relative_tail,
0, mesh->triangle_count);
copy_psf_splat_stats(psf_stats, stats);
return stats.images;
}
@@ -1084,8 +1094,9 @@ size_t frame_splat_catalog(const FrameLensMesh *mesh,
free(private_hdr[--allocated]);
free(private_hdr);
const CatalogSplatStats stats = splat_catalog_triangles(
mesh, catalog, hdr, width, height, exposure, psf, psf_cache,
max_magnification, max_cache_psf_flux, 0, mesh->triangle_count);
mesh, catalog, hdr, width, height, exposure, psf, psf_cache,
max_magnification, max_cache_psf_flux, psf_relative_tail,
0, mesh->triangle_count);
copy_psf_splat_stats(psf_stats, stats);
return stats.images;
}
@@ -1114,7 +1125,8 @@ size_t frame_splat_catalog(const FrameLensMesh *mesh,
for (size_t triangle = 0; triangle < mesh->triangle_count; ++triangle) {
const CatalogSplatStats stats = splat_catalog_triangles(
mesh, catalog, private_hdr[worker], width, height, exposure, psf,
psf_cache, max_magnification, max_cache_psf_flux, triangle, triangle + 1);
psf_cache, max_magnification, max_cache_psf_flux, psf_relative_tail,
triangle, triangle + 1);
images += stats.images;
direct_fallbacks += stats.direct_fallbacks;
cached_wing_clipped += stats.cached_wing_clipped;
@@ -1145,7 +1157,8 @@ size_t frame_splat_catalog(const FrameLensMesh *mesh,
for (size_t triangle = 0; triangle < mesh->triangle_count; ++triangle) {
const CatalogSplatStats stats = splat_catalog_triangles(
mesh, catalog, private_hdr[worker], width, height, exposure, psf,
psf_cache, max_magnification, max_cache_psf_flux, triangle, triangle + 1);
psf_cache, max_magnification, max_cache_psf_flux, psf_relative_tail,
triangle, triangle + 1);
images += stats.images;
direct_fallbacks += stats.direct_fallbacks;
cached_wing_clipped += stats.cached_wing_clipped;
+1
View File
@@ -126,6 +126,7 @@ size_t frame_splat_catalog(const FrameLensMesh *mesh,
const PsfKernelCache *psf_cache,
double max_magnification,
double max_cache_psf_flux,
double psf_relative_tail,
int limit_workers_by_memory,
int catalog_load_workers,
CatalogPrefetchStats *prefetch_stats,
+10 -1
View File
@@ -24,6 +24,7 @@ typedef struct {
double horizontal_fov_deg, look_ra_deg, look_dec_deg, exposure;
double max_magnification;
double max_cache_psf_flux;
double psf_relative_tail;
double observer_radius;
double observer_inward_speed;
PointSpreadFunction psf;
@@ -186,6 +187,7 @@ static int parse_args(int argc, char **argv, Settings *s,
.exposure = 1e-3,
.max_magnification = INFINITY,
.max_cache_psf_flux = 1.0,
.psf_relative_tail = 1e-8,
.observer_radius = 30.0,
.psf = {2.7, 4.5},
.catalog_path = "assets/sky_grid_5deg.csv",
@@ -255,6 +257,9 @@ static int parse_args(int argc, char **argv, Settings *s,
!parse_finite_positive(argv[++i], &s->max_magnification)) {
} else if (!strcmp(argv[i], "--max-cache-psf-flux") && i + 1 < argc &&
!parse_at_least_one(argv[++i], &s->max_cache_psf_flux)) {
} else if (!strcmp(argv[i], "--psf-relative-tail") && i + 1 < argc &&
!parse_finite_positive(argv[++i], &s->psf_relative_tail) &&
s->psf_relative_tail < 1.0) {
} else if (!strcmp(argv[i], "--psf-direct")) {
s->psf_direct = 1;
} else if (!strcmp(argv[i], "--verbose")) {
@@ -463,6 +468,7 @@ static int render_observer_frame(const Settings *s, StarCatalog *catalog,
size_t images = frame_splat_catalog(
&mesh, catalog, hdr, s->width, s->height, s->exposure, &s->psf,
&s->psf_cache, s->max_magnification, s->max_cache_psf_flux,
s->psf_relative_tail,
spacetime_limits_render_workers_by_memory(spacetime),
s->catalog_load_workers, &prefetch, &psf_stats,
&(FrameSplatProgress){report_splat_progress,
@@ -641,6 +647,7 @@ static int render_movie(const Settings *s, StarCatalog *catalog,
const size_t images = frame_splat_catalog(
&movie.frames[i].mesh, catalog, hdr, s->width, s->height, s->exposure,
&s->psf, &s->psf_cache, s->max_magnification, s->max_cache_psf_flux,
s->psf_relative_tail,
spacetime_limits_render_workers_by_memory(spacetime),
s->catalog_load_workers, &prefetch, &psf_stats,
&(FrameSplatProgress){report_splat_progress,
@@ -686,6 +693,7 @@ int main(int argc, char **argv) {
"[--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] "
"[--psf-relative-tail R] "
"[--psf-direct] [--verbose] "
#ifdef ENABLE_HDR_OUTPUT
"[--hdr-output] "
@@ -749,7 +757,8 @@ int main(int argc, char **argv) {
catalog_destroy(&catalog);
return 1;
}
if (!settings.psf_direct && psf_kernel_cache_init(&settings.psf_cache, &settings.psf))
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);
int result = settings.frames_dir != NULL
+35 -18
View File
@@ -92,7 +92,6 @@ enum {
* relative flux cut. The historical 1e-8 relative tail remains a floor for
* ordinary images; brighter images grow their support or use the reference
* fallback rather than acquiring a visible clipped wing. */
static const double psf_relative_tail_fraction = 1e-8;
static const double psf_tail_absolute_hdr_budget = 1e-6;
static const double psf_boundary_hdr_budget = 1e-7;
static const double pi = 3.14159265358979323846;
@@ -125,10 +124,14 @@ static double moffat_alpha(const PointSpreadFunction *psf)
}
static double moffat_support_radius(double alpha, double beta,
double scaled_flux)
double scaled_flux,
double relative_tail_fraction)
{
if (!isfinite(relative_tail_fraction) || relative_tail_fraction <= 0.0 ||
relative_tail_fraction >= 1.0)
return NAN;
const double normalization = scaled_flux * (beta - 1.0) / (pi * alpha * alpha);
const double relative_tail = fmin(psf_relative_tail_fraction,
const double relative_tail = fmin(relative_tail_fraction,
psf_tail_absolute_hdr_budget / scaled_flux);
const double tail_radius = alpha * sqrt(pow(relative_tail,
1.0 / (1.0 - beta)) - 1.0);
@@ -190,13 +193,17 @@ void psf_kernel_cache_destroy(PsfKernelCache *cache)
*cache = (PsfKernelCache){0};
}
int psf_kernel_cache_init(PsfKernelCache *cache, const PointSpreadFunction *psf)
int psf_kernel_cache_init(PsfKernelCache *cache, const PointSpreadFunction *psf,
double relative_tail_fraction)
{
if (cache == NULL || !valid_psf(psf))
if (cache == NULL || !valid_psf(psf) ||
!isfinite(relative_tail_fraction) || relative_tail_fraction <= 0.0 ||
relative_tail_fraction >= 1.0)
return -1;
psf_kernel_cache_destroy(cache);
const double alpha = moffat_alpha(psf);
const double requested_radius = moffat_support_radius(alpha, psf->moffat_beta, 1.0);
const double requested_radius = moffat_support_radius(
alpha, psf->moffat_beta, 1.0, relative_tail_fraction);
if (!isfinite(requested_radius) || requested_radius <= 0.0)
return -1;
/* The ordinary cache must cover the support implied by this invocation's
@@ -221,6 +228,7 @@ int psf_kernel_cache_init(PsfKernelCache *cache, const PointSpreadFunction *psf)
PsfKernelCache building = {.fwhm_pixels = psf->fwhm_pixels,
.moffat_beta = psf->moffat_beta,
.alpha_pixels = alpha,
.relative_tail_fraction = relative_tail_fraction,
.max_radius_pixels = radius,
.weights = weights,
.phase_resolution = PSF_PHASE_RESOLUTION,
@@ -274,9 +282,10 @@ void psf_kernel_cache_report_ready(const PsfKernelCache *cache, FILE *stream) {
fputs("PSF cache: disabled; using the direct evaluator.\n", stream);
return;
}
fprintf(stream, "PSF cache ready: 64x64 phases, radius %.0f px, tail abs %.0e, "
"boundary %.0e, build %.3f s\n",
cache->max_radius_pixels, psf_tail_absolute_hdr_budget,
fprintf(stream, "PSF cache ready: 64x64 phases, radius %.0f px, relative tail %.0e, "
"tail abs %.0e, boundary %.0e, build %.3f s\n",
cache->max_radius_pixels, cache->relative_tail_fraction,
psf_tail_absolute_hdr_budget,
psf_boundary_hdr_budget, cache->build_seconds);
}
@@ -300,14 +309,16 @@ void psf_kernel_cache_report(const PsfKernelCache *cache,
void splat_moffat_direct(double *hdr, int width, int height, double x, double y,
LinearRgb color, double flux,
const PointSpreadFunction *psf)
const PointSpreadFunction *psf,
double relative_tail_fraction)
{
if (hdr == NULL || width <= 0 || height <= 0 || flux <= 0.0 || !valid_psf(psf))
return;
const double alpha = moffat_alpha(psf);
if (!isfinite(flux))
return;
const double support_radius = moffat_support_radius(alpha, psf->moffat_beta, flux);
const double support_radius = moffat_support_radius(
alpha, psf->moffat_beta, flux, relative_tail_fraction);
const int min_x = fmax(0.0, floor(x - support_radius));
const int max_x = fmin((double)width - 1.0, ceil(x + support_radius));
const int min_y = fmax(0.0, floor(y - support_radius));
@@ -331,17 +342,21 @@ int splat_moffat_cached(double *hdr, int width, int height, double x, double y,
LinearRgb color, double flux,
const PointSpreadFunction *psf,
const PsfKernelCache *cache,
double max_cache_psf_flux)
double max_cache_psf_flux,
double relative_tail_fraction)
{
if (hdr == NULL || width <= 0 || height <= 0 || flux <= 0.0 || !valid_psf(psf))
return 1;
const double alpha = moffat_alpha(psf);
const double support_radius = moffat_support_radius(alpha, psf->moffat_beta,
flux);
const double support_radius = moffat_support_radius(
alpha, psf->moffat_beta, flux, relative_tail_fraction);
if (cache == NULL || !cache->ready || cache->fwhm_pixels != psf->fwhm_pixels ||
cache->moffat_beta != psf->moffat_beta || !isfinite(support_radius) ||
cache->moffat_beta != psf->moffat_beta ||
cache->relative_tail_fraction != relative_tail_fraction ||
!isfinite(support_radius) ||
(support_radius > cache->max_radius_pixels && flux > max_cache_psf_flux)) {
splat_moffat_direct(hdr, width, height, x, y, color, flux, psf);
splat_moffat_direct(hdr, width, height, x, y, color, flux, psf,
relative_tail_fraction);
return 1;
}
const double base_x = floor(x), base_y = floor(y);
@@ -376,9 +391,11 @@ int splat_moffat_cached(double *hdr, int width, int height, double x, double y,
void splat_moffat(double *hdr, int width, int height, double x, double y,
LinearRgb color, double flux,
const PointSpreadFunction *psf)
const PointSpreadFunction *psf,
double relative_tail_fraction)
{
splat_moffat_direct(hdr, width, height, x, y, color, flux, psf);
splat_moffat_direct(hdr, width, height, x, y, color, flux, psf,
relative_tail_fraction);
}
static unsigned char tonemap_channel(double hdr_value)
+11 -6
View File
@@ -10,10 +10,11 @@ typedef struct {
double moffat_beta;
} PointSpreadFunction;
/* One immutable process-wide kernel for the one PSF parameter pair accepted
* by the current renderer. Its storage remains private to optics.c. */
/* One immutable process-wide kernel for the one PSF parameter/tail-policy pair
* accepted by the current renderer. Its storage remains private to optics.c. */
typedef struct {
double fwhm_pixels, moffat_beta, alpha_pixels;
double relative_tail_fraction;
double max_radius_pixels, build_seconds;
float *weights;
int phase_resolution, radius_pixels;
@@ -34,7 +35,8 @@ typedef struct {
* (W m^-2 sr^-1), before catalog amplitude and display exposure. */
LinearRgb blackbody_to_linear_rgb(double temperature_K);
int psf_kernel_cache_init(PsfKernelCache *cache,
const PointSpreadFunction *psf);
const PointSpreadFunction *psf,
double relative_tail_fraction);
void psf_kernel_cache_destroy(PsfKernelCache *cache);
void psf_kernel_cache_report_ready(const PsfKernelCache *cache, FILE *stream);
void psf_kernel_cache_report(const PsfKernelCache *cache,
@@ -43,10 +45,12 @@ void psf_kernel_cache_report(const PsfKernelCache *cache,
* budgets as the cache-aware renderer. */
void splat_moffat_direct(double *hdr, int width, int height, double x, double y,
LinearRgb color, double flux,
const PointSpreadFunction *psf);
const PointSpreadFunction *psf,
double relative_tail_fraction);
void splat_moffat(double *hdr, int width, int height, double x, double y,
LinearRgb color, double flux,
const PointSpreadFunction *psf);
const PointSpreadFunction *psf,
double relative_tail_fraction);
/* Returns 0 for a complete cached splat, 1 for the direct reference fallback,
* and 2 when the cached core was used with its outer wing intentionally
* clipped. max_cache_psf_flux == 1 preserves the historical behavior. */
@@ -54,7 +58,8 @@ int splat_moffat_cached(double *hdr, int width, int height, double x, double y,
LinearRgb color, double flux,
const PointSpreadFunction *psf,
const PsfKernelCache *cache,
double max_cache_psf_flux);
double max_cache_psf_flux,
double relative_tail_fraction);
/* Writes PNG when built with libpng; non-libpng builds use PPM fallback. */
int write_tonemapped_image(const char *path, const double *hdr, int width,
int height);
+25 -11
View File
@@ -60,6 +60,7 @@ static int mesh_has_same_winding_shared_edge(const FrameLensMesh *mesh) {
int main(void) {
const int width = 100, height = 100;
const double test_exposure = 1e-3;
const double psf_relative_tail = 1e-8;
const PointSpreadFunction psf = {.fwhm_pixels = 2.7, .moffat_beta = 4.5};
const GeodesicTraceConfig trace = {.coordinate_time_step = 0.25,
.max_steps = 100};
@@ -77,7 +78,8 @@ int main(void) {
goto done;
const size_t images =
frame_splat_catalog(&mesh, &catalog, hdr, width, height, test_exposure,
&psf, NULL, INFINITY, 1.0, 0, 1, NULL, NULL, NULL);
&psf, NULL, INFINITY, 1.0, psf_relative_tail,
0, 1, NULL, NULL, NULL);
if (images != 1 || hdr[3 * (50 * width + 50)] <= 0.0) {
fputs("flat-space inverse lens-map regression failed\n", stderr);
goto done;
@@ -95,11 +97,11 @@ int main(void) {
omp_set_num_threads(1);
const size_t serial_images = frame_splat_catalog(
&mesh, &catalog, serial_hdr, width, height, test_exposure, &psf, NULL,
INFINITY, 1.0, 0, 1, NULL, NULL, NULL);
INFINITY, 1.0, psf_relative_tail, 0, 1, NULL, NULL, NULL);
omp_set_num_threads(4);
const size_t parallel_images = frame_splat_catalog(
&mesh, &catalog, parallel_hdr, width, height, test_exposure, &psf, NULL,
INFINITY, 1.0, 0, 1, NULL, NULL, NULL);
INFINITY, 1.0, psf_relative_tail, 0, 1, NULL, NULL, NULL);
omp_set_num_threads(original_threads);
for (int value = 0; value < width * height * 3; ++value)
if (fabs(serial_hdr[value] - parallel_hdr[value]) >
@@ -126,7 +128,7 @@ int main(void) {
* beyond the former Gaussian's 3-sigma raster box. */
memset(hdr, 0, (size_t)width * height * 3 * sizeof *hdr);
splat_moffat(hdr, width, height, 50.5, 50.5,
(LinearRgb){1.0, 1.0, 1.0}, 1.0, &psf);
(LinearRgb){1.0, 1.0, 1.0}, 1.0, &psf, psf_relative_tail);
double moffat_flux = 0.0;
for (int pixel = 0; pixel < width * height; ++pixel)
moffat_flux += hdr[3 * pixel];
@@ -142,27 +144,37 @@ int main(void) {
double *cached_hdr = calloc((size_t)width * height * 3, sizeof *cached_hdr);
double *reference_hdr = calloc((size_t)width * height * 3, sizeof *reference_hdr);
if (cached_hdr == NULL || reference_hdr == NULL ||
psf_kernel_cache_init(&cache, &psf)) {
psf_kernel_cache_init(&cache, &psf, psf_relative_tail)) {
free(cached_hdr);
free(reference_hdr);
psf_kernel_cache_destroy(&cache);
fputs("PSF cache construction regression failed\n", stderr);
goto done;
}
PsfKernelCache loose_tail_cache = {0};
if (psf_kernel_cache_init(&loose_tail_cache, &psf, 1e-5) ||
loose_tail_cache.relative_tail_fraction != 1e-5 ||
loose_tail_cache.radius_pixels >= cache.radius_pixels) {
fputs("PSF relative-tail cache-radius regression failed\n", stderr);
psf_kernel_cache_destroy(&loose_tail_cache);
free(cached_hdr); free(reference_hdr); psf_kernel_cache_destroy(&cache);
goto done;
}
psf_kernel_cache_destroy(&loose_tail_cache);
const double phases[][2] = {{0.01, 0.99}, {0.499, 0.501}, {0.999, 0.001}};
for (size_t phase = 0; phase < sizeof phases / sizeof *phases; ++phase) {
memset(cached_hdr, 0, (size_t)width * height * 3 * sizeof *cached_hdr);
memset(reference_hdr, 0, (size_t)width * height * 3 * sizeof *reference_hdr);
if (splat_moffat_cached(cached_hdr, width, height, 50.0 + phases[phase][0],
50.0 + phases[phase][1], (LinearRgb){1.0, 1.0, 1.0},
1.0, &psf, &cache, 1.0) != 0) {
1.0, &psf, &cache, 1.0, psf_relative_tail) != 0) {
fputs("ordinary PSF cache unexpectedly fell back\n", stderr);
free(cached_hdr); free(reference_hdr); psf_kernel_cache_destroy(&cache);
goto done;
}
splat_moffat_direct(reference_hdr, width, height,
50.0 + phases[phase][0], 50.0 + phases[phase][1],
(LinearRgb){1.0, 1.0, 1.0}, 1.0, &psf);
(LinearRgb){1.0, 1.0, 1.0}, 1.0, &psf, psf_relative_tail);
double peak = 0.0, max_error = 0.0;
for (int value = 0; value < width * height * 3; ++value) {
peak = fmax(peak, reference_hdr[value]);
@@ -177,7 +189,8 @@ int main(void) {
/* A bright event must avoid a cached hard cutoff by selecting the direct
* reference path when the requested support exceeds the cache. */
if (splat_moffat_cached(cached_hdr, width, height, 50.5, 50.5,
(LinearRgb){1.0, 1.0, 1.0}, 1000.0, &psf, &cache, 1.0) != 1) {
(LinearRgb){1.0, 1.0, 1.0}, 1000.0, &psf, &cache, 1.0,
psf_relative_tail) != 1) {
fputs("bright PSF direct-fallback regression failed\n", stderr);
free(cached_hdr); free(reference_hdr); psf_kernel_cache_destroy(&cache);
goto done;
@@ -186,13 +199,13 @@ int main(void) {
memset(reference_hdr, 0, (size_t)width * height * 3 * sizeof *reference_hdr);
if (splat_moffat_cached(cached_hdr, width, height, 50.5, 50.5,
(LinearRgb){1.0, 1.0, 1.0}, 1000.0, &psf, &cache,
1000.0) != 2) {
1000.0, psf_relative_tail) != 2) {
fputs("bright PSF cached-wing-clipping regression failed\n", stderr);
free(cached_hdr); free(reference_hdr); psf_kernel_cache_destroy(&cache);
goto done;
}
splat_moffat_direct(reference_hdr, width, height, 50.5, 50.5,
(LinearRgb){1.0, 1.0, 1.0}, 1000.0, &psf);
(LinearRgb){1.0, 1.0, 1.0}, 1000.0, &psf, psf_relative_tail);
const size_t center = 3 * (50 * width + 50);
if (cached_hdr[center] <= 0.0 ||
fabs(cached_hdr[center] - reference_hdr[center]) >
@@ -223,7 +236,8 @@ int main(void) {
if (frame_lens_mesh_build_coarse(&fine_mesh, width, height, 1, 0.1) ||
frame_lens_mesh_trace(&fine_mesh, &spacetime, &observer, &trace) ||
frame_splat_catalog(&fine_mesh, &fine_catalog, hdr, width, height,
test_exposure, &psf, NULL, INFINITY, 1.0, 0, 1, NULL,
test_exposure, &psf, NULL, INFINITY, 1.0,
psf_relative_tail, 0, 1, NULL,
NULL, NULL) != 1) {
fputs("fine source-triangle containment regression failed\n", stderr);
frame_lens_mesh_destroy(&fine_mesh);