Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Access and Visualize HyTES Data

Overview

The Hyperspectral Thermal Emission Spectrometer (HyTES) is now available through NASA Earthdata and the Earthdata Cloud as airborne Facility Instrument data.

In this tutorial, we will use earthaccess to discover, access, and visualize HyTES data over a region of interest — the Langebaan Lagoon Marine Protected Area in South Africa, acquired during the BioSCape campaign.

The HyTES instrument is an airborne imaging spectrometer with 256 spectral channels between 7.5 and 12 micrometers in the thermal infrared (TIR) part of the electromagnetic spectrum and 512 pixels cross-track. HyTES provides high spatial and high spectral resolution data on surface temperature and emissivity. TIR data are used to measure land surface temperature (LST), which informs models of water flux from land surface through processes such as evapotranspiration and supports applications like point-source pollution detection and volcano monitoring.

Datasets Used

DatasetDOIConcept ID
HyTES Level 1 Radiance, Facility Instrument CollectionHook et al. (2026)C4205447735-ORNL_CLOUD
HyTES L1 Geolocation, Facility Instrument CollectionHook et al. (2026)C4254189775-ORNL_CLOUD
HyTES: Level 2 Emissivity and Land Surface TemperatureHulley et al. (2026)C4247014847-ORNL_CLOUD

Learning Objectives

  • Discover available HyTES datasets on NASA Earthdata using earthaccess

  • Search for granules by bounding box and date range, and extract CMR footprint geometries to map flight lines

  • Stream HyTES L1 Radiance and L2 Land Surface Temperature data directly from the Earthdata Cloud

  • Visualize flight paths, L1 radiance bands, and L2 LST/emissivity

Prerequisites

A free NASA Earthdata Login account is required. See the prerequisites page for setup instructions.

Citation

Hook, S. J., Hulley, G. C., La, T. T., Rivera, G., Johnson, W. R., & Eng, B. T. (2026). HyTES Level 1 Radiance, Facility Instrument Collection (Version 1). ORNL DAAC. Hook et al. (2026)

Hook, S. J., Hulley, G. C., La, T. T., Rivera, G., Johnson, W. R., & Eng, B. T. (2026). HyTES: Level 2 Emissivity and Land Surface Temperature (Version 1). ORNL DAAC. Hulley et al. (2026)

Import Libraries

import earthaccess
import xarray as xr
import numpy as np
import pandas as pd
import geopandas as gpd
import hvplot.xarray
import hvplot.pandas
import holoviews as hv
import panel as pn
from os import path
hv.extension('bokeh')
import warnings
warnings.filterwarnings('ignore')

# ESRI World Imagery basemap
xyz = "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}"
attr = "ESRI"
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...

Authenticate with NASA Earthdata

earthaccess.login() handles authentication with your NASA Earthdata Login credentials. It will look for stored credentials first, then prompt if not found. Credentials can be persisted with persist=True.

auth = earthaccess.login()

Discover HyTES Datasets

We can use earthaccess.search_datasets() to list all HyTES datasets available on NASA Earthdata.

datasets = earthaccess.search_datasets(instrument="HyTES")
print(f"HyTES datasets found: {len(datasets)}\n")
for ds in datasets:
    summary = ds.summary()
    print(f"  Short name : {summary.get('short-name', 'N/A')}")
HyTES datasets found: 11

  Short name : HyTES_Calibration_Data_2468
  Short name : HyTES_L1_Geolocation_2459
  Short name : HyTES_L1_Geolocation_2013_2016_2467
  Short name : HyTES_L1_Flight_Path_2463
  Short name : HyTES_L1_Radiance_Overlays_2466
  Short name : HyTES_L1_Radiance_2462
  Short name : HyTES_L1_Radiance_2013_2016_2476
  Short name : HyTES_L2_Emissivity_Overlays_2469
  Short name : HyTES_L2_Emissivity_LST_2460
  Short name : SHIFT_HyTES_L1_2022_2245
  Short name : SHIFT_HyTES_L2_2022_2246

Study Area

We will use the boundary of Langebaan Lagoon Marine Protected Area as our region of interest (ROI). Langebaan is a coastal conservation area in South Africa declared as a Ramsar Site. This area was surveyed during the 2023 BioSCape campaign.

A local GeoJSON of the study area polygon is available in the data/ directory.

# Load the study area boundary
roi_gdf = gpd.read_file(path.join("data", "langebaan.json"))
roi_gdf.explore(color='red', tiles=xyz, attr=attr)
Loading...

Each granule returned by earthaccess.search_data() carries a CMR spatial footprint — the bounding polygon that NASA’s Common Metadata Repository (CMR) stores for that granule. We can extract these footprint polygons directly from the L1 Radiance search results (DOI: Hook et al. (2026)) to map the flight lines without opening any files.

# BioSCape campaign dates: October–November 2023
date_range = ("2023-10-01", "2023-11-30")

# Extract the bounding box of the ROI for earthaccess queries
# bounding_box = (min_lon, min_lat, max_lon, max_lat)
bounds = roi_gdf.total_bounds  # [minx, miny, maxx, maxy]

# Search for L1 Radiance granules over the study area
# We will reuse l1_granules in later sections, so this search covers both purposes
l1_granules = earthaccess.search_data(
    doi="10.3334/ORNLDAAC/2462",
    bounding_box=tuple(bounds),
    temporal=date_range,
)
print(f"L1 Radiance granules found: {len(l1_granules)}")
for g in l1_granules:
    print(f"  {g['meta']['native-id']}")
L1 Radiance granules found: 26
  20231026t124346_LangebaanBox9ZA_L1_B110_V01
  20231026t125601_LangebaanBox9ZA_L1_B110_V01
  20231026t130800_LangebaanBox9ZA_L1_B110_V01
  20231026t132008_LangebaanBox9ZA_L1_B110_V01
  20231026t133258_LangebaanBox9ZA_L1_B110_V01
  20231026t134517_LangebaanBox9ZA_L1_B110_V01
  20231026t135720_LangebaanBox9ZA_L1_B110_V01
  20231026t140938_LangebaanBox9ZA_L1_B110_V01
  20231026t142132_LangebaanBox9ZA_L1_B110_V01
  20231026t143401_LangebaanBox9ZA_L1_B110_V01
  20231026t144600_LangebaanBox9ZA_L1_B110_V01
  20231029t071408_LangebaanBox9ZA_L1_B110_V01
  20231029t071516_LangebaanBox9ZA_L1_B110_V01
  20231029t072634_LangebaanBox9ZA_L1_B110_V01
  20231029t072742_LangebaanBox9ZA_L1_B110_V01
  20231029t074013_LangebaanBox9ZA_L1_B110_V01
  20231029t075159_LangebaanBox9ZA_L1_B110_V01
  20231029t075313_LangebaanBox9ZA_L1_B110_V01
  20231029t080449_LangebaanBox9ZA_L1_B110_V01
  20231029t080559_LangebaanBox9ZA_L1_B110_V01
  20231029t081958_LangebaanBox9ZA_L1_B110_V01
  20231029t082109_LangebaanBox9ZA_L1_B110_V01
  20231029t083439_LangebaanBox9ZA_L1_B110_V01
  20231029t083545_LangebaanBox9ZA_L1_B110_V01
  20231029t084700_LangebaanBox9ZA_L1_B110_V01
  20231029t084759_LangebaanBox9ZA_L1_B110_V01
# Plot CMR footprints over the study area
flight_paths_gdf = gpd.GeoDataFrame(l1_granules, geometry=gpd.GeoSeries(l1_granules, crs=4326))
m = flight_paths_gdf.geometry.explore(
    tiles=xyz, attr=attr, 
    color="yellow",
    style_kwds={"weight": 2, "fillOpacity": 0.1},
    name="L1 Radiance Footprints",
)
roi_gdf.explore(m=m,color="red", name="ROI")
Loading...

SRF and WMX files

Also notice there are *.csv files that are named as HyTES_SRF_* and HyTES_WMX_* available at https://data.ornldaac.earthdata.nasa.gov/public/hytes/HyTES_L1_Radiance/comp/HyTES_SRF_v6.csv and https://data.ornldaac.earthdata.nasa.gov/public/hytes/HyTES_L1_Radiance/comp/HyTES_WMX_v6.csv. These files provides information on Signal Response Function (SRF) and Wave Matrix Data (WMX). Let’s read these CSV files using pandas.

# csv file paths
srf_f = 'https://data.ornldaac.earthdata.nasa.gov/public/hytes/HyTES_L1_Radiance/comp/HyTES_SRF_v6.csv'
wmx_f = 'https://data.ornldaac.earthdata.nasa.gov/public/hytes/HyTES_L1_Radiance/comp/HyTES_WMX_v6.csv'
# open srf files
srf_df = pd.read_csv(srf_f, skiprows=1, index_col='BND')
srf_df.head()
Loading...
# open wmx file
wmx_df = pd.read_csv(wmx_f, skiprows=1, index_col='BND')
wmx_df.head()
Loading...

We can now plot to show how the SRF is distributed for each HyTES bands. Use the slider in the plot to scroll across the bands.

def srf_plot(n):
    """returns wmx srf values for a band"""
    return pd.concat([wmx_df.loc[n], srf_df.loc[n]], 
                     keys = ['WMX','SRF'], axis=1)

# band slider
nbands = pn.widgets.IntSlider(name='Band', start=1, end=256)
# interactive SRF plot 
temp_df = hvplot.bind(srf_plot, nbands).interactive(width=600)
temp_df.hvplot.line(x='WMX', y='SRF')
Loading...

Search for HyTES L1 Radiance and L2 Granules

As we see above, there are two levels of HyTES products currently available for download: HyTES Level 1 (L1) product: Radiance and Locational Information and HyTES Level 2 (L2) product: Emissivity and Land Surface Temperature. The l1_granules from the previous section will be reused here to open and stream the actual radiance data. We also search for co-located L2 Emissivity and LST granules (DOI: 10.3334/ORNLDAAC/2460) over the same area and date range.

# Search for co-located L2 Emissivity and LST granules
l2_granules = earthaccess.search_data(
    doi="10.3334/ORNLDAAC/2460",
    bounding_box=tuple(bounds),
    temporal=date_range,
)
print(f"L2 Emissivity/LST granules found: {len(l2_granules)}")
for g in l2_granules:
    print(f"  {g['meta']['native-id']}")
L2 Emissivity/LST granules found: 26
  20231026t124346_LangebaanBox9ZA_L2_B200_V06
  20231026t125601_LangebaanBox9ZA_L2_B200_V06
  20231026t130800_LangebaanBox9ZA_L2_B200_V06
  20231026t132008_LangebaanBox9ZA_L2_B200_V06
  20231026t133258_LangebaanBox9ZA_L2_B200_V06
  20231026t134517_LangebaanBox9ZA_L2_B200_V06
  20231026t135720_LangebaanBox9ZA_L2_B200_V06
  20231026t140938_LangebaanBox9ZA_L2_B200_V06
  20231026t142132_LangebaanBox9ZA_L2_B200_V06
  20231026t143401_LangebaanBox9ZA_L2_B200_V06
  20231026t144600_LangebaanBox9ZA_L2_B200_V06
  20231029t071408_LangebaanBox9ZA_L2_B200_V06
  20231029t071516_LangebaanBox9ZA_L2_B200_V06
  20231029t072634_LangebaanBox9ZA_L2_B200_V06
  20231029t072742_LangebaanBox9ZA_L2_B200_V06
  20231029t074013_LangebaanBox9ZA_L2_B200_V06
  20231029t075159_LangebaanBox9ZA_L2_B200_V06
  20231029t075313_LangebaanBox9ZA_L2_B200_V06
  20231029t080449_LangebaanBox9ZA_L2_B200_V06
  20231029t080559_LangebaanBox9ZA_L2_B200_V06
  20231029t081958_LangebaanBox9ZA_L2_B200_V06
  20231029t082109_LangebaanBox9ZA_L2_B200_V06
  20231029t083439_LangebaanBox9ZA_L2_B200_V06
  20231029t083545_LangebaanBox9ZA_L2_B200_V06
  20231029t084700_LangebaanBox9ZA_L2_B200_V06
  20231029t084759_LangebaanBox9ZA_L2_B200_V06

We’ll work with one of the matching L1 and L2 granule. earthaccess.open() returns file-like objects that stream data directly from the cloud — no local download needed.

# Open the first L1 and L2 granule from the cloud
l1_files = earthaccess.open([l1_granules[4]], provider="ORNL_CLOUD")
l2_files = earthaccess.open([l2_granules[4]], provider="ORNL_CLOUD")

print(f"L1 file: {l1_files[0].path}")
print(f"L2 file: {l2_files[0].path}")
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
L1 file: ornl-cumulus-prod-protected/hytes/HyTES_L1_Radiance/data/20231026t133258_LangebaanBox9ZA_L1_B110_V01.hdf5
L2 file: ornl-cumulus-prod-protected/hytes/HyTES_L2_Emissivity_LST/data/20231026t133258_LangebaanBox9ZA_L2_B200_V06.hdf5

HyTES L1 Radiance

The L1 data file (HDF-5) provides calibrated radiance in band-interleaved-by-pixel (BIP) format with three dimensions:

  • line — along-track scan lines (variable length)

  • sample — 512 cross-track pixels

  • band — 256 spectral channels from 7.5–12 µm

The file also includes per-pixel geolocation (latitude, longitude).

Let’s open it with xarray and explore its structure.

# Open L1 HDF5 file via xarray (streaming from cloud)
ds_l1_raw = xr.open_datatree(l1_files[0], engine="h5netcdf", chunks="auto", phony_dims="sort")

# Flatten to a single dataset and rename phony dims to meaningful names
ds_l1 = ds_l1_raw.to_dataset().rename({
    "phony_dim_0": "line",
    "phony_dim_1": "sample",
    "phony_dim_2": "band",
})

# # Attach lat/lon as coordinates for easy spatial indexing
# ds_l1.coords["latitude"]  = ds_l1["latitude"]
# ds_l1.coords["longitude"] = ds_l1["longitude"]

print(f"Lines: {ds_l1.sizes['line']}, Samples: {ds_l1.sizes['sample']}, Bands: {ds_l1.sizes['band']}")
ds_l1
Lines: 4456, Samples: 512, Bands: 256
Loading...

Plot a Single Radiance Band

Band 179 is near 10.8 µm, a commonly used window channel for LST retrieval.

ds_l1["radiance_data"].isel(band=179).hvplot.image(
    data_aspect=1,
    aspect="equal",
    cmap="Greys",
    clim=(7, 12),
    frame_width=150,
    title="L1 Radiance — Band 179 (~10.8 µm)",
).opts(invert_yaxis=True)
Loading...

False-Color Radiance Composite

We create a false-color RGB composite using three TIR channels:

  • R: Band 150 (~10.1 µm)

  • G: Band 100 (~9.2 µm)

  • B: Band 58 (~8.5 µm)

ds_rgb = ds_l1["radiance_data"].isel(band=[150, 100, 58])
# Assign integer coords so hvplot can index the band dimension
ds_rgb.coords["sample"] = np.arange(ds_rgb.sizes["sample"])
ds_rgb.coords["line"]   = np.arange(ds_rgb.sizes["line"])
ds_rgb.coords["band"]   = np.arange(ds_rgb.sizes["band"])

ds_rgb.hvplot.rgb(
    x="sample", y="line", bands="band",
    rasterize=True, flip_yaxis=True,
    robust=True, data_aspect=1, aspect="equal",
    frame_width=150,
    title="False-color composite (R:10.1µm, G:9.2µm, B:8.5µm)",
)
Loading...

Radiance Spectra for Selected Surface Types

Let’s compare radiance spectra from three surface types within the scene: inshore water, land, and open water. We find the pixel nearest to each reference location using the per-pixel latitude/longitude arrays.

# Three reference locations (lon, lat)
points_df = pd.DataFrame({
    "Type": ["Inshore", "Land", "Water"],
    "lat":  [-33.15014, -33.21875, -33.04839],
    "lon":  [ 18.06706,  18.11642,  18.00005],
})
points_gdf = gpd.GeoDataFrame(
    points_df,
    geometry=gpd.points_from_xy(points_df.lon, points_df.lat),
    crs="EPSG:4326",
)
points_gdf.explore("Type", marker_kwds={"radius": 6}, tiles=xyz, attr=attr)
Loading...
def get_nearest_pixel(ds, lat, lon):
    """Return the pixel in ds whose lat/lon is closest to the given coordinates."""
    r2 = (ds["latitude"] - lat) ** 2 + (ds["longitude"] - lon) ** 2
    idx = np.where(r2.values == r2.values.min())
    return ds.sel(line=idx[0][0], sample=idx[1][0])

# assign coordinates to the xarray dataset
ds_l1.coords['longitude'] = ds_l1.longitude
ds_l1.coords['latitude'] = ds_l1.latitude

# Retrieve spectra for all three points
spectra = []
for row in points_gdf.itertuples():
    px = get_nearest_pixel(ds_l1, row.lat, row.lon)
    spectra.append(px)

pts = xr.concat(spectra, pd.Index(points_df["Type"].tolist(), name="Surface Type"))

pts["radiance_data"].hvplot.scatter(
    x="band",
    by="Surface Type",
    xlabel="Band index",
    ylabel="Radiance (W m⁻² µm⁻¹ sr⁻¹)",
    title="HyTES L1 Radiance Spectra by Surface Type",
    frame_width=500,
)
Loading...

HyTES L2 Land Surface Temperature and Emissivity

The HyTES Level 2 dataset (DOI: 10.3334/ORNLDAAC/2460) provides three key variables in HDF-5 format, derived using the Temperature Emissivity Separation (TES) algorithm:

VariableDescriptionSpectral RangeBands
L2_LSTLand surface temperature in Kelvin. Derived from atmospherically corrected level-1 radiance data using the TES algorithm.1
L2_EmissivityEmissivity spectral data from 8.2-11.5 micrometers, retrieved on 164 Temperature Emissivity Separation (TES) bands instead of 256.8.2–11.5 µm164
L2_Emissivity_PCPrincipal Component (PC) eigenvector regression emissivity data from 7.4-12 micrometers for all 256 channels.7.4–12.0 µm256

Note some HyTES campaigns are flown on “ER2” and others campaigns are “Twin Otter” flights. The L2 products are slightly different between these two flights. Please refer to the user guide and the L2 ATBD for more information.

# Open L2 HDF5 file via xarray (streaming from cloud)
ds_l2_raw = xr.open_datatree(l2_files[0], engine="h5netcdf", chunks="auto", phony_dims="sort")

# The L2 file has a different dim ordering: band, line, sample, bands_emis
ds_l2 = ds_l2_raw.to_dataset().rename({
    "phony_dim_0": "band",
    "phony_dim_1": "line",
    "phony_dim_2": "sample",
    "phony_dim_3": "bands_emis",
})

print(f"L2 variables: {list(ds_l2.data_vars)}")
ds_l2
L2 variables: ['ISAC_Path_Rad', 'ISAC_Skydown_Rad', 'L2_Emissivity', 'L2_Emissivity_PC', 'L2_Emissivity_PC_Wavelengths', 'L2_Emissivity_Wavelengths', 'L2_LST', 'Transmission']
Loading...

Land Surface Temperature

LST is stored in Kelvin. We plot the full spatial image to see the temperature distribution across the flight line.

ds_l2["L2_LST"].hvplot.image(
    width=300, height=600,
    cmap="seismic",
    clim=(250, 350),
    title="L2 Land Surface Temperature (K)",
).opts(invert_yaxis=True)
Loading...

Emissivity Spectra for Selected Surface Types

Using the same reference locations as before, we compare the two emissivity products (L2_Emissivity on 164 TES bands and L2_Emissivity_PC on all 256 channels) at the inshore pixel.

# Select the inshore pixel in the L2 dataset (using fixed indices from the L1 lookup)
# L2 dimensions correspond to the same spatial grid as L1
ds_px = ds_l2.sel(sample=218, line=2164)

plot_tes = ds_px.hvplot.scatter(
    y="L2_Emissivity",
    x="L2_Emissivity_Wavelengths",
    color="red",
    label="L2_Emissivity (TES, 164 bands)",
    xlabel="Wavelength (µm)",
    ylabel="Emissivity",
)

plot_pc = ds_px.hvplot.scatter(
    y="L2_Emissivity_PC",
    x="L2_Emissivity_PC_Wavelengths",
    color="green",
    label="L2_Emissivity_PC (PC regression, 256 bands)",
)

(plot_tes * plot_pc).opts(
    title="HyTES L2 Emissivity — Inshore pixel (Langebaan)",
    frame_width=500,
    legend_position="bottom_right",
)
Loading...

Summary

In this tutorial we used earthaccess.search_datasets(instrument="HyTES") to discover all HyTES collections on NASA Earthdata. We then searched for L1 Radiance granules over Langebaan Lagoon during the BioSCape 2023 campaign, and plotted the CMR granule footprint polygons as flight lines — no extra file download needed. We also streamed HDF-5 files directly from the Earthdata Cloud using earthaccess.open() — no local download required. We opened and explored the L1 radiance dataset with xarray, plotted a single band and a false-color TIR composite, and compared radiance spectra across surface types. We finally opened the L2 dataset to visualize the Land Surface Temperature image and compare TES vs. PC-regression emissivity products.

References
  1. Hook, S. J., Hulley, G. C., La, T. T., Rivera, G., Johnson, W. R., & Eng, B. T. (2026). HyTES Level 1 Radiance, Facility Instrument Collection. ORNL Distributed Active Archive Center. 10.3334/ORNLDAAC/2462
  2. Hook, S. J., Hulley, G. C., La, T. T., Rivera, G., Johnson, W. R., & Eng, B. T. (2026). HyTES L1 Geolocation, Facility Instrument Collection. ORNL Distributed Active Archive Center. 10.3334/ORNLDAAC/2459
  3. Hulley, G. C., Hook, S. J., La, T. T., Rivera, G., Johnson, W. R., & Eng, B. T. (2026). HyTES: Level 2 Emissivity and Land Surface Temperature. ORNL Distributed Active Archive Center. 10.3334/ORNLDAAC/2460