Examples¶
Practical examples covering the main use cases of GEE ACOLITE: atmospheric correction workflows and satellite-derived bathymetry.
Example 1 — Basic Atmospheric Correction¶
Correct a single Sentinel-2 scene and compute water quality products.
import ee
import acolite as ac
from gee_acolite import ACOLITE
from gee_acolite.utils.search import search
ee.Initialize(project='your-cloud-project-id')
# --- 1. Search ---
roi = ee.Geometry.Rectangle([-0.40, 39.27, -0.28, 39.38])
images = search(roi, '2023-07-15', '2023-07-16', tile='30SYJ')
print(f"Images found: {images.size().getInfo()}")
# --- 2. Configure ---
settings = {
's2_target_res': 10,
'dsf_spectrum_option': 'darkest',
'dsf_model_selection': 'min_drmsd',
'dsf_nbands': 2,
'pressure': 1013.25,
'uoz': 0.3,
'uwv': 1.5,
'wind': 2.5,
'l2w_parameters': [
'spm_nechad2016', # SPM at 665 nm (mg/L)
'spm_nechad2016_704', # SPM at 704 nm (mg/L)
'tur_nechad2016', # Turbidity at 665 nm (FNU)
'chl_oc3', # Chlorophyll-a OC3 (mg/m3)
'chl_re_mishra', # Chlorophyll-a NDCI red-edge
'pSDB_green', # Pseudo-SDB blue/green
'pSDB_red', # Pseudo-SDB blue/red
],
}
# --- 3. Correct ---
ac_gee = ACOLITE(ac, settings)
corrected, final_settings = ac_gee.correct(images)
# --- 4. Inspect bands ---
bands = corrected.first().bandNames().getInfo()
print("Output bands:", bands)
# Example: ['rhot_B1', ..., 'rhot_B12', 'rhos_B1', ..., 'rhos_B12',
# 'spm_nechad2016', 'tur_nechad2016', 'chl_oc3', 'pSDB_green', ...]
Example 2 — Fixed AOT (Skip DSF)¶
Use a known AOT and LUT instead of running the dark spectrum fitting. Useful when you have external AOT estimates (e.g., AERONET).
settings = {
's2_target_res': 10,
'dsf_fixed_aot': 0.05,
'dsf_fixed_lut': 'ACOLITE-LUT-202110081200-MOD1',
'pressure': 1013.25,
'uoz': 0.3,
'uwv': 1.5,
'wind': 2.5,
'l2w_parameters': ['spm_nechad2016', 'chl_oc3'],
}
ac_gee = ACOLITE(ac, settings)
corrected, _ = ac_gee.correct(images)
Available LUT names
LUT names depend on the ACOLITE version and are loaded from ACOLITE/data/LUT/.
Use acolite.aerlut.import_luts('S2A_MSI', settings) to list available models.
Example 3 — With Ancillary Atmospheric Data¶
Retrieve pressure, ozone, and water vapour from NASA Earthdata automatically for each image.
settings = {
's2_target_res': 10,
'dsf_spectrum_option': 'darkest',
'dsf_model_selection': 'min_drmsd',
'ancillary_data': True,
'EARTHDATA_u': 'your_username',
'EARTHDATA_p': 'your_password',
'l2w_parameters': ['spm_nechad2016', 'chl_oc3'],
}
ac_gee = ACOLITE(ac, settings)
corrected, _ = ac_gee.correct(images)
Example 4 — Time Series with Cloud Probability Masking¶
Process a multi-month time series with enhanced cloud and shadow masking.
from gee_acolite.utils.search import search_with_cloud_proba
import pandas as pd
import datetime
roi = ee.Geometry.Rectangle([-0.40, 39.27, -0.28, 39.38])
images = search_with_cloud_proba(roi, '2023-01-01', '2023-12-31', tile='30SYJ')
images = images.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 20))
print(f"Found {images.size().getInfo()} images")
settings = {
's2_target_res': 10,
'dsf_spectrum_option': 'darkest',
'dsf_model_selection': 'min_drmsd',
'pressure': 1013.25,
'uoz': 0.3,
'uwv': 1.5,
'wind': 3.0,
's2_cloud_proba': True,
's2_cloud_proba__cloud_threshold': 50,
's2_cloud_proba__nir_dark_threshold': 0.15,
's2_cloud_proba__cloud_proj_distance': 10,
's2_cloud_proba__buffer': 50,
'l2w_parameters': ['spm_nechad2016', 'chl_oc3'],
}
ac_gee = ACOLITE(ac, settings)
corrected, final_settings = ac_gee.correct(images)
# Extract SPM time series at a point
point = ee.Geometry.Point([-0.35, 39.32])
def extract_spm(image):
val = image.select('spm_nechad2016').reduceRegion(
reducer=ee.Reducer.mean(),
geometry=point.buffer(100),
scale=10,
)
return image.set('spm_mean', val.get('spm_nechad2016'))
ts = corrected.map(extract_spm)
dates = ts.aggregate_array('system:time_start').getInfo()
values = ts.aggregate_array('spm_mean').getInfo()
df = pd.DataFrame({
'date': [datetime.datetime.fromtimestamp(d / 1000) for d in dates],
'spm_mg_L': values,
}).sort_values('date')
print(df.head())
Example 5 — Download Corrected Images as GeoTIFF¶
Download corrected scenes locally using geemap. Two options: a single best-pixel mosaic or one file per scene.
5.1 — Single mosaic¶
import geemap
from datetime import datetime, timedelta
from gee_acolite.utils.search import search_list
from gee_acolite import bathymetry
bands = [
'Rrs_B1', 'Rrs_B2', 'Rrs_B3', 'Rrs_B4',
'Rrs_B5', 'Rrs_B6', 'Rrs_B7', 'Rrs_B8', 'Rrs_B8A',
'Rrs_B11', 'Rrs_B12',
'pSDB_green', 'pSDB_red',
'tur_nechad2016', 'chl_oc3',
]
roi = ee.Geometry.Rectangle([-0.40, 39.27, -0.28, 39.38])
starts = ['2018-09-19', '2018-10-04', '2018-10-17']
ends = [
(datetime.strptime(d, '%Y-%m-%d') + timedelta(days=1)).strftime('%Y-%m-%d')
for d in starts
]
corrector = ACOLITE(ac, settings)
collection = search_list(roi, starts, ends, tile='30SYJ')
corrected, _ = corrector.correct(collection)
mosaic = bathymetry.multi_image(corrected)
geemap.download_ee_image(
image=mosaic.select(bands),
filename='output/mosaic.tif',
region=roi,
crs='EPSG:32630',
scale=10,
dtype='float32',
)
Masked pixels and NaN
GEE exports masked pixels as nodata. When opening the result with rasterio or xarray, use masked=True or set nodata=float('nan') to avoid treating them as valid zeros or infinities.
5.2 — One file per scene¶
filenames = [f'{d.replace("-", "_")}.tif' for d in starts]
geemap.download_ee_image_collection(
collection=corrected.select(bands),
out_dir='output/scenes/',
filenames=filenames,
region=roi,
crs='EPSG:32630',
scale=10,
dtype='float32',
)
Example 6 — Satellite-Derived Bathymetry (Full Workflow)¶
End-to-end bathymetry: pSDB computation, optical filtering, and calibration against in-situ depths.
6.1 — Compute pSDB¶
from gee_acolite.bathymetry import multi_image, optical_deep_water_model
from gee_acolite.bathymetry import calibrate_sdb, apply_calibration
roi = ee.Geometry.Rectangle([-0.40, 39.27, -0.28, 39.38])
images = search(roi, '2022-06-01', '2022-09-30', tile='30SYJ')
images = images.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 5))
settings = {
's2_target_res': 10,
'dsf_spectrum_option': 'darkest',
'dsf_model_selection': 'min_drmsd',
'pressure': 1013.25,
'uoz': 0.3,
'uwv': 1.5,
'wind': 2.5,
'l2w_parameters': ['pSDB_green', 'pSDB_red'],
}
ac_gee = ACOLITE(ac, settings)
corrected, _ = ac_gee.correct(images)
6.2 — Best-Pixel Mosaic¶
# Quality mosaic: selects best pixel across all scenes
psdb_mosaic_green = multi_image(corrected, band='pSDB_green')
psdb_mosaic_red = multi_image(corrected, band='pSDB_red')
multi_image() uses ee.ImageCollection.qualityMosaic(band) to pick the highest-value pixel per location, corresponding to the clearest water observation.
6.3 — Optical Deep-Water Filter¶
Removes pixels that are optically too deep or turbid for reliable depth retrieval.
blue_mosaic = corrected.select('rhos_B2').mosaic()
green_mosaic = corrected.select('rhos_B3').mosaic()
nir_mosaic = corrected.select('rhos_B8').mosaic()
psdb_filtered = optical_deep_water_model(
model=psdb_mosaic_green,
blue=blue_mosaic,
green=green_mosaic,
vnir=nir_mosaic,
)
Filtering criteria applied:
| Criterion | Equation | Purpose |
|---|---|---|
| Clear water | B2 > 0.003 AND B3 > 0.003 |
Remove optically shallow pixels |
| Turbid depth limit | Ymax = -0.251 * ln(NIR) + 0.8 |
Limit depth in turbid water |
6.4 — Calibrate Against In-Situ Data¶
# Load in-situ bathymetry (GEE Image with depth values in meters)
insitu = ee.Image('projects/your-project/assets/insitu_bathymetry_2022')
calibration = calibrate_sdb(
psdb_image=psdb_filtered,
insitu_bathymetry=insitu,
region=roi,
max_depth=15.0, # Maximum depth considered for calibration
num_points=50, # Number of random calibration points
seed=42,
scale=10,
)
print(f"Slope: {calibration['slope']:.4f}")
print(f"Intercept: {calibration['intercept']:.4f}")
print(f"Pearson R: {calibration['correlation']:.4f}")
# Apply linear calibration: depth = slope * pSDB + intercept
sdb_map = apply_calibration(
psdb_image=psdb_filtered,
slope=calibration['slope'],
intercept=calibration['intercept'],
output_name='depth_m',
)
6.5 — Visualize and Export¶
import geemap
Map = geemap.Map()
Map.centerObject(roi, 12)
Map.addLayer(
sdb_map.select('depth_m'),
{'min': 0, 'max': 15, 'palette': ['navy', 'blue', 'cyan', 'white']},
'SDB Depth (m)',
)
Map.addLayer(
psdb_mosaic_green,
{'min': 1.0, 'max': 1.35, 'palette': ['white', 'cyan', 'blue', 'navy']},
'pSDB green (raw)',
)
Map
# Export
task = ee.batch.Export.image.toDrive(
image=sdb_map.clip(roi),
description='sdb_calibrated',
folder='GEE_ACOLITE',
fileNamePrefix='sdb_2022_summer',
region=roi,
scale=10,
maxPixels=1e9,
crs='EPSG:32630',
)
task.start()
Example 7 — Multi-Year Bathymetry Mosaic¶
Combine summer seasons from multiple years for maximum benthic coverage.
from gee_acolite.bathymetry import multi_image
def process_summer(year, roi, tile):
images = search(roi, f'{year}-06-01', f'{year}-09-30', tile=tile)
images = images.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 5))
if images.size().getInfo() == 0:
return None
settings = {
's2_target_res': 10,
'dsf_spectrum_option': 'darkest',
'dsf_model_selection': 'min_drmsd',
'pressure': 1013.25, 'uoz': 0.3, 'uwv': 1.5, 'wind': 2.5,
'l2w_parameters': ['pSDB_green', 'pSDB_red'],
}
ac_gee = ACOLITE(ac, settings)
corrected, _ = ac_gee.correct(images)
return corrected
# Process 2020-2023 summer seasons
all_collections = [
col for year in [2020, 2021, 2022, 2023]
if (col := process_summer(year, roi, '30SYJ')) is not None
]
combined = ee.ImageCollection(all_collections).flatten()
best_green = multi_image(combined, band='pSDB_green')
best_red = multi_image(combined, band='pSDB_red')
print("Multi-year mosaic created.")