Feat: Add --fast-mode supersampled point-source accumulation
Add an optional CPU preview path that deposits each point-source image as a supersampled delta and resolves the whole frame with one global Moffat convolution plus an N x N box average, instead of splatting a per-event PSF. - optics: FastPsfAccumulator builds the pixel-area-integral kernel of the target Moffat at the supersampled scale (width N*alpha, same beta). The 1/N^2 box average then reproduces the final pixel-area integral, so the requested FWHM and beta are preserved without renormalisation. Deposits are per-cell atomic adds; resolve accumulates into the caller's HDR buffer. - frame: fast branch in frame_splat_catalog with one shared supersampled buffer and a single resolve per frame; the accumulator is reused across movie frames and built from the map dimensions on lens-map import. - main: --fast-mode, --fast-supersample N (1..8, default 2) and --fast-deposit nearest|bilinear (default nearest). CPU-only and rejected in the HIP/dummy backends; --psf-min-y still applies per event while --max-cache-psf-flux does not. - The deposition scheme was chosen by scripts/fast_mode_deposit_error.py: nearest keeps the PSF shape exactly with <= 0.5/N px position quantization; bilinear keeps the exact centroid but broadens FWHM and beta. Recorded in benchmarks/fast_mode_deposit_2026-09-18.md. - tests/test_frame.c covers fast nearest vs the direct evaluator at the snapped centre, flux conservation, bilinear centroid, min-Y discard, frame plumbing, and HDR accumulation onto a non-zero background. - benchmarks/fast_mode_cpu_2026-09-18.md records a ~10x speedup on the 2MASS galactic-centre field with small tone-mapped differences.
This commit is contained in:
1 parent
7ddc58515a
commit
3deebfb2fa
13 files changed
+1249
-36
No files matched your search
+2
-2
@@ -119,7 +119,7 @@ int main(int argc, char **argv) {
|
||||
#pragma omp for schedule(dynamic,1)
|
||||
for(size_t t=first;t<last;++t) {
|
||||
CatalogSplatStats result=splat_catalog_triangles(&map.frames[0].mesh,&catalog,NULL,
|
||||
map.width,map.height,exposure,&psf,&cache,1e6,max_flux,1e-8,min_y,t,t+1,&local);
|
||||
map.width,map.height,exposure,&psf,&cache,1e6,max_flux,1e-8,min_y,t,t+1,&local,NULL);
|
||||
failed|=result.failed;
|
||||
}
|
||||
for(int k=0;k<4;++k)totals[k]+=classified[k];
|
||||
@@ -134,7 +134,7 @@ int main(int argc, char **argv) {
|
||||
PsfEventSink sink={.cache=&cache};
|
||||
double start=omp_get_wtime();
|
||||
CatalogSplatStats stats=splat_catalog_triangles(&map.frames[0].mesh,&catalog,NULL,
|
||||
map.width,map.height,exposure,&psf,&cache,1e6,max_flux,1e-8,min_y,first,last,&sink);
|
||||
map.width,map.height,exposure,&psf,&cache,1e6,max_flux,1e-8,min_y,first,last,&sink,NULL);
|
||||
printf("DIAGNOSTIC ONLY, no HDR: workers=1 catalog=%s stars=%zu triangles=[%zu,%zu) emitting_triangles=%zu last_emitting_triangle=%zu cached=%zu wing=%zu direct=%zu discarded=%zu events=%zu capped=%d producer_wall=%.9f digest=%016llx\n",
|
||||
kind,catalog.count,first,last,triangles_seen,last_triangle,classified[0]+classified[2],classified[2],classified[1],classified[3],captured_count,frame_psf_diagnostic_stopped(),omp_get_wtime()-start,(unsigned long long)digest);
|
||||
printf("checksum=%016llx\n",(unsigned long long)checksum);
|
||||
|
||||
+141
-7
@@ -82,7 +82,7 @@ int main(void) {
|
||||
const size_t images =
|
||||
frame_splat_catalog(&mesh, &catalog, hdr, width, height, test_exposure,
|
||||
&psf, NULL, INFINITY, 1.0, psf_relative_tail,
|
||||
0.0, 0, 1, NULL, NULL, NULL);
|
||||
0.0, 0, 1, NULL, 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;
|
||||
@@ -104,7 +104,7 @@ int main(void) {
|
||||
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) {
|
||||
1.0, psf_relative_tail, 0.0, 0, 1, NULL, 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;
|
||||
@@ -140,7 +140,7 @@ int main(void) {
|
||||
PsfSplatStats min_y_stats = {0};
|
||||
if (frame_splat_catalog(&mesh, &catalog, hdr, width, height, test_exposure,
|
||||
&psf, NULL, INFINITY, 1.0, psf_relative_tail,
|
||||
1e300, 0, 1, NULL, &min_y_stats, NULL) != 1 ||
|
||||
1e300, 0, 1, NULL, &min_y_stats, NULL, NULL) != 1 ||
|
||||
min_y_stats.discarded_below_min_y != 1 ||
|
||||
hdr[3 * (50 * width + 50)] != 0.0) {
|
||||
fputs("PSF minimum-Y discard regression failed\n", stderr);
|
||||
@@ -159,11 +159,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, psf_relative_tail, 0.0, 0, 1, NULL, NULL, NULL);
|
||||
INFINITY, 1.0, psf_relative_tail, 0.0, 0, 1, NULL, 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, psf_relative_tail, 0.0, 0, 1, NULL, NULL, NULL);
|
||||
INFINITY, 1.0, psf_relative_tail, 0.0, 0, 1, NULL, 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]) >
|
||||
@@ -304,6 +304,140 @@ int main(void) {
|
||||
free(cached_hdr);
|
||||
free(reference_hdr);
|
||||
psf_kernel_cache_destroy(&cache);
|
||||
/* Fast mode: a nearest deposit must reproduce the current pixel-integrated
|
||||
* Moffat at the snapped supersampled centre, preserve total flux, and honour
|
||||
* the min-Y discard rule; bilinear deposition must preserve the centroid. */
|
||||
{
|
||||
int fast_ok = 1;
|
||||
const int supersample = 2;
|
||||
FastPsfAccumulator fast = {0};
|
||||
FastPsfAccumulator bilinear = {0};
|
||||
FastPsfAccumulator min_y_fast = {0};
|
||||
FastPsfAccumulator accumulation = {0};
|
||||
double *fast_hdr = calloc((size_t)width * height * 3, sizeof *fast_hdr);
|
||||
double *direct_hdr = calloc((size_t)width * height * 3, sizeof *direct_hdr);
|
||||
double *background_hdr =
|
||||
calloc((size_t)width * height * 3, sizeof *background_hdr);
|
||||
if (fast_hdr == NULL || direct_hdr == NULL || background_hdr == NULL ||
|
||||
fast_psf_accumulator_init(&fast, width, height, supersample,
|
||||
FAST_PSF_DEPOSIT_NEAREST, &psf,
|
||||
psf_relative_tail, 0.0, 1) ||
|
||||
fast_psf_accumulator_init(&bilinear, width, height, supersample,
|
||||
FAST_PSF_DEPOSIT_BILINEAR, &psf,
|
||||
psf_relative_tail, 0.0, 1) ||
|
||||
fast_psf_accumulator_init(&min_y_fast, width, height, supersample,
|
||||
FAST_PSF_DEPOSIT_NEAREST, &psf,
|
||||
psf_relative_tail, 0.5, 1) ||
|
||||
fast_psf_accumulator_init(&accumulation, width, height, supersample,
|
||||
FAST_PSF_DEPOSIT_NEAREST, &psf,
|
||||
psf_relative_tail, 0.0, 1)) {
|
||||
fputs("fast-mode accumulator construction regression failed\n", stderr);
|
||||
fast_ok = 0;
|
||||
goto fast_done;
|
||||
}
|
||||
/* (50.2, 50.2) snaps to supersampled cell 100, centre (100.5, 100.5) in
|
||||
* ss coordinates, i.e. final position (50.25, 50.25). */
|
||||
if (fast_psf_accumulator_deposit(&fast, 50.2, 50.2,
|
||||
(LinearRgb){1.0, 1.0, 1.0}, 1.0) == 3 ||
|
||||
fast_psf_accumulator_resolve(&fast, fast_hdr, 4)) {
|
||||
fputs("fast-mode nearest deposit regression failed\n", stderr);
|
||||
fast_ok = 0;
|
||||
goto fast_done;
|
||||
}
|
||||
splat_moffat_direct(direct_hdr, width, height, 50.25, 50.25,
|
||||
(LinearRgb){1.0, 1.0, 1.0}, 1.0, &psf,
|
||||
psf_relative_tail, 0.0);
|
||||
double peak = 0.0, max_error = 0.0, fast_flux = 0.0, direct_flux = 0.0;
|
||||
for (int value = 0; value < width * height * 3; ++value) {
|
||||
peak = fmax(peak, direct_hdr[value]);
|
||||
max_error = fmax(max_error, fabs(fast_hdr[value] - direct_hdr[value]));
|
||||
fast_flux += fast_hdr[value];
|
||||
direct_flux += direct_hdr[value];
|
||||
}
|
||||
if (!(peak > 0.0) || max_error > 1e-4 * peak ||
|
||||
fabs(fast_flux - direct_flux) > 1e-4) {
|
||||
fputs("fast-mode nearest semantics regression failed\n", stderr);
|
||||
fast_ok = 0;
|
||||
goto fast_done;
|
||||
}
|
||||
/* Bilinear keeps the exact continuous centroid. */
|
||||
memset(fast_hdr, 0, (size_t)width * height * 3 * sizeof *fast_hdr);
|
||||
if (fast_psf_accumulator_deposit(&bilinear, 50.37, 50.62,
|
||||
(LinearRgb){1.0, 1.0, 1.0}, 1.0) == 3 ||
|
||||
fast_psf_accumulator_resolve(&bilinear, fast_hdr, 4)) {
|
||||
fputs("fast-mode bilinear deposit regression failed\n", stderr);
|
||||
fast_ok = 0;
|
||||
goto fast_done;
|
||||
}
|
||||
double weight_sum = 0.0, cx = 0.0, cy = 0.0;
|
||||
for (int row = 0; row < height; ++row)
|
||||
for (int column = 0; column < width; ++column) {
|
||||
const double weight = fast_hdr[3 * (row * width + column)];
|
||||
weight_sum += weight;
|
||||
cx += weight * (column + 0.5);
|
||||
cy += weight * (row + 0.5);
|
||||
}
|
||||
if (!(weight_sum > 0.0) ||
|
||||
hypot(cx / weight_sum - 50.37, cy / weight_sum - 50.62) > 1e-6) {
|
||||
fputs("fast-mode bilinear centroid regression failed\n", stderr);
|
||||
fast_ok = 0;
|
||||
goto fast_done;
|
||||
}
|
||||
/* The min-Y cutoff discards an event whose peak luminance is below it. */
|
||||
if (fast_psf_accumulator_deposit(&fast, 10.5, 10.5,
|
||||
(LinearRgb){1.0, 1.0, 1.0}, 1.0) != 0 ||
|
||||
fast_psf_accumulator_deposit(&min_y_fast, 10.5, 10.5,
|
||||
(LinearRgb){1.0, 1.0, 1.0}, 1.0) != 3) {
|
||||
fputs("fast-mode min-Y discard regression failed\n", stderr);
|
||||
fast_ok = 0;
|
||||
goto fast_done;
|
||||
}
|
||||
/* End-to-end plumbing through the frame splat path. */
|
||||
memset(fast_hdr, 0, (size_t)width * height * 3 * sizeof *fast_hdr);
|
||||
PsfSplatStats fast_stats = {0};
|
||||
const size_t fast_images = frame_splat_catalog(
|
||||
&mesh, &catalog, fast_hdr, width, height, test_exposure, &psf, NULL,
|
||||
INFINITY, 1.0, psf_relative_tail, 0.0, 0, 1, NULL, &fast_stats, NULL,
|
||||
&fast);
|
||||
if (fast_images != 1 || fast_stats.discarded_below_min_y != 0) {
|
||||
fputs("fast-mode frame splat regression failed\n", stderr);
|
||||
fast_ok = 0;
|
||||
goto fast_done;
|
||||
}
|
||||
/* HDR accumulation semantics: resolve must add onto an existing
|
||||
* background, not overwrite it. A prefilled buffer plus one deposit must
|
||||
* preserve the far-field background exactly and add the PSF core. */
|
||||
for (int value = 0; value < width * height * 3; ++value)
|
||||
background_hdr[value] = 0.25;
|
||||
if (fast_psf_accumulator_deposit(&accumulation, 10.5, 10.5,
|
||||
(LinearRgb){1.0, 1.0, 1.0}, 1.0) == 3 ||
|
||||
fast_psf_accumulator_resolve(&accumulation, background_hdr, 4)) {
|
||||
fputs("fast-mode HDR accumulation regression failed\n", stderr);
|
||||
fast_ok = 0;
|
||||
goto fast_done;
|
||||
}
|
||||
/* (90, 90) is far outside the kernel support of a star at (10.5, 10.5). */
|
||||
if (background_hdr[3 * (90 * width + 90)] != 0.25) {
|
||||
fputs("fast-mode HDR accumulation lost the background\n", stderr);
|
||||
fast_ok = 0;
|
||||
goto fast_done;
|
||||
}
|
||||
if (!(background_hdr[3 * (10 * width + 10)] > 0.25)) {
|
||||
fputs("fast-mode HDR accumulation did not add the deposit\n", stderr);
|
||||
fast_ok = 0;
|
||||
goto fast_done;
|
||||
}
|
||||
fast_done:
|
||||
free(fast_hdr);
|
||||
free(direct_hdr);
|
||||
free(background_hdr);
|
||||
fast_psf_accumulator_destroy(&fast);
|
||||
fast_psf_accumulator_destroy(&bilinear);
|
||||
fast_psf_accumulator_destroy(&min_y_fast);
|
||||
fast_psf_accumulator_destroy(&accumulation);
|
||||
if (!fast_ok)
|
||||
goto done;
|
||||
}
|
||||
frame_draw_mesh(&mesh, hdr, width, height, 0.5, 0.5);
|
||||
if (hdr[3 * (10 * width + 20)] != 0.25) {
|
||||
fputs("mesh diagnostic overlay regression failed\n", stderr);
|
||||
@@ -325,7 +459,7 @@ int main(void) {
|
||||
frame_splat_catalog(&fine_mesh, &fine_catalog, hdr, width, height,
|
||||
test_exposure, &psf, NULL, INFINITY, 1.0,
|
||||
psf_relative_tail, 0.0, 0, 1, NULL,
|
||||
NULL, NULL) != 1) {
|
||||
NULL, NULL, NULL) != 1) {
|
||||
fputs("fine source-triangle containment regression failed\n", stderr);
|
||||
frame_lens_mesh_destroy(&fine_mesh);
|
||||
goto done;
|
||||
@@ -369,7 +503,7 @@ int main(void) {
|
||||
memset(hdr, 0, (size_t)width * height * 3 * sizeof *hdr);
|
||||
if (frame_splat_catalog(&thin_mesh, &thin_catalog, hdr, width, height,
|
||||
test_exposure, &psf, NULL, 1.0, 1.0,
|
||||
psf_relative_tail, 0.0, 0, 1, NULL, NULL, NULL) != 1 ||
|
||||
psf_relative_tail, 0.0, 0, 1, NULL, NULL, NULL, NULL) != 1 ||
|
||||
hdr[3 * (43 * width + 43)] <= 0.0) {
|
||||
fputs("thin source-triangle inverse-map regression failed\n", stderr);
|
||||
goto done;
|
||||
|
||||
Reference in new issue
Block a user