79 lines
3.1 KiB
Python
79 lines
3.1 KiB
Python
#!/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())
|