Commit Graph
7 Commits
Author SHA1 Message Date
Claude c09b872f52 Apply atmosphere overlay ON TOP of moon, add overcast fallback
The core mental model was wrong: clouds are between the observer and
the moon, so they occlude the disk — they don't sit behind it.

moon_composite.py:
  _make_east_sky_backdrop() → _make_atmosphere_layer()
  render_phase_closeup() pipeline is now:
    1. Black background
    2. NASA moon disk centred (correct phase / libration / shadows)
    3. East frame scaled + blurred → composited OVER the disk at
       atmosphere_opacity (0.0 = clear, 0.68 = heavy overcast)
  Parameters: east_sky_enabled/blur → atmosphere_opacity/blur

moon_phase_monthly.py:
  _atmosphere_opacity_from_quality() maps detection quality to opacity:
    quality >= 0.85 → 0.00 (clear)
    quality   0.70  → 0.20 (light haze)
    quality   0.55  → 0.40 (notable cloud, still detected)
    quality   None  → 0.68 (no detection, heavy overcast)

  Frame scan now tracks two lists:
    qualifying     — frames passing quality + illumination (existing)
    above_horizon  — frames where moon is up but detection failed

  When qualifying is empty and MOON_CLOUDY_POST_ENABLED=true, the
  above-horizon frame closest to the exact phase moment is used with
  opacity 0.45–0.68.  The month is always represented — full moon,
  first quarter, and third quarter each get a post even in cloudy
  months, showing the moon as a faint glow behind cloud.

sky-cam.conf:
  MOON_EAST_SKY_ENABLED/BLUR → MOON_ATMOSPHERE_BLUR (opacity is
  computed automatically from quality, not configured directly)
  New: MOON_CLOUDY_POST_ENABLED=true

test_moon_composite.py:
  Generates three outputs at opacity 0.00 / 0.20 / 0.65 so the
  full opacity range is visible in one test run.

https://claude.ai/code/session_01HkTxpNSTWtViZzxbrbKytR
2026-05-01 21:51:42 +00:00
Claude 1425a6e5f4 Replace annular cloud veil with full-frame east sky backdrop
The annular-ring veil approach sampled too small a region (38 px moon
in 3840×2160 gives a tiny annulus) and was invisible in practice.
More fundamentally, using the whole east sky is what "relayed to where
it was taken" actually means.

New approach — _make_east_sky_backdrop():
  Scale the full east camera frame to output size, apply GaussianBlur
  r = output_width // 6 (≈ 320 px at 1920 wide).  The blur erases RTSP
  artefacts, OSD overlays, and the wide-angle look while preserving real
  sky colour, cloud / haze gradients, and the dark ground silhouette.
  The NASA moon disk is pasted sharp on top.  The result reads as east
  photographed the moon through a telephoto with its actual sky that night.

  Clear dark sky → nearly black backdrop (same feel as before).
  Thin cloud cover → soft grey-blue haze behind the sharp moon.
  Heavy overcast → the moon detection would not qualify, so this case
    never reaches rendering.

Config rename: MOON_CLOUD_OVERLAY_* → MOON_EAST_SKY_ENABLED / _BLUR.
moon_phase_monthly.py: CLOUD_OVERLAY_* → EAST_SKY_*.
render_phase_closeup(): cloud_overlay_* params → east_sky_*.

test_moon_composite.py: replace full_moon_closeup.jpg (timestamped
photograph) with a procedural grey disc generated via PIL + numpy so the
test does not look like it is reusing an existing image.

https://claude.ai/code/session_01HkTxpNSTWtViZzxbrbKytR
2026-05-01 18:17:54 +00:00
Claude 5244fbb89b Fix missed raw draw.text in render_phase_closeup
The previous commit replaced the caption block in composite_full_moon
but missed render_phase_closeup because that block had no comment to
match the replace_all pattern. Both caption sites now go through
_draw_caption() so the Unicode / font fix applies everywhere.

https://claude.ai/code/session_01HkTxpNSTWtViZzxbrbKytR
2026-05-01 18:10:25 +00:00
Claude b45ef3a791 Fix UnicodeEncodeError in caption drawing
The default PIL bitmap font only covers latin-1, so the em dash (U+2014)
in captions raised UnicodeEncodeError at runtime.

Add _load_font() which tries DejaVu Sans TTF from common system paths
(the fonts-dejavu package is already a listed dependency) and falls back
to the PIL default only when no TTF is found.  Add _draw_caption() which
wraps both the font load and the two-pass drop-shadow draw, and sanitises
the string for the bitmap fallback path so it never crashes.

Replace both raw draw.text caption blocks in composite_full_moon() and
render_phase_closeup() with _draw_caption().

https://claude.ai/code/session_01HkTxpNSTWtViZzxbrbKytR
2026-05-01 18:07:53 +00:00
Claude 3ededd68b8 Add per-event NASA moon images with east-sky cloud veil
Three changes driven by the same goal: make each monthly moon post
look like east captured it at that exact moment.

Tighter illumination windows (±4% of target phase):
  Full moon:   95% → 96–100%  (was a 5-point band; now hugs exact full)
  Quarters:    40–65% → 46–54%  (was a 25-point band; now ±4% of 50%)
This ensures the NASA Dial-a-Moon render timestamp is pulled within
4 percentage points of the true phase, making the fetched image
genuinely represent that night's moon.  One fresh fetch per event,
~3/month, cached by hour — never reused across months.

Atmospheric cloud veil from east's surrounding sky:
  _extract_cloud_veil() samples the annular sky region just outside
  east's moon disk (2×–5× radius), scales it to the output frame,
  blurs heavily (GaussianBlur r≈output_width/10) so it reads as haze
  rather than an upscaled photo, then blends it over the NASA composite
  at an opacity proportional to sky brightness:
    sky < 5% mean brightness → no veil (clear dark night)
    sky ~10%                 → ~17% veil (thin haze / airglow)
    sky ≥ 20%                → 40% veil (max, MOON_CLOUD_OVERLAY_MAX_OPACITY)
  After the veil pass the NASA moon disk is re-pasted sharply so the
  haze sits naturally behind the crisp lunar surface.
  This is the honest answer to "east can't capture high-res clouds":
  east's real atmospheric fingerprint becomes the veil texture.

New sky-cam.conf keys:
  MOON_CLOUD_OVERLAY_ENABLED=true
  MOON_CLOUD_OVERLAY_MAX_OPACITY=0.40
  #MOON_CLOUD_OVERLAY_BLUR=0  (0 = auto)

https://claude.ai/code/session_01HkTxpNSTWtViZzxbrbKytR
2026-05-01 17:07:16 +00:00
Claude 28c3c1dc63 Switch monthly phase close-ups to NASA SVS Dial-a-Moon at fullscreen
The previous east-flavored composite (tight-crop east region + lunar
texture overlay) was honest but visually limited: 38-px source moon, no
real terminator shadows on quarters, and a faint upscaled-halo
background that was less compelling than just a clean lunar render.

New flow:
- East stays the witness (verifies the moon was visible during the
  collection window) and supplies the timestamp.
- moon_dialamoon.py fetches the NASA SVS Dial-a-Moon render for that
  exact UTC hour. Free, public-domain, real-physics: correct phase,
  libration, and crater shadows for any timestamp.
- moon_composite.render_phase_closeup() places that render at ~92% of
  frame height on a 1920x1080 black background -- the long-telephoto
  look the user asked for.
- One API call per phase event (~36/year), cached forever in
  moon-ref/dialamoon/.

Quarter moons now show real 3D crater shadows along the terminator,
which the prior algorithmic phase-shadow couldn't simulate from a
full-moon reference.

Adds MOON_REQUIRE_EAST_VERIFY=true|false toggle so the user can choose
"only post when east saw it" (default, current behavior) vs "post every
cycle regardless of weather over east."

Drops the install-time lunar reference download (no longer needed) and
the now-unused per-phase phase-shadow path stays in moon_composite.py
for reference / future reuse.

https://claude.ai/code/session_015PBVDESC3KLMbq1LpA6qLn
2026-05-01 11:54:46 +00:00
Claude 5fb7ff09a3 Add nightly moon-track timelapse and monthly moon-phase composites
Two new jobs operate on the SUNRISE_CAM, sharing three Python helpers
(moon_phase, moon_detect, moon_composite) and one cached lunar texture:

- moon-track.sh: nightly batch detects the moon in each east frame,
  crops a 480x480 box around it, stitches into an mp4 that holds the
  moon roughly centred while clouds and stars drift past.

- moon-phase-monthly.sh: daily check that runs whichever phase composite
  is due that day. Handles full moon (posts D+3), first quarter (D+2,
  best-effort due to daytime-only geometry from east), and third quarter
  (D+2). Picks the frame closest in time to the exact phase moment that
  meets quality / altitude / illumination thresholds, then composites
  the cached lunar texture into it -- sky/halo/parallactic-angle/timing
  real from east, surface detail borrowed from the reference image.

Honest-by-design: a 38-px white blob from a wide-field IP camera cannot
be enhanced into crater detail by software. The composite makes the
borrowing explicit and constrains everything else (when, where, sky,
orientation) to match what east actually saw.

install.sh now downloads the skyfield ephemeris (de421.bsp) and the
default lunar reference (Wikipedia CC BY-SA full-moon photo) on first
run. Both can be overridden via .env.

https://claude.ai/code/session_015PBVDESC3KLMbq1LpA6qLn
2026-04-30 11:40:11 +00:00