diff --git a/5-metrics.py b/5-metrics.py index 5f9a811..d4a833e 100644 --- a/5-metrics.py +++ b/5-metrics.py @@ -82,6 +82,14 @@ PHENOCAM_IMAGE_URL = ( # --------------------------------------------------------------------------- +def _window_mean(data: np.ndarray) -> float | None: + """Mean of a pixel window; ``None`` when every value is NaN.""" + valid = data[~np.isnan(data)] + if valid.size == 0: + return None + return float(np.mean(valid)) + + def _read_center_pixel(path: Path, lat: float, lon: float) -> float | None: """Return the 3×3 mean GCC value at (lat, lon) from a single-band raster. @@ -103,9 +111,7 @@ def _read_center_pixel(path: Path, lat: float, lon: float) -> float | None: if nodata is not None: data = np.where(data == nodata, np.nan, data) data[data == 0] = np.nan - with np.errstate(all="ignore"): - val = np.nanmean(data) - return None if np.isnan(val) else float(val) + return _window_mean(data) except Exception: return None @@ -334,9 +340,8 @@ def _read_multiband_center( if nodata is not None: data = np.where(data == nodata, np.nan, data) data[data == 0] = np.nan - with np.errstate(all="ignore"): - val = np.nanmean(data) - result[name] = None if np.isnan(val) else round(float(val), 6) + val = _window_mean(data) + result[name] = None if val is None else round(val, 6) return result except Exception: return {name: None for name in band_names} diff --git a/run-pipeline.py b/run-pipeline.py index db2c493..3c18dc9 100644 --- a/run-pipeline.py +++ b/run-pipeline.py @@ -1,9 +1,11 @@ """Pipeline wrapper: run steps 1 → 2 → 3 → 4 → 5. Steps 1 and 2 run once for the whole year (skipped when their output already -exists). Steps 3–5 run site-by-site for every PASS site from -``data/phenocam_screening/{year}.json``; a site is skipped entirely when -``data/metrics/{year}/{site}/metrics.json`` already exists. +exists). Steps 3 and 4 run site-by-site for every PASS site from +``data/phenocam_screening/{year}.json``; a site is skipped when +``data/metrics/{year}/{site}/metrics.json`` already exists. Step 5 always +runs once at the end without ``--site`` so that ``manifest.json`` is written +with all processed sites (not just the last one). Any failure stops the run immediately. Fix the issue and re-run — completed steps and sites are skipped automatically. @@ -11,7 +13,7 @@ steps and sites are skipped automatically. CLI: - ``--evaluation-year`` (default 2025) -- ``--site`` single site to run steps 3–5 for (default: all PASS sites) +- ``--site`` single site to run steps 3–4 for (default: all PASS sites) """ from __future__ import annotations @@ -36,7 +38,6 @@ GLOBAL_STEPS: list[tuple[str, Path]] = [ PER_SITE_STEPS = [ "3-sentinel-data.py", "4-fusion.py", - "5-metrics.py", ]