#!/usr/bin/env python3 """test_moon_composite.py — smoke-test atmospheric overlay at three opacity levels. Generates three output images from the same east camera frame (21-07-00.jpg) to show how the atmospheric overlay looks across the quality spectrum: test_composite_clear.jpg — opacity 0.00 (quality ≥ 0.85, clear sky) test_composite_hazy.jpg — opacity 0.20 (quality ~ 0.70, light haze) test_composite_cloudy.jpg — opacity 0.65 (overcast fallback) The moon disk in each image is procedurally generated (clean grey sphere, no camera timestamp). In production it is replaced by the NASA SVS Dial-a-Moon render for the exact UTC hour east captured the moon. Run from the sky-cam directory: python3 test_moon_composite.py """ import os import pathlib import sys import tempfile HERE = pathlib.Path(__file__).resolve().parent sys.path.insert(0, str(HERE)) EAST_FRAME = HERE / '21-07-00.jpg' CASES = [ ('test_composite_clear.jpg', 0.00, 'clear sky (quality >= 0.85)'), ('test_composite_hazy.jpg', 0.20, 'light haze (quality ~ 0.70)'), ('test_composite_cloudy.jpg', 0.65, 'heavy overcast fallback'), ] def _make_procedural_moon(size: int = 2048) -> 'Image': """Clean grey disc with simplified lunar maria and limb darkening.""" from PIL import Image, ImageDraw, ImageFilter import numpy as np img = Image.new('RGB', (size, size), (0, 0, 0)) draw = ImageDraw.Draw(img) cx = cy = size // 2 r = int(size * 0.47) draw.ellipse([cx - r, cy - r, cx + r, cy + r], fill=(218, 214, 200)) draw.ellipse([cx - r//3, cy - r//3, cx + r//6, cy + r//5], fill=(170, 167, 154)) draw.ellipse([cx + r//8, cy - r//5, cx + r//3, cy + r//8], fill=(182, 179, 166)) draw.ellipse([cx - r//4, cy + r//6, cx + r//8, cy + r//3], fill=(175, 172, 159)) draw.ellipse([cx - r//2, cy - r//10, cx - r//5, cy + r//4], fill=(185, 182, 169)) img = img.filter(ImageFilter.GaussianBlur(radius=size // 80)) vignette = Image.new('L', (size, size), 0) vd = ImageDraw.Draw(vignette) vd.ellipse([cx - r, cy - r, cx + r, cy + r], fill=255) vignette = vignette.filter(ImageFilter.GaussianBlur(radius=size // 30)) arr = np.asarray(img).astype(float) vig = np.asarray(vignette).astype(float) / 255.0 arr = np.clip(arr * (0.75 + 0.25 * vig)[..., None], 0, 255).astype('uint8') return Image.fromarray(arr) def main(): if not EAST_FRAME.exists(): print(f'ERROR: missing {EAST_FRAME}', file=sys.stderr) sys.exit(1) print('generating procedural moon disc …') moon_img = _make_procedural_moon(2048) tmp = tempfile.NamedTemporaryFile(suffix='.png', delete=False) tmp_moon = tmp.name tmp.close() moon_img.save(tmp_moon) from moon_composite import render_phase_closeup try: for filename, opacity, label in CASES: out = HERE / filename caption = ( f'Full Moon — April 2026 — ' f'sky-cam east 2026-04-29 21:07 UTC — ' f'NASA SVS Dial-a-Moon [{label}]' ) print(f'rendering {filename} (opacity={opacity:.2f}) {label} …') render_phase_closeup( nasa_render_path=tmp_moon, out_path=str(out), output_size=(1920, 1080), moon_height_pct=0.88, caption=caption, east_frame_path=str(EAST_FRAME), atmosphere_opacity=opacity, atmosphere_blur=0, ) print(f' -> {out}') finally: os.unlink(tmp_moon) print() print('done. Three outputs:') for filename, opacity, label in CASES: print(f' {filename:35s} opacity={opacity:.2f} {label}') print() print('What you should see in each:') print(' clear — NASA moon disk sharp and unobscured on black background') print(' hazy — same moon but with a soft grey-blue veil over the disk') print(' (from the thin cloud visible in 21-07-00.jpg)') print(' cloudy — moon mostly hidden; visible as a bright glow through') print(' the cloud texture from east\'s April 29 frame') if __name__ == '__main__': main()