[Python] Google Earth Engine: Exported NDVI Rasters are Empty Despite Correct Visualization

Discussão em 'Python' iniciado por Stack, Fevereiro 20, 2025.

  1. Stack

    Stack Membro Participativo

    I am using Google Earth Engine (GEE) to export monthly median NDVI rasters from the MODIS MOD13Q1 dataset, clipped to a specific fire polygon.

    Issue

    • NDVI visualizes correctly in the GEE Console (with expected values).
    • Exported GeoTIFFs are completely empty (NaN values) despite no errors in the logs.
    • The script successfully initiates exports, but the files contain no valid NDVI data when opened in QGIS or Python (rasterio).
    What I've Tried

    1️⃣ Visualizing NDVI in GEE Console (Works)


    I confirmed that MODIS NDVI data exists for my region by displaying the median NDVI for a month:

    var fireRegion = ee.Geometry.Polygon([
    [[-121.974, 37.634], [-121.974, 36.972], [-120.700, 36.972], [-120.700, 37.634]]
    ]);

    var modis = ee.ImageCollection("MODIS/061/MOD13Q1")
    .select("NDVI")
    .filterBounds(fireRegion)
    .filterDate("2020-08-01", "2020-08-31")
    .median()
    .multiply(0.0001) // Scale NDVI correctly
    .clip(fireRegion);

    var ndviVis = {min: -0.2, max: 1.0, palette: ["red", "yellow", "green"]};

    Map.centerObject(fireRegion, 8);
    Map.addLayer(modis, ndviVis, "MODIS NDVI (August 2020)");
    print("NDVI Image:", modis);


    ✅ NDVI renders correctly on the GEE map. 2️⃣ Batch Export in Python (Fails)

    I wrote a script in Python to export NDVI for each month from 2019-2022.

    import ee
    import geopandas as gpd
    import logging
    from datetime import datetime, timedelta

    # Set up logging
    logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")

    # Initialize Earth Engine
    try:
    ee.Initialize(project="sound-of-resiliency")
    except Exception as e:
    ee.Authenticate()
    ee.Initialize(project="sound-of-resiliency")

    # Load the shapefile
    shapefile = gpd.read_file("fire_poly.shp")

    # Ensure the shapefile is in WGS84 (EPSG:4326)
    if shapefile.crs != "EPSG:4326":
    shapefile = shapefile.to_crs("EPSG:4326")

    # Convert shapefile to an EE geometry
    fire_region_geom = shapefile.geometry.union_all()
    fire_region = ee.Geometry(fire_region_geom.__geo_interface__)

    # Load MODIS NDVI dataset
    modis = ee.ImageCollection("MODIS/061/MOD13Q1").select("NDVI").filterBounds(fire_region)

    def get_monthly_ndvi(year, month):
    """Compute median NDVI for all images within a month."""
    start = datetime(year, month, 1)
    end = (start + timedelta(days=32)).replace(day=1) # First day of next month

    monthly_collection = modis.filterDate(start.strftime("%Y-%m-%d"), end.strftime("%Y-%m-%d"))

    # Debugging: Print number of images found
    image_count = monthly_collection.size().getInfo()
    logging.info(f"MODIS images found for {year}-{month}: {image_count}")

    if image_count == 0:
    logging.warning(f"No MODIS NDVI images found for {year}-{month}. Skipping.")
    return None

    monthly_ndvi = (
    monthly_collection.median()
    .multiply(0.0001) # Convert to NDVI scale (-1 to 1)
    .clip(fire_region)
    )

    return monthly_ndvi

    def export_ndvi_raster(image, year, month):
    """Export NDVI raster to Google Drive if valid."""
    if image is None:
    logging.warning(f"Skipping export for {year}-{str(month).zfill(2)} (No NDVI data)")
    return

    image = image.reproject(crs="EPSG:4326", scale=250)

    stats = image.reduceRegion(
    reducer=ee.Reducer.mean(),
    geometry=fire_region,
    scale=250,
    maxPixels=1e13
    )

    ndvi_value = stats.get("NDVI")

    if ndvi_value is not None:
    task = ee.batch.Export.image.toDrive(
    image=image,
    description=f"NDVI_{year}_{str(month).zfill(2)}",
    folder="GEE_Exports",
    fileNamePrefix=f"NDVI_{year}_{str(month).zfill(2)}",
    scale=250,
    region=fire_region,
    fileFormat="GeoTIFF",
    maxPixels=1e13
    )
    task.start()
    logging.info(f"Export started for NDVI {year}-{str(month).zfill(2)}")
    else:
    logging.warning(f"Skipping export for {year}-{str(month).zfill(2)} (No valid NDVI pixels)")

    for year in range(2019, 2023):
    for month in range(1, 13):
    logging.info(f"Processing NDVI for {year}-{str(month).zfill(2)}")
    monthly_ndvi = get_monthly_ndvi(year, month)
    export_ndvi_raster(monthly_ndvi, year, month)

    logging.info("All NDVI exports have been started. Check GEE tasks panel.")


    3️⃣ Checking the Exported .tif Files

    After downloading the .tif files from Google Drive, I analyzed them using Python (rasterio):

    import rasterio
    import numpy as np

    with rasterio.open("NDVI_2022_12.tif") as src:
    data = src.read(1)
    print(f"Min: {np.nanmin(data)}, Max: {np.nanmax(data)}, Mean: {np.nanmean(data)}")

    Output:

    Min: nan, Max: nan, Mean: nan


    ✅ The raster has data, but all values are NaN. Things I Have Checked

    ✅ Fire polygon (fire_region) is correctly defined (since NDVI visualizes correctly).
    ✅ MODIS NDVI images exist for each month (verified with size().getInfo()).
    ✅ NDVI is properly scaled (multiply(0.0001)).
    ✅ Reprojection to EPSG:4326 is applied before export.
    ✅ The export is correctly initiated and appears in GEE "Tasks".
    ❌ But the exported rasters contain only NaN values.


    ❓ Questions

    Why does the NDVI render fine in GEE but export as NaN when using .median()?
    Is there a better way to compute and export monthly NDVI from MOD13Q1?
    Could the .median() operation be causing an issue with missing pixels?
    Should scale=250 be modified during export, or is MODIS using an incompatible projection?


    Any help would be greatly appreciated! Thanks in advance.

    Continue reading...

Compartilhe esta Página