diff --git a/Makefile b/Makefile index c7c3050..45d7e30 100644 --- a/Makefile +++ b/Makefile @@ -123,3 +123,4 @@ clean: rm -rf build -include $(RENDER_DEPS) +include mk/reference_images.mk diff --git a/benchmarks/psf_event_sink_reference.md b/benchmarks/psf_event_sink_reference.md new file mode 100644 index 0000000..b4405d7 --- /dev/null +++ b/benchmarks/psf_event_sink_reference.md @@ -0,0 +1,70 @@ +# PSF event-sink image references + +Run date: 2026-09-04 + +Purpose: fixed pixel-comparison references before replacing the sink consumer +with HIP. The renderer used the default CPU `PSF_EVENT_SINK=1` path. There +was no adaptive refinement. The catalog is the repository test catalog, not +an all-sky tile snapshot. This is not a performance benchmark. + +Environment: + +- Git `HEAD`: `ab56e23` (worktree contains the uncommitted event-sink change) +- Build: `BUILD_TYPE=Release`, `ENABLE_HDR=1`, `ENABLE_PNG=1`, OpenMP +- Host worker setting: `OMP_NUM_THREADS=16` +- Images: 640 x 360 px; Moffat FWHM 2.7 px, beta 4.5 +- Observer: radius 30, inward speed 0; ICRS look direction RA 1 deg, Dec 1 deg + +The explicit `1e300` magnification cap is the practical command-line spelling +of the normal unlimited default. `--draw-mesh` and `--psf-direct` are absent, +therefore disabled. All refinement thresholds are included even though level +zero prevents refinement, so later default changes cannot affect this run. + +## Minkowski + +Build: + +```bash +make -B SPACETIME=minkowski ENABLE_HDR=1 PSF_EVENT_SINK=1 backend +``` + +Render: + +```bash +OMP_NUM_THREADS=16 build/Release/minkowski_sky --catalog assets/sky_grid_5deg.csv --output tests/data/psf_event_sink_reference/minkowski_ra1_dec1_640x360.png --hdr-output --width 640 --height 360 --fov-deg 30 --look-ra-deg 1 --look-dec-deg 1 --exposure 0.1 --observer-radius 30 --observer-inward-speed 0 --psf-fwhm-pixels 2.7 --psf-moffat-beta 4.5 --max-magnification 1e300 --max-cache-psf-flux 1 --psf-relative-tail 1e-8 --psf-min-y 0 --coarse-cell-pixels 16 --refine-max-level 0 --refine-angle-abs-deg 0.001 --refine-angle-rel 0.1 --refine-jacobian-min 1e-3 --refine-min-edge-pixels 0.5 --refine-min-area-pixels2 0.25 --catalog-load-workers 4 +``` + +Result: 35 images from 5,654 catalog stars; 35 cached, 0 wing-clipped, 0 direct +fallback, 0 min-Y discarded. + +Versioned reference outputs: + +- `tests/data/psf_event_sink_reference/minkowski_ra1_dec1_640x360.png` +- `tests/data/psf_event_sink_reference/minkowski_ra1_dec1_640x360_HDR.fits` + +## Analytic Schwarzschild + +This reference uses a 60 deg horizontal field of view. At radius 30, the +former 30 deg framing mostly contained the shadow and high-order images inside +the Einstein ring, without enough field to test the first-image star field. + +Build: + +```bash +make -B SPACETIME=schwarzschild ENABLE_HDR=1 PSF_EVENT_SINK=1 backend +``` + +Render: + +```bash +OMP_NUM_THREADS=16 build/Release/schwarzschild_sky --catalog assets/sky_grid_5deg.csv --output tests/data/psf_event_sink_reference/schwarzschild_ra1_dec1_fov60_640x360.png --hdr-output --width 640 --height 360 --fov-deg 60 --look-ra-deg 1 --look-dec-deg 1 --exposure 0.1 --observer-radius 30 --observer-inward-speed 0 --psf-fwhm-pixels 2.7 --psf-moffat-beta 4.5 --max-magnification 1e300 --max-cache-psf-flux 1 --psf-relative-tail 1e-8 --psf-min-y 0 --coarse-cell-pixels 16 --refine-max-level 0 --refine-angle-abs-deg 0.001 --refine-angle-rel 0.1 --refine-jacobian-min 1e-3 --refine-min-edge-pixels 0.5 --refine-min-area-pixels2 0.25 --catalog-load-workers 4 +``` + +Result: 5,206 images from 5,654 catalog stars; 5,206 cached, 0 wing-clipped, +0 direct fallback, 0 min-Y discarded. This is a visual regression reference, +not a performance result. + +Versioned reference outputs: + +- `tests/data/psf_event_sink_reference/schwarzschild_ra1_dec1_fov60_640x360.png` +- `tests/data/psf_event_sink_reference/schwarzschild_ra1_dec1_fov60_640x360_HDR.fits` diff --git a/mk/reference_images.mk b/mk/reference_images.mk new file mode 100644 index 0000000..ee6be30 --- /dev/null +++ b/mk/reference_images.mk @@ -0,0 +1,16 @@ +REFERENCE_DIR := tests/data/psf_event_sink_reference +REFERENCE_TMP_DIR := /tmp/gr_psf_event_sink_reference +FLOATDIFF_SCRIPT := scripts/fits_floatdiff.py + +.PHONY: test-reference-images + +test: test-reference-images + +test-reference-images: $(FLOATDIFF_SCRIPT) $(REFERENCE_DIR)/minkowski_ra1_dec1_640x360_HDR.fits $(REFERENCE_DIR)/schwarzschild_ra1_dec1_fov60_640x360_HDR.fits + $(MAKE) SPACETIME=minkowski ENABLE_HDR=1 backend + mkdir -p $(REFERENCE_TMP_DIR) + OMP_NUM_THREADS=16 $(BUILD_DIR)/minkowski_sky --catalog assets/sky_grid_5deg.csv --output $(REFERENCE_TMP_DIR)/minkowski_ra1_dec1_640x360.png --hdr-output --width 640 --height 360 --fov-deg 30 --look-ra-deg 1 --look-dec-deg 1 --exposure 0.1 --observer-radius 30 --observer-inward-speed 0 --psf-fwhm-pixels 2.7 --psf-moffat-beta 4.5 --max-magnification 1e300 --max-cache-psf-flux 1 --psf-relative-tail 1e-8 --psf-min-y 0 --coarse-cell-pixels 16 --refine-max-level 0 --refine-angle-abs-deg 0.001 --refine-angle-rel 0.1 --refine-jacobian-min 1e-3 --refine-min-edge-pixels 0.5 --refine-min-area-pixels2 0.25 --catalog-load-workers 4 + python3 $(FLOATDIFF_SCRIPT) $(REFERENCE_DIR)/minkowski_ra1_dec1_640x360_HDR.fits $(REFERENCE_TMP_DIR)/minkowski_ra1_dec1_640x360_HDR.fits + $(MAKE) SPACETIME=schwarzschild ENABLE_HDR=1 backend + OMP_NUM_THREADS=16 $(BUILD_DIR)/schwarzschild_sky --catalog assets/sky_grid_5deg.csv --output $(REFERENCE_TMP_DIR)/schwarzschild_ra1_dec1_fov60_640x360.png --hdr-output --width 640 --height 360 --fov-deg 60 --look-ra-deg 1 --look-dec-deg 1 --exposure 0.1 --observer-radius 30 --observer-inward-speed 0 --psf-fwhm-pixels 2.7 --psf-moffat-beta 4.5 --max-magnification 1e300 --max-cache-psf-flux 1 --psf-relative-tail 1e-8 --psf-min-y 0 --coarse-cell-pixels 16 --refine-max-level 0 --refine-angle-abs-deg 0.001 --refine-angle-rel 0.1 --refine-jacobian-min 1e-3 --refine-min-edge-pixels 0.5 --refine-min-area-pixels2 0.25 --catalog-load-workers 4 + python3 $(FLOATDIFF_SCRIPT) $(REFERENCE_DIR)/schwarzschild_ra1_dec1_fov60_640x360_HDR.fits $(REFERENCE_TMP_DIR)/schwarzschild_ra1_dec1_fov60_640x360_HDR.fits diff --git a/scripts/fits_floatdiff.py b/scripts/fits_floatdiff.py new file mode 100644 index 0000000..3b73085 --- /dev/null +++ b/scripts/fits_floatdiff.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +"""Compare primary-image IEEE float values in two simple FITS files. + +The renderer writes a single 32-bit RGB primary HDU. Keeping this dependency- +free tool in the repository makes HDR reference comparisons available on the +minimal Gentoo installation as well as in CI. +""" + +import argparse +import math +import struct +import sys + + +def read_fits(path): + with open(path, "rb") as stream: + cards = [] + while True: + block = stream.read(2880) + if len(block) != 2880: + raise ValueError(f"{path}: truncated FITS header") + cards.extend(block[i:i + 80].decode("ascii") for i in range(0, 2880, 80)) + if any(card.startswith("END") for card in cards[-36:]): + break + values = {} + for card in cards: + if card[8:10] == "= ": + values[card[:8].strip()] = card[10:30].strip() + if values.get("BITPIX") != "-32" or values.get("NAXIS") != "3": + raise ValueError(f"{path}: expected a 32-bit, three-axis primary image") + shape = tuple(int(values[f"NAXIS{axis}"]) for axis in (1, 2, 3)) + count = math.prod(shape) + payload = stream.read(count * 4) + if len(payload) != count * 4: + raise ValueError(f"{path}: truncated image payload") + return shape, payload + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("reference") + parser.add_argument("candidate") + parser.add_argument("--abs-tolerance", type=float, default=0.0) + parser.add_argument("--rel-tolerance", type=float, default=0.0) + args = parser.parse_args() + if args.abs_tolerance < 0 or args.rel_tolerance < 0: + parser.error("tolerances must be non-negative") + ref_shape, ref_payload = read_fits(args.reference) + candidate_shape, candidate_payload = read_fits(args.candidate) + if ref_shape != candidate_shape: + print(f"shape mismatch: {ref_shape} != {candidate_shape}", file=sys.stderr) + return 1 + maximum_abs = maximum_rel = 0.0 + mismatch_count = 0 + worst_index = 0 + for index, (reference, candidate) in enumerate( + zip(struct.iter_unpack(">f", ref_payload), + struct.iter_unpack(">f", candidate_payload))): + reference, candidate = reference[0], candidate[0] + if not math.isfinite(reference) or not math.isfinite(candidate): + equal = reference == candidate + absolute = relative = math.inf if not equal else 0.0 + else: + absolute = abs(candidate - reference) + relative = absolute / max(abs(reference), 1.0e-30) + equal = absolute <= args.abs_tolerance or relative <= args.rel_tolerance + if absolute > maximum_abs: + maximum_abs, worst_index = absolute, index + maximum_rel = max(maximum_rel, relative) + mismatch_count += not equal + print(f"floatdiff: shape={ref_shape} samples={math.prod(ref_shape)} " + f"mismatches={mismatch_count} max_abs={maximum_abs:.9g} " + f"max_rel={maximum_rel:.9g} worst_sample={worst_index}") + return 1 if mismatch_count else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/data/psf_event_sink_reference/minkowski_ra1_dec1_640x360.png b/tests/data/psf_event_sink_reference/minkowski_ra1_dec1_640x360.png new file mode 100644 index 0000000..c76cdcf Binary files /dev/null and b/tests/data/psf_event_sink_reference/minkowski_ra1_dec1_640x360.png differ diff --git a/tests/data/psf_event_sink_reference/minkowski_ra1_dec1_640x360_HDR.fits b/tests/data/psf_event_sink_reference/minkowski_ra1_dec1_640x360_HDR.fits new file mode 100644 index 0000000..678196a Binary files /dev/null and b/tests/data/psf_event_sink_reference/minkowski_ra1_dec1_640x360_HDR.fits differ diff --git a/tests/data/psf_event_sink_reference/schwarzschild_ra1_dec1_fov60_640x360.png b/tests/data/psf_event_sink_reference/schwarzschild_ra1_dec1_fov60_640x360.png new file mode 100644 index 0000000..20094ea Binary files /dev/null and b/tests/data/psf_event_sink_reference/schwarzschild_ra1_dec1_fov60_640x360.png differ diff --git a/tests/data/psf_event_sink_reference/schwarzschild_ra1_dec1_fov60_640x360_HDR.fits b/tests/data/psf_event_sink_reference/schwarzschild_ra1_dec1_fov60_640x360_HDR.fits new file mode 100644 index 0000000..0aca09c Binary files /dev/null and b/tests/data/psf_event_sink_reference/schwarzschild_ra1_dec1_fov60_640x360_HDR.fits differ