usdata
All examples

Notebook · saved results

What did a satellite see in infrared?

Decode a GOES CONUS scene, inspect quality flags and scan coordinates, and plot brightness temperatures.

Download notebookDownload manifestView source

These are saved results, not live data. Execution times, source URLs, and checksums are recorded below. Set up and run examples.

Run this example · instructions and limitations

Open example.ipynb to see an executed GOES-18 channel-13 scene, quality-filtered brightness-temperature image in scan coordinates, and pixel histogram. Available since v0.8.

Use notebook setup from the repository root. The NetCDF reader requires usdata[netcdf]; the example environment supplies plotting and Jupyter. The repository pins Python 3.14.7 to avoid an upstream crash in older Linux uv Python 3.14 builds. Other supported Python versions can also run the notebook.

The manifest requests one exact scan start, 2024-05-06T12:01:18.1Z, with explicit satellite 18, channel 13, and product ABI-L2-CMIPC. The archive file is ~3.8 MB; decoding uses additional memory. The notebook preserves the full input checksum, source URL and retrieval time, verifies the manifest lockfile, and demonstrates cache reuse. Raw scene bytes and provenance remain unchanged.

CMI is infrared brightness temperature in kelvin; DQF == 0 is selected explicitly by this analysis. It is not a surface-air-temperature estimate. The plot uses geostationary scan angles rather than latitude/longitude. The reader preserves projection metadata but does not reproject the image. Plot sampling reduces output size; summaries use all accepted finite pixels.

Fetch a single GOES-18 ABI channel-13 CONUS scene, open its NetCDF4 locally, and inspect brightness temperature with the source quality flags. Available since v0.8 with usdata[netcdf] and the optional examples environment.

Saved outputs record one execution. Restart the kernel and run all cells to refresh it. This is a whole satellite scene (~3.8 MB), selected by its exact scan-start timestamp. It is not geographically cropped.

Code · cell 1
import sys
from datetime import UTC, datetime
from pathlib import Path

import h5netcdf
import h5py
import matplotlib.pyplot as plt
import numpy as np
import xarray as xr

import usdata
from usdata.pull import pull, verify

manifest = Path("examples/goes-imagery/dataset.yaml")
if not manifest.is_file():
    manifest = Path("dataset.yaml")
print("Executed (UTC):", datetime.now(UTC).isoformat(timespec="seconds"))
print(f"Python {sys.version.split()[0]}; h5netcdf {h5netcdf.__version__}; h5py {h5py.__version__}")
print(f"usdata {usdata.__version__}; xarray {xr.__version__}; NumPy {np.__version__}")
Executed (UTC): 2026-09-09T00:46:04+00:00
Python 3.14.7; h5netcdf 1.8.1; h5py 3.16.0
usdata 0.7.0; xarray 2026.7.0; NumPy 2.5.2

Fetch and preserve the input

The manifest explicitly selects satellite 18, channel 13, and ABI-L2-CMIPC. Both bounds equal the scan start, including its tenth of a second. The first pull creates a lockfile; later pulls restore its pinned URL and checksum.

Code · cell 2
result = pull(manifest)
(item,) = result.fetched
print("Source:", item.provenance.source_url)
print("Retrieved (UTC):", item.provenance.retrieved_at.isoformat())
print("Input checksum:", item.provenance.checksum)
print("Bytes:", item.provenance.size)
print("Scan:", item.asset.time.start.isoformat(), "to", item.asset.time.end.isoformat())
assert verify(manifest) == []
Source: s3://noaa-goes18/ABI-L2-CMIPC/2024/127/12/OR_ABI-L2-CMIPC-M6C13_G18_s20241271201181_e20241271203566_c20241271204061.nc
Retrieved (UTC): 2026-09-09T00:46:05.647041+00:00
Input checksum: sha256:3331dc3dcf67f3015e221484750fefebead4c51d728272045c3e31c9123a0c0a
Bytes: 3821830
Scan: 2024-05-06T12:01:18.100000+00:00 to 2024-05-06T12:03:56.600000+00:00

Open, decode, and keep quality information

open() loads the root NetCDF4 Dataset and closes its file handles before returning. CF scale/offset and fill values are decoded, while coordinate units, projection metadata, and quality flags remain available. It does not crop, reproject, or decide which quality flags to accept. Here the analysis explicitly keeps only DQF == 0 (the source's good_pixel_qf).

Code · cell 3
scene = item.open()
assert scene["CMI"].attrs["units"] == "K"
assert scene["CMI"].dims == ("y", "x")
assert "good_pixel_qf" in scene["DQF"].attrs["flag_meanings"]
assert scene.attrs["usdata"]["provenance"]["checksum"] == item.provenance.checksum
brightness = scene["CMI"].where(scene["DQF"] == 0)
print("Grid:", dict(scene.sizes))
print("CMI:", scene["CMI"].attrs["long_name"])
print("Units:", scene["CMI"].attrs["units"])
print("Quality flags:", scene["DQF"].attrs["flag_meanings"])
print("Accepted finite pixels:", int(brightness.count()), "of", brightness.size)
print(
    f"Pixel mean: {float(brightness.mean()):.2f} K; range: "
    f"{float(brightness.min()):.2f}-{float(brightness.max()):.2f} K"
)
Grid: {'y': 1500, 'x': 2500, 'number_of_time_bounds': 2, 'number_of_image_bounds': 2, 'band': 1}
CMI: ABI L2+ Cloud and Moisture Imagery brightness temperature
Units: K
Quality flags: good_pixel_qf conditionally_usable_pixel_qf out_of_range_pixel_qf no_value_pixel_qf focal_plane_temperature_threshold_exceeded_qf
Accepted finite pixels: 3750000 of 3750000
Pixel mean: 273.10 K; range: 208.29-296.29 K

Inspect scan coordinates and pixel temperatures

Channel 13 measures infrared brightness temperature, not necessarily surface air temperature: clouds and the surface both contribute to the observed scene. The image below uses the file's geostationary scan angles in radians, not a latitude/longitude map. The plot uses every fourth grid point for a compact render; the summary and histogram use every accepted finite pixel. The mean is a pixel mean, not an area-weighted regional temperature.

Code · cell 4
fig, axes = plt.subplots(1, 2, figsize=(10, 3.8), constrained_layout=True)
preview = brightness.isel(y=slice(None, None, 4), x=slice(None, None, 4))
mesh = axes[0].pcolormesh(
    preview.x, preview.y, preview, shading="auto", cmap="magma", vmin=200, vmax=310, rasterized=True
)
axes[0].set(
    xlabel="East-west scan angle (rad)",
    ylabel="North-south scan angle (rad)",
    title=(
        f"{scene.attrs['platform_ID']} C{int(scene.band_id.item()):02d} · "
        f"{item.asset.time.start:%Y-%m-%d %H:%M UTC}"
    ),
)
fig.colorbar(mesh, ax=axes[0], label="Brightness temperature (K)")
values = brightness.values
axes[1].hist(values[np.isfinite(values)], bins=50, color="#345c80")
axes[1].set(
    xlabel="Brightness temperature (K)",
    ylabel="Accepted pixel count",
    title="Scene distribution · DQF = 0",
)
axes[1].ticklabel_format(axis="y", style="sci", scilimits=(0, 0))
plt.show()
Saved plot from What did a satellite see in infrared?
Saved notebook output; see the surrounding analysis for units and interpretation.

Verify the source after analysis

Reading and plotting change neither the cached source nor its provenance. The returned Dataset is already detached from the file, so it stays usable without an open HDF5 handle. Keep the manifest, lockfile, and cached bytes for long-lived reproducibility; upstream revisions can fail a future locked restore.

Code · cell 5
assert verify(manifest) == []
again = pull(manifest)
assert again.from_lockfile and all(asset.from_cache for asset in again.fetched)
print("Input verified; repeat pull used the lockfile and cache.")
print("Projection:", scene["goes_imager_projection"].attrs["grid_mapping_name"])
print("Original checksum:", scene.attrs["usdata"]["provenance"]["checksum"])
Input verified; repeat pull used the lockfile and cache.
Projection: geostationary
Original checksum: sha256:3331dc3dcf67f3015e221484750fefebead4c51d728272045c3e31c9123a0c0a

Source documentation: NOAA ABI/CMIP, NODD public archive, and usdata GOES access notes.