qin5·intermediate

Is terminal heat stressing the wheat during grain-fill this season?

landagriculturefood-securityclimate Datasets: 6 15–30 min

Wheat spends its last few weeks filling its grain, pumping starch into the kernels before harvest. A burst of late-season heat in that window, called terminal heat stress, cuts the fill short and shrivels the harvest. This question watches two warning signs from space: how hot the fields are running, and whether the crop is browning off (drying down) earlier than it should.

Is terminal heat stressing the wheat during grain-fill this season?

Wheat spends its last few weeks filling its grain, pumping starch into the kernels before harvest. A burst of late-season heat in that window, called terminal heat stress, cuts the fill short and shrivels the harvest. This question watches two warning signs from space: how hot the fields are running, and whether the crop is browning off (drying down) earlier than it should.

Which data to use, and how

Use this dataset: MODIS MOD11A2 LST (short name MOD11A2). LST means land-surface temperature, how hot the actual ground and crop canopy are, measured from orbit. This product gives you one temperature map every 8 days at ~1 km resolution (each pixel is a 1 km square), going back to 2000. Start here because it’s a long, steady record with one number per pixel.

Then do four steps. The runnable code further down does all of this; this is just the idea.

  1. Pick your field or region (a small latitude/longitude box around it) and find your grain-fill window: the few weeks when wheat is filling grain, usually Feb–March in the Indo-Gangetic plain, but set it from your own sowing date.
  2. Map the heat. Pull MODIS LST over that window and see which fields run hottest. It gives a separate daytime and nighttime temperature.
  3. Count the hot days. For a true heat threshold you need air temperature, not surface temperature, so bring in ERA5 2 m air temperature and count the days the daily high (Tmax) goes over a crop-specific cutoff. Get that cutoff from a local farming-weather (agromet) source. Don’t invent one.
  4. Watch for early browning. Pull MODIS MOD13Q1 NDVI, a greenness index (high = lush green, falling = drying/dying), and check whether it turns down earlier this year than in a normal year. An early down-turn is the senescence (the crop’s natural end-of-life browning) arriving too soon, a possible heat-stress sign.

That’s the whole method. Below, “How a scientist answers this” names the exact tools, and the Run it cell does it live on synthetic data so you can see each step work.

What you can find out

  • How many hot days hit during the grain-fill window over your area (using ERA5 air temperature against a local threshold).
  • Which fields run hottest: the canopy/surface heating pattern from MODIS LST (~1 km), or ECOSTRESS (~70 m) for a much finer field-scale view.
  • Whether the crop is browning early: does NDVI turn down sooner than in a normal/reference year?
  • This season vs a typical one, compared against the usual grain-fill window.
  • Day vs night heat. MODIS gives both, and warm nights also stress grain-fill (the plant burns energy respiring instead of storing it).

What it can’t tell you

  • An exact yield loss in kg/ha. These are stress indicators, not a yield model. They tell you something looks wrong, not how much grain you’ll lose. For numbers you need agronomic crop models plus ground data.
  • One universal heat threshold. The “too hot” cutoff depends on the wheat variety and the growth stage, so there’s no single magic number to hard-code. Take it from local agromet guidance.
  • Air temperature straight from the satellite. MODIS and ECOSTRESS measure the surface/canopy temperature, not the 2 m air temperature a weather station reports. For real threshold work use ERA5 or station data.
  • Hour-by-hour heat spikes from MODIS alone. MODIS only flies over ~1–2 times a day, so it can miss a short afternoon spike. For sub-daily detail use ERA5 hourly data or a geostationary satellite (one parked over the same region).

Gotchas to watch for

  • Surface temperature is not air temperature (LST ≠ air temp). MODIS/ECOSTRESS report the skin/canopy temperature, which on clear dry days can run several °C hotter than the 2 m air temperature. Hold LST up against an air-temp threshold and you’ll over-count hot days. Keep ERA5 or station air temperature for the actual threshold, and lean on LST for the spatial pattern of where it’s hottest.
  • Clouds blank out the view. Optical LST and NDVI need a clear sky, so cloud (and the Feb–March haze/fog over the Indo-Gangetic plain) leaves gaps. Composite or gap-fill over a window rather than trusting a single day.
  • The window must match the sowing date. Late-sown wheat fills grain later, so a fixed Feb–March window can miss the real vulnerable weeks entirely. Align the window to the local sowing calendar.
  • The threshold isn’t one-size-fits-all. Terminal-heat cutoffs depend on cultivar and stage. Verify against IMD/agromet advisories rather than asserting a single number.
  • 1 km pixels are mixed. A single MODIS 1 km pixel blends wheat with roads, villages, and other crops, smearing the reading. For smallholder fields, reach for HLS (30 m) or ECOSTRESS (~70 m) to get finer detail.

The real-data code

The Run it cell above runs this method on synthetic data with no login. Below is the whole recipe against the real archive: search, open the granules, map the grain-fill heat, count hot canopy days, compare this season’s NDVI down-turn to a normal year, and plot it. It needs a free Earthdata Login.

import earthaccess, numpy as np, xarray as xr
import matplotlib.pyplot as plt

earthaccess.login(strategy="netrc")

aoi = (75, 28, 82, 30.5)      # Indo-Gangetic wheat belt as (W, S, E, N)
this_year, ref_year = 2026, 2024
grainfill = (f"{this_year}-02-10", f"{this_year}-03-31")  # SET FROM LOCAL SOWING DATE
lst_threshold_c = 33.0        # canopy-heat flag; SET FROM LOCAL AGROMET GUIDANCE

# 1. MODIS LST (MOD11A2, 8-day, 1 km) over the grain-fill window. The gridded
#    HDF-EOS opens with xarray; scale daytime LST (×0.02, in K) and go to °C.
lst_grans = earthaccess.search_data(short_name="MOD11A2", bounding_box=aoi, temporal=grainfill)
day_c = []
for fh in earthaccess.open(lst_grans):
    ds = xr.open_dataset(fh, engine="rasterio")          # HDF-EOS grid via GDAL
    lst_k = ds["LST_Day_1km"].where(ds["LST_Day_1km"] > 0) * 0.02
    day_c.append(lst_k.squeeze().values - 273.15)
lst_c = np.stack(day_c)                                   # (time, y, x) in °C

# 2. Map the spatial pattern of peak canopy heat, and count hot canopy days.
peak_lst = np.nanmax(lst_c, axis=0)                       # hottest 8-day value per pixel
hot_days = np.nansum(lst_c > lst_threshold_c, axis=0)     # 8-day periods over threshold
print(f"Peak grain-fill canopy LST: {np.nanmax(peak_lst):.1f} °C, "
      f"area-mean {np.nanmean(peak_lst):.1f} °C; "
      f"hottest pixel had {int(np.nanmax(hot_days))} of {lst_c.shape[0]} periods "
      f"above {lst_threshold_c:.0f} °C")

# 3. NDVI senescence timing (MOD13Q1, 250 m): build an area-mean greenness curve
#    for this season and a reference year; an earlier down-turn flags heat stress.
def ndvi_curve(year):
    grans = earthaccess.search_data(short_name="MOD13Q1", bounding_box=aoi,
                                    temporal=(f"{year}-01-01", f"{year}-04-30"))
    days, vals = [], []
    for fh in earthaccess.open(grans):
        ds = xr.open_dataset(fh, engine="rasterio")
        ndvi = (ds["_250m_16_days_NDVI"].where(ds["_250m_16_days_NDVI"] > -2000) * 1e-4)
        days.append(int(str(ds.attrs.get("RANGEBEGINNINGDATE", f"{year}-01-01"))[5:7]))
        vals.append(float(ndvi.mean()))
    return np.array(days), np.array(vals)
m_now, n_now = ndvi_curve(this_year)
m_ref, n_ref = ndvi_curve(ref_year)
peak_now, peak_ref = m_now[np.nanargmax(n_now)], m_ref[np.nanargmax(n_ref)]
verdict = "EARLY browning — possible terminal heat stress" if peak_now < peak_ref else "on schedule"
print(f"NDVI peak (greenness max) this year ~month {peak_now} vs ref ~month {peak_ref}: {verdict}")

# 4. Plot: heat map + the two NDVI curves side by side.
fig, (a, b) = plt.subplots(1, 2, figsize=(11, 4))
im = a.imshow(peak_lst, cmap="inferno"); a.set_title("Peak grain-fill LST (°C)")
fig.colorbar(im, ax=a, shrink=0.8)
b.plot(m_ref, n_ref, "o-", label=f"{ref_year} (reference)")
b.plot(m_now, n_now, "o-", label=f"{this_year}")
b.set_xlabel("month"); b.set_ylabel("area-mean NDVI"); b.set_title("Senescence timing"); b.legend()
plt.tight_layout(); plt.show()

# Cross-check before trusting it: LST is canopy SKIN temp, not 2 m air temp — count true
# heat days against ERA5 Tmax (or an IMD station) before calling a threshold breach (see gotchas).

Where the data comes from

Most of this is NASA data through one free login (Earthdata Login): the MODIS LST and NDVI products, ECOSTRESS, and HLS all come from NASA’s LP DAAC archive. The one outside piece is ERA5 air temperature, a non-NASA source (ECMWF/Copernicus, via the Climate Data Store). You need it because satellites can’t measure true 2 m air temperature directly, and that’s what heat thresholds are defined on.

Sources

How a scientist answers this
Parameters
Land-surface temperature (MODIS MOD11 LST, 1 km, day/night) and canopy temperature (ECOSTRESS, ~70 m) during the Feb–Mar wheat grain-filling window, alongside 2 m air temperature from ERA5 reanalysis for true threshold work, and crop greenness/senescence from NDVI (MODIS MOD13, HLS). Terminal heat stress is heat occurring late in the season during grain-fill, which shortens the fill period and cuts yield.
Method
Define the grain-fill window from the local sowing calendar (commonly Feb–Mar in the Indo-Gangetic plain), then count terminal-heat days — days where Tmax exceeds a crop-specific threshold during that window — using ERA5 for air temperature, with MODIS/ECOSTRESS LST showing canopy heating and spatial pattern. Separately, detect early senescence as an NDVI down-turn that arrives earlier than in a reference/normal year. Compare both against the climatological window. The exact heat cutoff varies by cultivar and study (often discussed near the low-to-mid 30s °C for sustained grain-fill stress) — pick the threshold from a local agromet source rather than hard-coding one.
Validation
LST is the skin/canopy temperature of the surface, not the 2 m air temperature an agromet station reports — on sunny dry days canopy LST can run several degrees above air temperature, so do not treat them as interchangeable. Account for cloud gaps in optical LST/NDVI, make sure the grain-fill window matches the actual local sowing date, and cross-check counts against IMD agromet advisories and ground stations.
In plain EnglishLate-season heat during the few weeks when wheat is filling its grain can shrivel the harvest. This counts the hot days in that window and watches whether the crop browns off early compared with a normal year — a warning sign, not a yield number.

Make it yours → Set your field/region, the grain-fill window from your sowing date, and your local heat threshold; the notebook counts hot days and flags early senescence. Swap MODIS 1 km for ECOSTRESS ~70 m or HLS 30 m when you need field-scale detail.

🧪 Virtual Mock Lab stand-in data · runs in your browser · no login

A safe place to practise the method on MOD11A2. The code is the real earthaccess workflow, but we feed it stand-in data shaped like the real product — so you can run it, change the numbers, and break it without an account. When you want the real data, use the recipe under “The real-data code” above, or download the notebook below.

editable · runs in your browser

Datasets used

Computed · real data Worked examples, computed end-to-end on real satellite data — not yet scientist-verified · expand ▾
Real trends, computed on Google Earth Engine (area-weighted Theil–Sen + Mann–Kendall).

We ran the analysis on real data for Indian regions and report exactly what came back — including the honest nulls and refusals. Each card shows the slope, significance, and (for rainfall) a gauge cross-check.

How has average temperature over the Indo-Gangetic Plain changed from 1980 to 2020?
temperature Indo-Gangetic Plain 1980–2020
+0.016 degC (trend) significant trend
95% CI [0.003, 0.03] Mann–Kendall p = 0.0059

Prior: IMD/IPCC consensus: India land-surface warming ~0.6-0.7C/century, accelerating.

✓ Ground-truth: gauges agree IMD gauges ↑* · ERA5 ↑ all sources agree on direction. IMD = India Meteorological Dept. gauge grid (the ground-truth); * = significant.
computed · ERA5/GEE · not yet scientist-verified
How has average temperature over Tamil Nadu changed from 1980 to 2020?
temperature Tamil Nadu 1980–2020
+0.02 degC (trend) significant trend
95% CI [0.011, 0.031] Mann–Kendall p = 0

Prior: IMD/IPCC consensus: India land-surface warming.

✓ Ground-truth: gauges agree IMD gauges ↑* · ERA5 ↑ all sources agree on direction. IMD = India Meteorological Dept. gauge grid (the ground-truth); * = significant.
computed · ERA5/GEE · not yet scientist-verified
How has average temperature over Karnataka changed from 1980 to 2020?
temperature Karnataka 1980–2020
+0.02 degC (trend) significant trend
95% CI [0.009, 0.029] Mann–Kendall p = 0

Prior: IMD/IPCC consensus: India land-surface warming.

✓ Ground-truth: gauges agree IMD gauges ↑* · ERA5 ↑ all sources agree on direction. IMD = India Meteorological Dept. gauge grid (the ground-truth); * = significant.
computed · ERA5/GEE · not yet scientist-verified
How has average temperature over Gujarat changed from 1980 to 2020?
temperature Gujarat 1980–2020
+0.018 degC (trend) significant trend
95% CI [0.009, 0.026] Mann–Kendall p = 0.0007

Prior: IMD/IPCC consensus: India land-surface warming.

✓ Ground-truth: gauges agree IMD gauges ↑* · ERA5 ↑ all sources agree on direction. IMD = India Meteorological Dept. gauge grid (the ground-truth); * = significant.
computed · ERA5/GEE · not yet scientist-verified
How has average temperature over Rajasthan changed from 1980 to 2020?
temperature Rajasthan 1980–2020
+0.013 degC (trend) significant trend
95% CI [0, 0.03] Mann–Kendall p = 0.0338

Prior: IMD/IPCC consensus: India land-surface warming.

✓ Ground-truth: gauges agree IMD gauges ↑* · ERA5 ↑ all sources agree on direction. IMD = India Meteorological Dept. gauge grid (the ground-truth); * = significant.
computed · ERA5/GEE · not yet scientist-verified
How has average temperature over West Bengal changed from 1980 to 2020?
temperature West Bengal 1980–2020
+0.018 degC (trend) significant trend
95% CI [0.006, 0.026] Mann–Kendall p = 0.0007

Prior: IMD/IPCC consensus: India land-surface warming.

✓ Ground-truth: gauges agree IMD gauges ↑ · ERA5 ↑ all sources agree on direction. IMD = India Meteorological Dept. gauge grid (the ground-truth); * = significant.
computed · ERA5/GEE · not yet scientist-verified
How has average temperature over Madhya Pradesh changed from 1980 to 2020?
temperature Madhya Pradesh 1980–2020
+0.016 degC (trend) significant trend
95% CI [0.006, 0.028] Mann–Kendall p = 0.0013

Prior: IMD/IPCC consensus: India land-surface warming.

✓ Ground-truth: gauges agree IMD gauges ↑* · ERA5 ↑ all sources agree on direction. IMD = India Meteorological Dept. gauge grid (the ground-truth); * = significant.
computed · ERA5/GEE · not yet scientist-verified
How has average temperature over Odisha changed from 1980 to 2020?
temperature Odisha 1980–2020
+0.01 degC (trend) significant trend
95% CI [0.002, 0.017] Mann–Kendall p = 0.0052

Prior: IMD/IPCC consensus: India land-surface warming.

✓ Ground-truth: gauges agree IMD gauges ↑ · ERA5 ↑ all sources agree on direction. IMD = India Meteorological Dept. gauge grid (the ground-truth); * = significant.
computed · ERA5/GEE · not yet scientist-verified
How has average temperature over Assam changed from 1980 to 2020?
temperature Assam 1980–2020
+0.024 degC (trend) significant trend
95% CI [0.017, 0.031] Mann–Kendall p = 0

Prior: IMD/IPCC consensus: India land-surface warming.

✓ Ground-truth: gauges agree IMD gauges ↑* · ERA5 ↑* all sources agree on direction. IMD = India Meteorological Dept. gauge grid (the ground-truth); * = significant.
computed · ERA5/GEE · not yet scientist-verified
How has average temperature over the Delhi NCR changed from 1980 to 2020?
temperature Delhi NCR 1980–2020
+0.009 degC (trend) no significant trend
95% CI [-0.005, 0.026] Mann–Kendall p = 0.1965

The data doesn't support a temperature change here — an earned null, not a missing answer.

Prior: IMD/IPCC consensus: India land-surface warming.

⚠ Ground-truth: gauges disagree IMD gauges ↑ · ERA5 ↓ sources DISAGREE on direction — treat the trend as unresolved. IMD = India Meteorological Dept. gauge grid (the ground-truth); * = significant.
computed · ERA5/GEE · not yet scientist-verified
How has average temperature over Mumbai & the Konkan coast changed from 1980 to 2020?
temperature Mumbai & Konkan 1980–2020
+0.022 degC (trend) significant trend
95% CI [0.015, 0.03] Mann–Kendall p = 0

Prior: IMD/IPCC consensus: India land-surface warming.

✓ Ground-truth: gauges agree IMD gauges ↑* · ERA5 ↑* all sources agree on direction. IMD = India Meteorological Dept. gauge grid (the ground-truth); * = significant.
computed · ERA5/GEE · not yet scientist-verified

…and what it refuses to answer

How has temperature changed in 'the valley' since 1990?
temperature 1990–2020
⊘ Asks to clarify

unresolved region name — the validator refuses ill-posed questions rather than guessing. That discipline is the point.

validator-enforced refusal

See all verified answers →