Wildlife Photo Review Workflow

Wildlife Photo Review Workflow

How wildlife photos are triaged, curated, and added to the /wildlife/ gallery. This is the repeatable process to run whenever a new folder of raw photos arrives.

Canonical data model

  • D:\Photos\Wildlife — the canonical folder of curated originals. Only photos that made it into (or were reviewed for) the gallery live here. Originals never enter the git repo.
  • _data/wildlife.yml — single source of truth for the catalog. One entry per photo: file, date, species, scientific, group (bird | mammal | reptile | insect | amphibian | crustacean), location ("" until filled), notes.
  • images/wildlife/{web,thumb}/<file>.jpg — derivatives (1600 px / 400 px), generated by python scripts/build_wildlife_images.py build. The build only processes files listed in the YAML, and reads originals from D:\Photos\Wildlife, so a selected original must be copied there first.
  • After any change, verify: catalog entries == web derivatives == thumb derivatives, with no orphans and nothing missing.

Progress so far (as of 2026-07-10)

FolderContentsStatus
D:\Photos\WildlifeCurated originals, DSCN0030–DSCN2218 (2026-02-04 → 2026-07-05)Fully reviewed; 136 photos in the gallery
D:\Photos\All_2026_06-16Full trip dump, 431 JPGs, DSCN1766–DSCN2216 (2026-05-28 → 2026-07-05, Outer Banks NC + South Florida)Reviewed — 61 already-curated skipped, 370 triaged, 14 new-species photos added
D:\Photos\All_2025-05~207 JPGs, May 2025Not yet processed
D:\Photos\All_2025_01-04~1476 JPGs, Jan–Apr 2025Not yet processed

“Date range processed” = 2026-02-04 through 2026-07-05. Everything from the 2026 spring/summer trips has been reviewed. The 2025 dumps are pending.

The review pipeline (run this for each new folder)

1. Generate stubs, excluding already-reviewed photos

For every *.JPG in the new folder, make a 400 px stub — but skip any basename already present in D:\Photos\Wildlife (those were reviewed in a prior round). Also capture EXIF date_taken into a manifest for dedup context.

# 400px stub generator (run once per new folder). Note: on Windows the
# filesystem is case-insensitive, so glob EITHER "*.JPG" OR "*.jpg" — not both,
# or every file is processed twice.
from pathlib import Path
from PIL import Image
SRC = Path(r"D:\Photos\<NEW_FOLDER>"); OUT = Path(r"<scratch>\stubs")
REVIEWED = {p.stem.upper() for p in Path(r"D:\Photos\Wildlife").glob("*.JPG")}
OUT.mkdir(parents=True, exist_ok=True)
for p in sorted(SRC.glob("*.JPG")):
    if p.stem.upper() in REVIEWED: continue
    with Image.open(p) as im:
        im.load(); s = 400/max(im.size)
        (im if s>=1 else im.resize((round(im.width*s),round(im.height*s)), Image.LANCZOS)
         ).convert("RGB").save(OUT/f"{p.stem}.jpg","JPEG",quality=82)

2. Triage with Sonnet sub-agents

Split the stub list into batches of ~31 and dispatch one Sonnet sub-agent per batch (they run in parallel). Each agent reads its batch list + stubs and, for every file, decides:

  • Is it wildlife?
    • INCLUDE free-ranging wild animals of any kind, including free-living introduced/invasive species (e.g., peacocks, iguanas, Egyptian geese, Brown Anoles in Miami).
    • EXCLUDE captive zoo/aquarium animals (enclosures, tanks, signage, exotic non-native captives), pets, and non-animal photos (scenery, people, food, buildings, plants).
  • If wildlife: identify species, rate quality 1–5, note behavior, verdict KEEP / DROP. Relax the quality bar for rare species or rare/interesting behavior. Drop lesser duplicates within the batch.
  • Give the agents the current collection’s species list so they can flag (common in collection) for dedup.

Output: a table File | Date | Wildlife? | Species | Quality | Verdict | Reason plus a tally. Persist each batch’s result to a scratch file (long task — guards against context loss).

3. Aggregate and dedup against the existing collection

The overriding rule is avoid duplicates with what’s already published. In practice, only keep:

  • New species not yet in the catalog, or
  • A clearly superior or distinctly different shot/behavior for a species already present (be conservative — the gallery should not fill with near-dupes).

4. Verify uncertain cases on the original yourself

Do not trust sub-agent IDs blindly for anything going public. Generate ~1200 px versions of the shortlisted candidates and review them directly to: confirm the species, pick the single best frame per species, and catch misidentifications. (This round alone that caught a “Bald Eagle” that was a cormorant, a “Great Blue Heron” that was a female Anhinga, a “Yellow-rumped Warbler” that was a Northern Parula, and a “deer leap” that was feral horses.)

5. Raise borderline photos for human review (required)

Do not silently decide borderline cases. Before adding anything, present a short list to the user for a decision on every photo that is any of:

  • an uncertain species ID (including ones where you overrode a sub-agent),
  • a rare species/behavior kept at low quality (the relaxed-bar cases),
  • a possible duplicate of a species already in the collection,
  • a borderline quality call (obscured, distant, soft) that is otherwise a new species or notable shot.

Give each a one-line reason and a recommended pick, and let the user confirm, swap the frame, or drop it. Only the clearly-good, clearly-new-species photos may be added without asking. (This step matters: past rounds the user swapped in a better frame — e.g. Common Gallinule 2123 over the obscured 2122 — and chose to keep a borderline record the pipeline had set aside.)

6. Add to the collection

  1. Copy the selected originals from the new folder into D:\Photos\Wildlife.
  2. Add entries to _data/wildlife.yml (keep it reverse-chronological; location: "" for manual fill later).
  3. python scripts/build_wildlife_images.py build
  4. Verify counts/consistency (catalog == web == thumb, no orphans/missing).
  5. Commit (originals stay out of git; only YAML + derivatives are committed).

Conventions & reminders

  • Quality bar: well-shot, subject prominent, in focus — except relax for rare species and rare behavior (say so in the notes when you do).
  • Invasive vs zoo: free-ranging invasives are in; captive non-native zoo animals are out.
  • Locations are added manually (photos have no GPS EXIF); the map shows only photos whose location resolves to a lat/lng in the locations: table.
  • No Ruby/Jekyll on this machine — verify the GitHub Pages build after pushing rather than rendering locally.