Simplify moon phase: binary go/no-go, parallactic angle rotation, no atmosphere overlay
- moon_composite.py: remove _make_atmosphere_layer() and all atmosphere parameters from render_phase_closeup(); add when_utc parameter and apply parallactic angle rotation so the NASA disk is oriented to match east's sky - moon_phase_monthly.py: change obs time default to 22:00 (aligns with NASA hourly renders); scan east frames in 22:00-23:00 window; binary go/no-go (quality >= MIN_QUALITY = post, no clear shot = skip entirely); remove _atmosphere_opacity_from_quality(), ATMOSPHERE_BLUR, CLOUDY_POST_ENABLED - sky-cam.conf: update MOON_OBS_TIME_LOCAL to 22:00, remove MOON_OBS_WINDOW_MIN, remove the atmospheric overlay and cloudy fallback sections and their config variables - test_moon_composite.py: single clean test — fetch NASA for 22:00 UTC on 2026-04-29, verify east frame quality, render with parallactic angle; no procedural fallback https://claude.ai/code/session_01HkTxpNSTWtViZzxbrbKytR
This commit is contained in:
+42
-112
@@ -3,25 +3,20 @@
|
||||
|
||||
Simulates what moon_phase_monthly.py does on the night of a full moon:
|
||||
|
||||
1. Round the obs time (22:30 local on 2026-04-29) to the nearest hour.
|
||||
2. Fetch the NASA SVS Dial-a-Moon render for that UTC hour.
|
||||
3. Load the east camera frame (21-07-00.jpg, captured at 21:07 UTC that night).
|
||||
4. Detect atmospheric conditions → compute overlay opacity.
|
||||
5. Render: NASA moon + east atmosphere overlay → test_composite_out.jpg.
|
||||
1. Fetch the NASA SVS Dial-a-Moon render for 2026-04-29T22:00Z.
|
||||
2. Load the east camera frame (21-07-00.jpg) and check for a clear moon.
|
||||
3. If clear: render — NASA moon with parallactic angle rotation → test_composite_out.jpg.
|
||||
4. If not clear: exit with a message (no fallback image).
|
||||
|
||||
Run from the sky-cam directory:
|
||||
|
||||
python3 test_moon_composite.py
|
||||
|
||||
Requires internet access to reach svs.gsfc.nasa.gov.
|
||||
If the API is unreachable a procedural moon disc is used as a fallback
|
||||
so the atmospheric overlay is still visible and testable.
|
||||
"""
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
import tempfile
|
||||
from datetime import datetime, timezone
|
||||
|
||||
HERE = pathlib.Path(__file__).resolve().parent
|
||||
@@ -30,67 +25,10 @@ sys.path.insert(0, str(HERE))
|
||||
EAST_FRAME = HERE / '21-07-00.jpg'
|
||||
OUT_PATH = HERE / 'test_composite_out.jpg'
|
||||
|
||||
# The east frame is 2026-04-29 21:07 UTC; obs time is 22:30 local → ~21:30 UTC
|
||||
# (assuming Eastern time UTC-4 in late April). Round to nearest hour → 22:00 UTC.
|
||||
# Obs time: 22:00 UTC on 2026-04-29 — aligns directly with NASA hourly renders.
|
||||
NASA_FETCH_UTC = datetime(2026, 4, 29, 22, 0, tzinfo=timezone.utc)
|
||||
|
||||
|
||||
def _fetch_nasa(dt: datetime) -> 'pathlib.Path | None':
|
||||
try:
|
||||
import moon_dialamoon
|
||||
print(f'fetching NASA Dial-a-Moon for {dt.strftime("%Y-%m-%dT%H:00Z")} …')
|
||||
path = moon_dialamoon.fetch_for_time(dt)
|
||||
print(f' cached at {path}')
|
||||
return path
|
||||
except Exception as e:
|
||||
print(f' NASA fetch failed: {e}')
|
||||
return None
|
||||
|
||||
|
||||
def _make_procedural_moon(size: int = 2048) -> 'pathlib.Path':
|
||||
"""Fallback: clean grey disc with 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')
|
||||
img = Image.fromarray(arr)
|
||||
|
||||
tmp = tempfile.NamedTemporaryFile(suffix='.png', delete=False)
|
||||
tmp.close()
|
||||
img.save(tmp.name)
|
||||
return pathlib.Path(tmp.name)
|
||||
|
||||
|
||||
def _detect_atmosphere(frame_path: str) -> tuple[float | None, float]:
|
||||
"""Run moon_detect and return (quality, opacity)."""
|
||||
try:
|
||||
from moon_detect import detect_moon
|
||||
from moon_phase_monthly import _atmosphere_opacity_from_quality
|
||||
det = detect_moon(frame_path)
|
||||
quality = det.quality if det is not None else None
|
||||
opacity = _atmosphere_opacity_from_quality(quality)
|
||||
return quality, opacity
|
||||
except Exception as e:
|
||||
print(f' detection failed: {e}')
|
||||
return None, 0.0
|
||||
MIN_QUALITY = 0.55
|
||||
|
||||
|
||||
def main():
|
||||
@@ -103,59 +41,51 @@ def main():
|
||||
print(f'NASA time : {NASA_FETCH_UTC.strftime("%Y-%m-%dT%H:00Z")}')
|
||||
print()
|
||||
|
||||
# 1. Try real NASA image
|
||||
tmp_to_delete = None
|
||||
nasa_path = _fetch_nasa(NASA_FETCH_UTC)
|
||||
if nasa_path is None:
|
||||
print('falling back to procedural moon disc …')
|
||||
nasa_path = _make_procedural_moon()
|
||||
tmp_to_delete = str(nasa_path)
|
||||
print(f' disc saved to {nasa_path}')
|
||||
# 1. Check east frame for clear moon detection
|
||||
print(f'checking moon in {EAST_FRAME.name} ...')
|
||||
try:
|
||||
from moon_detect import detect_moon
|
||||
det = detect_moon(str(EAST_FRAME))
|
||||
quality = det.quality if det is not None else None
|
||||
except Exception as e:
|
||||
print(f' detection failed: {e}', file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
if quality is None or quality < MIN_QUALITY:
|
||||
print(f' quality={quality} — no clear moon detection — skipping (no fallback)')
|
||||
sys.exit(0)
|
||||
|
||||
print(f' quality={quality:.3f} — clear shot confirmed')
|
||||
print()
|
||||
|
||||
# 2. Atmosphere from east frame
|
||||
print(f'checking atmosphere in {EAST_FRAME.name} …')
|
||||
quality, opacity = _detect_atmosphere(str(EAST_FRAME))
|
||||
if quality is not None:
|
||||
print(f' moon quality: {quality:.3f} → atmosphere opacity: {opacity:.2f}')
|
||||
else:
|
||||
print(f' no moon detected (overcast) → opacity: {opacity:.2f}')
|
||||
# 2. Fetch NASA Dial-a-Moon
|
||||
print(f'fetching NASA Dial-a-Moon for {NASA_FETCH_UTC.strftime("%Y-%m-%dT%H:00Z")} ...')
|
||||
try:
|
||||
import moon_dialamoon
|
||||
nasa_path = moon_dialamoon.fetch_for_time(NASA_FETCH_UTC)
|
||||
print(f' cached at {nasa_path}')
|
||||
except Exception as e:
|
||||
print(f' NASA fetch failed: {e}', file=sys.stderr)
|
||||
sys.exit(3)
|
||||
print()
|
||||
|
||||
# 3. Render
|
||||
# 3. Render with parallactic angle rotation
|
||||
from moon_composite import render_phase_closeup
|
||||
caption = (
|
||||
f'Full Moon — April 2026 — '
|
||||
f'sky-cam east 2026-04-29 22:30 local — '
|
||||
f'sky-cam east 2026-04-29 22:00 UTC — '
|
||||
f'NASA SVS Dial-a-Moon'
|
||||
)
|
||||
print(f'rendering {OUT_PATH.name} …')
|
||||
try:
|
||||
render_phase_closeup(
|
||||
nasa_render_path=str(nasa_path),
|
||||
out_path=str(OUT_PATH),
|
||||
output_size=(1920, 1080),
|
||||
moon_height_pct=0.88,
|
||||
caption=caption,
|
||||
east_frame_path=str(EAST_FRAME),
|
||||
atmosphere_opacity=opacity,
|
||||
atmosphere_blur=0,
|
||||
)
|
||||
finally:
|
||||
if tmp_to_delete:
|
||||
os.unlink(tmp_to_delete)
|
||||
|
||||
print(f'done → {OUT_PATH}')
|
||||
print()
|
||||
print(f'opacity={opacity:.2f}:', end=' ')
|
||||
if opacity == 0.0:
|
||||
print('clear sky — pure NASA moon on black background')
|
||||
elif opacity < 0.15:
|
||||
print('slight haze — moon visible, softly veiled')
|
||||
elif opacity < 0.40:
|
||||
print('cloud cover — moon partially obscured')
|
||||
else:
|
||||
print('heavy overcast — moon a glow through cloud')
|
||||
print(f'rendering {OUT_PATH.name} ...')
|
||||
render_phase_closeup(
|
||||
nasa_render_path=str(nasa_path),
|
||||
out_path=str(OUT_PATH),
|
||||
output_size=(1920, 1080),
|
||||
moon_height_pct=0.88,
|
||||
caption=caption,
|
||||
when_utc=NASA_FETCH_UTC,
|
||||
)
|
||||
print(f'done -> {OUT_PATH}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
Reference in New Issue
Block a user