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:
+11
-46
@@ -240,29 +240,6 @@ def composite_full_moon(
|
||||
return out_path
|
||||
|
||||
|
||||
def _make_atmosphere_layer(
|
||||
east_frame_path: str,
|
||||
output_size: tuple[int, int],
|
||||
blur_radius: int = 0,
|
||||
) -> Image.Image:
|
||||
"""Scale the full east frame to output_size and blur to atmospheric haze.
|
||||
|
||||
The blur removes wide-angle camera detail (RTSP artefacts, OSD text,
|
||||
pixel noise) while preserving real sky colour and large-scale cloud
|
||||
structure. The resulting layer is applied OVER the NASA moon disk so
|
||||
it reads as "clouds between the observer and the moon" — which is
|
||||
physically correct.
|
||||
|
||||
blur_radius=0 → auto: output_width // 20 (96 px at 1920 wide).
|
||||
This smooths pixel-level RTSP noise and OSD text while keeping
|
||||
recognisable cloud shapes and sky-colour gradients intact.
|
||||
"""
|
||||
src = Image.open(east_frame_path).convert('RGB')
|
||||
layer = src.resize(output_size, LANCZOS)
|
||||
r = blur_radius if blur_radius > 0 else output_size[0] // 20
|
||||
return layer.filter(ImageFilter.GaussianBlur(radius=r))
|
||||
|
||||
|
||||
def render_phase_closeup(
|
||||
nasa_render_path: str,
|
||||
out_path: str,
|
||||
@@ -270,28 +247,16 @@ def render_phase_closeup(
|
||||
moon_height_pct: float = 0.92,
|
||||
caption: str | None = None,
|
||||
background: tuple[int, int, int] = (0, 0, 0),
|
||||
east_frame_path: str | None = None,
|
||||
atmosphere_opacity: float = 0.0,
|
||||
atmosphere_blur: int = 0,
|
||||
when_utc: datetime | None = None,
|
||||
):
|
||||
"""Full-screen close-up rendering using a NASA SVS Dial-a-Moon image.
|
||||
|
||||
Rendering pipeline:
|
||||
1. Black background (outer space).
|
||||
2. NASA moon disk — correct phase, libration, crater shadows — centred
|
||||
and scaled to moon_height_pct of frame height.
|
||||
3. Atmospheric layer (optional): east's camera frame, scaled to output
|
||||
size and blurred, composited OVER the moon at atmosphere_opacity.
|
||||
This is physically correct — clouds are between the observer and the
|
||||
moon so they occlude the disk, not sit behind it.
|
||||
|
||||
atmosphere_opacity controls what the viewer sees through east's sky:
|
||||
0.00 — perfectly clear: pure NASA render, no overlay
|
||||
0.10 — slight haze: moon is sharp but slightly softened
|
||||
0.35 — noticeable cloud cover: moon partially obscured
|
||||
0.65 — heavy overcast: moon a faint glow through thick cloud
|
||||
Renders the NASA moon disk centred on a black background, rotated by
|
||||
the parallactic angle so its orientation matches what east's camera sees
|
||||
from its geographic location at the given UTC time.
|
||||
"""
|
||||
# ── Background + NASA moon ────────────────────────────────────────────
|
||||
import moon_phase
|
||||
|
||||
bg = Image.new('RGB', output_size, background)
|
||||
moon = Image.open(nasa_render_path).convert('RGB')
|
||||
moon = _square_crop_to_disk(moon)
|
||||
@@ -300,6 +265,11 @@ def render_phase_closeup(
|
||||
target += target % 2
|
||||
moon_resized = moon.resize((target, target), LANCZOS)
|
||||
|
||||
# Rotate by parallactic angle so "up" on the moon matches east's sky
|
||||
if when_utc is not None:
|
||||
par = moon_phase.parallactic_angle(when_utc)
|
||||
moon_resized = moon_resized.rotate(-par, resample=BICUBIC, expand=False)
|
||||
|
||||
feather = max(3, target // 240)
|
||||
mask = _disk_mask(target, feather_px=feather)
|
||||
|
||||
@@ -307,11 +277,6 @@ def render_phase_closeup(
|
||||
py = (output_size[1] - target) // 2
|
||||
bg.paste(moon_resized, (px, py), mask)
|
||||
|
||||
# ── Atmospheric layer from east frame — applied OVER the moon ─────────
|
||||
if east_frame_path and atmosphere_opacity > 0.0:
|
||||
atm = _make_atmosphere_layer(east_frame_path, output_size, atmosphere_blur)
|
||||
bg = Image.blend(bg, atm, alpha=atmosphere_opacity)
|
||||
|
||||
if caption:
|
||||
draw = ImageDraw.Draw(bg)
|
||||
_draw_caption(draw, caption, (22, output_size[1] - 48), output_size[0])
|
||||
|
||||
Reference in New Issue
Block a user