Skip to content

Latest commit

 

History

21 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Anequim

Anequim — Catching the ocean spectrum

Catching the ocean spectrum.

Anequim is a sensor-independent Python framework for retrieving ocean-color remote sensing reflectance (Rrs) and associated atmospheric products from local Level-2 satellite granules — fast, reliable, and without silently resampling a sensor's native spectral bands.

Retrieve the spectrum first. Everything else builds on that.

Author: Lucas Barbedo — Oceanographer, M.Sc. in Remote Sensing, Ph.D. in Oceanography, currently at Météo-France.

Citation: If you use Anequim in a publication, please cite it — see CITATION.cff, or:

Barbedo, L. (2026). Anequim: A sensor-independent Python framework for ocean-color remote sensing reflectance and atmospheric product retrieval (Version 0.1.0) [Computer software]. https://github.com/lucasbarbedo83/anequim

from anequim import Anequim

cube = Anequim.retrieve(
    files="PACE_OCI.20240615T144200.L2.OC_AOP.nc",
    longitude=-70.5,
    latitude=41.3,
    time="2024-06-15T15:00:00Z",
    sensor="OCI",
)

print(cube)
print(cube.spectrum_dataframe())

Design principles

  1. Sensor independence — one API (Anequim.get_rrs(...)) across PACE OCI, Sentinel-3 OLCI, VIIRS, and MODIS. You interact with the same object regardless of sensor.
  2. Preserve native spectral bands — anequim never interpolates automatically. Each sensor keeps its own wavelengths and spectral response; harmonization is opt-in (anequim.harmonization).
  3. Fast spectral retrieval — answer "what is the ocean reflectance spectrum at this location?" in one call.
  4. Atmospheric products included — AOT, Ångström exponent, solar/ sensor zenith, relative azimuth, wind speed, and quality flags ride alongside Rrs through the same interface, when available. Total column ozone and water vapor/surface pressure can additionally be fetched on demand to compute downwelling irradiance just above the sea surface, Ed(0+) — see Downwelling irradiance below.
  5. Region-of-interest tools — single pixel, circular, rectangular, and lon/lat bounding-box ROIs today; polygon ROI is planned.
  6. SpectralCube — every sensor returns the same container: Rrs, wavelengths, metadata, navigation, atmospheric products, and quality flags.
  7. Scientific reproducibility — every result carries a Provenance record: package version, acquisition time, sensor identity, processing version, and the exact ROI/QC configuration used.

What's implemented in this release (v0.1)

Module Status
core (config, QC, flags, SpectralCube, Provenance, Anequim) Working
readers — PACE OCI (sensor="OCI") Working
readers — MODIS-Aqua/Terra (sensor="MODIS") Working
readers — VIIRS SNPP/NOAA-20 (sensor="VIIRS") Working
readers — Sentinel-3 OLCI (sensor="OLCI") Working (directory-based SAFE granules; see caveats below)
roi — pixel, rectangular, circular, bounding box Working
roi — polygon Stub (NotImplementedError)
geometry (haversine distance, nearest-pixel search, pixel size/footprint) Working
statistics (mean, median, std, percentile, covariance, correlation) Working
download — PACE OCI / MODIS / VIIRS via NASA Earthdata (earthaccess) Working (pip install anequim[download])
download — Sentinel-3 OLCI via EUMETSAT Data Store (eumdac, default) or CDSE (alternative) Working (pip install anequim[download])
download.ancillary — ozone (OMI/Aura) + water vapor/pressure (MERRA-2) via NASA Earthdata Working (pip install anequim[download])
atmosphere — Ed(0+) via Anequim.get_Ed(...), pluggable algorithms (Frouin is the default) Working
harmonization (wavelength interpolation, SRF convolution) Stub, opt-in by design
algorithms (QAA, GIOP, GSM) Stub — future bio-optical inversion
plot plot_spectrum working; comparison/map plots stubbed

All four NASA/ESA ocean color missions anequim targets (PACE OCI, MODIS-Aqua, MODIS-Terra, VIIRS-SNPP/NOAA-20, Sentinel-3 OLCI) are now readable and downloadable through the same one-call interface: Anequim.retrieve_online(longitude, latitude, time, sensor=...). Every sensor returns the identical SpectralCube shape — same fields, same methods — regardless of which agency or file format it came from.

Match-up methodology

For a point/time query, Anequim follows the spirit of the widely-used satellite ocean-color match-up protocol described by Bailey & Werdell (2006):

  1. Locate the pixel nearest the target longitude/latitude, then extract an odd-sized N x N box around it (default 5x5) — or use a circular / bounding-box ROI instead.
  2. Only use granules whose overpass time is within a configurable window of the target time (default ±3 hours).
  3. Drop pixels flagged by the sensor's own quality flags (cloud, land, glint, extreme geometry, stray light, navigation failure, ...).
  4. Require a minimum valid-pixel fraction in the ROI (default 50%), and flag (but don't silently discard) ROIs with excessive spatial heterogeneity via coefficient of variation (default threshold 0.15).
  5. Report the mean or median of the valid pixels as the representative spectrum, with full statistics attached so you can apply your own, stricter or looser, acceptance criteria.

See docs/METHODOLOGY.md for the full write-up and references, and docs/ARCHITECTURE.md for how the modules fit together.

Installation

pip install -e ".[all]"   # editable install with plotting + xarray + test extras

Core runtime dependencies: numpy, netCDF4, pandas.

Quick start (download + retrieve in one call — any supported sensor)

The interaction is the same regardless of sensor: give a point, a time, and a sensor; anequim finds the granule, downloads it, and returns a SpectralCube. Only the one-time credential setup differs, since PACE OCI and Sentinel-3 OLCI are distributed by different agencies.

Requires pip install anequim[download].

PACE OCI — via NASA Earthdata (earthaccess):

from anequim import Anequim
from anequim.download import login

login(strategy="netrc")  # or strategy="interactive", persist=True the first time

cube = Anequim.retrieve_online(
    longitude=-70.5, latitude=41.3, time="2024-06-15T15:00:00Z", sensor="OCI",
)

One-time setup: create a free NASA Earthdata Login account (https://urs.earthdata.nasa.gov/), then either run anequim.download.login(strategy="interactive", persist=True) once (it saves credentials to ~/.netrc), or write ~/.netrc yourself.

Sentinel-3 OLCI — via EUMETSAT's Data Store (default backend, uses the official eumdac client):

import os
os.environ["EUMETSAT_CONSUMER_KEY"] = "..."
os.environ["EUMETSAT_CONSUMER_SECRET"] = "..."

from anequim import Anequim

cube = Anequim.retrieve_online(
    longitude=-70.5, latitude=41.3, time="2024-06-15T15:00:00Z", sensor="OLCI",
)

One-time setup: create a free EUMETSAT User Portal account (https://user.eumetsat.int/register), then — important, easy to miss — log in, open your profile, go to "My data licenses", and enable "Meteosat > 1hr latency & Metop, Copernicus data & Third party data" (Sentinel-3 products aren't accessible without this, even with valid credentials). Then generate a consumer key/secret at https://api.eumetsat.int/api-key and set them as environment variables. This credential type is a static key pair — no 2FA interaction, unlike CDSE below.

Alternative OLCI backend — Copernicus Data Space Ecosystem (CDSE), same underlying data, different agency/account:

import os
os.environ["CDSE_USERNAME"] = "you@example.com"
os.environ["CDSE_PASSWORD"] = "..."

from anequim import Anequim

cube = Anequim.retrieve_online(
    longitude=-70.5, latitude=41.3, time="2024-06-15T15:00:00Z", sensor="OLCI",
    download_kwargs={"backend": "cdse"},
)

One-time setup: create a free CDSE account (https://dataspace.copernicus.eu/) with a direct email+password login (not Google/institutional SSO — that login type isn't supported by the API used here), then set CDSE_USERNAME / CDSE_PASSWORD as environment variables.

If two-factor authentication is enabled on your CDSE account (check your account settings), plain username/password isn't enough — call login() once per session with your current 2FA code:

from anequim.download.copernicus import login
login(totp="123456")  # current 6-digit code from your authenticator app

This caches a refresh token to ~/.anequim/cdse_refresh_token, so every retrieve_online(sensor="OLCI", download_kwargs={"backend": "cdse"}) call afterward — including from unattended scripts — works silently without needing your password or a new 2FA code again, until that refresh token eventually expires (at which point call login() again).

MODIS / VIIRS — via NASA Earthdata, same credentials as PACE OCI above:

cube = Anequim.retrieve_online(
    longitude=-70.5, latitude=41.3, time="2024-06-15T15:00:00Z", sensor="MODIS",
    download_kwargs={"platform": "Aqua"},   # or "Terra"
)
cube = Anequim.retrieve_online(
    longitude=-70.5, latitude=41.3, time="2024-06-15T15:00:00Z", sensor="VIIRS",
    download_kwargs={"platform": "SNPP"},   # or "NOAA-20"
)

All calls search the respective agency's catalog for granules covering your point and time window, download to ~/.anequim/cache/... (or cache_dir= of your choosing), and run the same retrieval pipeline as Anequim.retrieve(files=..., ...). Note: PACE OCI/MODIS/VIIRS and Sentinel-3 OLCI are distributed by different space agencies (NASA vs. ESA/EU/EUMETSAT — and OLCI even has two independent portals) via genuinely separate catalogs and credential systems — that's a fact about the data providers, not a design choice of anequim's.

Quick start (CLI, local files)

anequim --files data/*.nc --sensor OCI --lon -70.5 --lat 41.3 \
    --time 2024-06-15T15:00:00Z --window-hours 3 --box-size 5 \
    --output matchup.csv

Pixel size and ROI footprint

Every SpectralCube reports the actual ground pixel size, measured directly from the granule's own geolocation grid at the matched location — not a hardcoded nominal constant, since real pixel size varies substantially off-nadir:

cube.pixel_size_km        # {'along_track_km':.., 'cross_track_km':.., 'mean_km':.., 'area_km2':..}
cube.roi_footprint_km      # {'n_rows':.., 'n_cols':.., 'along_track_km':.., 'cross_track_km':.., 'diagonal_km':..}
cube.provenance.nominal_pixel_size_m   # sensor's documented nadir spec, for reference (300 for OLCI, ~1000 for PACE OCI)

Downwelling irradiance (Ed(0+)) — get_Ed

Beyond Rrs, anequim can compute hyperspectral downwelling irradiance just above the sea surface, Ed(0+, λ), via Anequim.get_Ed(...) — the same "just give me longitude/latitude/time" ergonomics as get_rrs. The irradiance method is pluggable (anequim.atmosphere.registry); Frouin (Frouin & Chertock, 1992; Frouin, Franz & Werdell, 2003 — the same physical framework NASA uses operationally for PAR and surface irradiance products) is the default and, for now, the only one implemented, but get_Ed(..., algorithm=...) exists specifically so a second method can be added later without changing any calling code.

Standalone — no satellite granule needed at all:

from anequim import Anequim

result = Anequim.get_Ed(longitude=-70.5, latitude=41.3, time="2024-06-15T15:00:00Z")

result.Ed            # spectral Ed(0+), W m^-2 nm^-1, shape (301,) — 400-700nm @ 1nm by default
result.wavelengths    # nm
result.par()          # mol quanta m^-2 s^-1
result.broadband()    # W m^-2
result.atmospheric_state   # the AtmosphericState actually used (ozone, water vapor, sza, ...)
result.provenance     # full Provenance record — sensor="N/A (Ed only, no granule)"

Solar geometry is computed astronomically (anequim.geometry.solar_position.solar_zenith_angle), and ozone, water vapor, surface pressure, and aerosol are fetched from NASA Earthdata (OMI/Aura + MERRA-2) — this requires the optional earthaccess dependency and NASA Earthdata Login credentials, the same as Anequim.retrieve_online(...) above.

Reusing a match-up — if you already called get_rrs, pass its result straight into get_Ed to reuse its acquisition geometry (solar_zenith, etc.) and, when available, its already-fetched ancillary atmosphere and scene-matched aerosol — no extra network call:

cube = Anequim.retrieve(
    files="PACE_OCI.20240615T144200.L2.OC_AOP.nc",
    longitude=-70.5, latitude=41.3, time="2024-06-15T15:00:00Z", sensor="OCI",
    include_ancillary_atmosphere=True,  # fetches ozone + water vapor/pressure once, here
)

result = Anequim.get_Ed(cube=cube)   # reuses cube's geometry + ancillary — no re-fetch

Aerosol optical depth in this mode comes from the granule's own aot_865 + angstrom (scene-matched, higher resolution than any reanalysis aerosol field), not from MERRA-2.

Already have better ancillary data from elsewhere? Any of ozone_du, water_vapor_cm, pressure_hpa, aod_550, angstrom_exponent passed explicitly overrides what either mode would otherwise use or fetch:

result = Anequim.get_Ed(
    longitude=-70.5, latitude=41.3, time="2024-06-15T15:00:00Z",
    ozone_du=290.0, water_vapor_cm=2.1,
)

Accuracy note: the built-in reference solar spectrum, ozone absorption cross-section, and water-vapor absorption coefficients (Frouin algorithm) are physically-reasonable placeholders (the solar spectrum integrates to the correct ~1360 W/m² total solar irradiance), not NASA's vicariously-calibrated operational tables. For quantitative work, supply your own via FrouinIrradiance.set_solar_spectrum(), .set_ozone_cross_section(), and .set_water_vapor_coefficients() (e.g. TSIS-1 HSRS, Bogumil et al. 2003, Bird & Riordan 1986), then pass that configured instance: Anequim.get_Ed(..., algorithm=my_frouin_instance). See the module docstring in anequim/atmosphere/frouin_irradiance.py for details and references.

OLCI L2 WFR granules are a directory (ESA SAFE format, S3?_OL_2_WFR____....SEN3), not a single file — pass the directory path itself to files=. A couple of things to know:

  • Units: EUMETSAT's Collection 4 (effective Feb 2026) switched the per-band product from water-leaving reflectance ρw to Rrs directly. The reader checks each band's own units attribute and converts automatically either way — no action needed.
  • Atmospheric geometry: solar/sensor zenith and relative azimuth live on a coarser "tie-point" grid in OLCI files and aren't interpolated to the full-resolution grid yet, so cube.atmospheric may be sparse (AOT/Ångström are included when present; geometry is not, for now).
  • Default QC flags: OLCI's WQSF flags have a different vocabulary than PACE/MODIS/VIIRS's l2_flags; a WQSF-appropriate default exclusion set is used automatically (see anequim.readers.olci.DEFAULT_OLCI_EXCLUDED_FLAGS) — verify it against your own file's flag_meanings for rigorous work.

Testing without real satellite data

examples/make_synthetic_pace_file.py and examples/make_synthetic_olci_directory.py generate small, structurally realistic synthetic granules (correct group/variable or directory layout, a plausible phytoplankton-like Rrs spectral shape, and a scattering of flagged pixels) so the whole pipeline can be exercised offline for either sensor. The test suite (tests/) uses them as pytest fixtures (synthetic_pace_file, synthetic_olci_directory).

pytest
python examples/quickstart.py

Long-term vision

Anequim aims to become a reference open-source library for ocean-color spectral retrieval: one consistent interface across sensors, preserving each sensor's scientific integrity. A companion project, Anequim Lab, is planned as a collection of notebooks for tutorials, validation exercises, inter-sensor comparisons, and advanced bio-optical workflows.

References

  • Bailey, S. W., & Werdell, P. J. (2006). A multi-sensor approach for the on-orbit validation of ocean color satellite data products. Remote Sensing of Environment, 102(1-2), 12-23.
  • Lee, Z., Carder, K. L., & Arnone, R. A. (2002). Deriving inherent optical properties from water color: a multiband quasi-analytical algorithm for optically deep waters. Applied Optics, 41(27), 5755-5772.
  • Werdell, P. J., et al. (2013). Generalized ocean color inversion model for retrieving marine inherent optical properties. Applied Optics, 52(10), 2019-2037.
  • Maritorena, S., Siegel, D. A., & Peterson, A. R. (2002). Optimization of a semianalytical ocean color model for global-scale applications. Applied Optics, 41(15), 2705-2714.
  • NASA Ocean Biology Processing Group, PACE OCI Level-2 Data Format and l2_flags bit definitions (oceancolor.gsfc.nasa.gov).

License

MIT.

About

Open-source Python frameworkOpen-source Python framework for reliable, reproducible, and sensor-independent retrieval of ocean-color remote sensing reflectance (Rrs).

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages