Skip to article frontmatterSkip to article content

Project Pythia Notebook Template

Authors
Affiliations
City College of New York and NOAA/OAR National Severe Storms Laboratory
NSF National Center for Atmospheric Research
University at Albany (State University of New York)
Metropolitan State University of Denver
Jackson State University
Argonne National Laboratory

Let’s start here! If you can directly link to an image relevant to your notebook, such as canonical logos, do so here at the top of your notebook. You can do this with MyST Markdown syntax, outlined in this MyST guide, or you edit this cell to see a demonstration. Be sure to include alt text for any embedded images to make your content more accessible.

Project Pythia logo

Next, title your notebook appropriately with a top-level Markdown header, # (see the very first cell above). Do not use this level header anywhere else in the notebook. Our book build process will use this title in the navbar, table of contents, etc. Keep it short, keep it descriptive.

Follow this with a --- cell to visually distinguish the transition to the prerequisites section.


Overview

This notebook demonstrates how to access, process, and visualize NOAA MRMS Composite Reflectivity (Low-Level) data using Python. You’ll learn how to:

Generate timestamps for a given time range and format them for MRMS data access

Download and decompress .grib2.gz MRMS files from AWS

Load the data using xarray and cfgrib

Visualize reflectivity data with Cartopy

Save individual reflectivity frames and prepare them for animation

Create a time-lapse animation using matplotlib.animation

By the end of this notebook, you will have a working animation of radar reflectivity spanning a multi-day period, and a clear understanding of how to automate data retrieval and visualization workflows for radar datasets.

Prerequisites

This section was inspired by this template of the wonderful The Turing Way Jupyter Book.

Following your overview, tell your reader what concepts, packages, or other background information they’ll need before learning your material. Tie this explicitly with links to other pages here in Foundations or to relevant external resources. Remove this body text, then populate the Markdown table, denoted in this cell with | vertical brackets, below, and fill out the information following. In this table, lay out prerequisite concepts by explicitly linking to other Foundations material or external resources, or describe generally helpful concepts.

Label the importance of each concept explicitly as helpful/necessary.

ConceptsImportanceNotes
Intro to CartopyNecessary
Understanding of NetCDFHelpfulFamiliarity with metadata structure
Project managementHelpful
  • Time to learn: estimate in minutes. For a rough idea, use 5 mins per subsection, 10 if longer; add these up for a total. Safer to round up and overestimate.
  • System requirements:
    • Populate with any system, version, or non-Python software requirements if necessary
    • Otherwise use the concepts table above and the Imports section below to describe required packages as necessary
    • If no extra requirements, remove the System requirements point altogether

Imports

Begin your body of content with another --- divider before continuing into this section, then remove this body text and populate the following code cell with all necessary Python imports up-front:

!conda install metpy -y -c conda-forge
import ipywidgets as widgets
import requests
import s3fs
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import xarray as xr
import datetime
import numpy.ma as ma
from metpy.plots import ctables
import urllib.request
import gzip
import tempfile
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
from metpy.plots import ctables

MRMS Data

# Using MRMS Data to plot the low level composite reflectivity at 12Z in Central Texas

Reading in MRMS Data

response = urllib.request.urlopen("https://noaa-mrms-pds.s3.amazonaws.com/CONUS/LayerCompositeReflectivity_Low_00.50/20250704/MRMS_LayerCompositeReflectivity_Low_00.50_20250704-001040.grib2.gz")
compressed_file = response.read()

with tempfile.NamedTemporaryFile(suffix=".grib2") as f:
            f.write(gzip.decompress(compressed_file))
            data_in = xr.load_dataarray(f.name, engine='cfgrib', decode_timedelta=True)

Plotting Reflectivity over Texas at 12Z

refl_norm, refl_cmap = ctables.registry.get_with_steps('NWSReflectivity', 5, 5)

# 2. Extract coords & data
lons = data_in.longitude.values
lats = data_in.latitude.values
refl = data_in.values

# If coords are 1D, make them 2D
if lons.ndim == 1 and lats.ndim == 1:
    lons, lats = np.meshgrid(lons, lats)
# 3. Plot
fig = plt.figure(figsize=(10, 8))
ax = plt.axes(projection=ccrs.PlateCarree())
ax.set_extent([-106, -93, 25, 36], crs=ccrs.PlateCarree())

ax.add_feature(cfeature.COASTLINE, linewidth=1)
ax.add_feature(cfeature.BORDERS, linewidth=1)
ax.add_feature(cfeature.STATES, linewidth=0.5)

mesh = ax.pcolormesh(
    lons, lats, ma.masked_where(refl<5,refl),
    cmap=refl_cmap,
    norm=refl_norm,
    transform=ccrs.PlateCarree()
)

cb = plt.colorbar(mesh, ax=ax, orientation='horizontal', pad=0.05, aspect=50)
cb.set_label('Reflectivity (dBZ)')

plt.title('MRMS Layer Composite Reflectivity – Texas', fontsize=14)
plt.show()
ma.masked_where(refl<5,refl)

Creating 6 frames

from datetime import datetime, timedelta
import urllib.request

start = datetime(2025, 7, 4, 0, 10, 40)
end = datetime(2025, 7, 7, 0, 0, 0)
step = timedelta(minutes=30)

valid_timestamps = []
t = start

print("Checking for available MRMS files...\n")

while t <= end and len(valid_timestamps) < 6:
    ts = t.strftime("%Y%m%d-%H%M%S")
    date_str = ts[:8]
    url = (
        f"https://noaa-mrms-pds.s3.amazonaws.com/CONUS/LayerCompositeReflectivity_Low_00.50/"
        f"{date_str}/MRMS_LayerCompositeReflectivity_Low_00.50_{ts}.grib2.gz"
    )
    try:
        resp = urllib.request.urlopen(url, timeout=5)
        print(f" Found: {ts}")
        valid_timestamps.append(ts)
    except:
        print(f" Missing: {ts}")
    t += step

print("\n Selected 6 timestamps:")
for ts in valid_timestamps:
    print(ts)
import urllib.request
import gzip
import tempfile
import xarray as xr
import numpy as np
import numpy.ma as ma
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
from metpy.plots import ctables
from matplotlib.animation import ArtistAnimation
from IPython.display import HTML

# Define the 6 known working timestamps (one every hour)
timestamps = [
    "20250704-001040",
    "20250704-011040",
    "20250704-031040",
    "20250704-054040",
    "20250704-071040",
    "20250704-091040"
]

# Set up colormap and normalization for reflectivity
refl_norm, refl_cmap = ctables.registry.get_with_steps('NWSReflectivity', 5, 5)

# Initialize animation container
frames_six = []

# Set up static map
fig = plt.figure(figsize=(10, 8))
ax = plt.axes(projection=ccrs.PlateCarree())
ax.set_extent([-106, -93, 25, 36], crs=ccrs.PlateCarree())
ax.add_feature(cfeature.COASTLINE, linewidth=1)
ax.add_feature(cfeature.BORDERS, linewidth=1)
ax.add_feature(cfeature.STATES, linewidth=0.5)

# Loop through timestamps and collect frames
for ts in timestamps:
    print(f"Loading {ts}...")
    try:
        url = (
            f"https://noaa-mrms-pds.s3.amazonaws.com/CONUS/LayerCompositeReflectivity_Low_00.50/"
            f"{ts[:8]}/MRMS_LayerCompositeReflectivity_Low_00.50_{ts}.grib2.gz"
        )
        response = urllib.request.urlopen(url)
        compressed_file = response.read()

        with tempfile.NamedTemporaryFile(suffix=".grib2") as f:
            f.write(gzip.decompress(compressed_file))
            f.flush()
            data_in = xr.load_dataarray(f.name, engine='cfgrib', decode_timedelta=True)

        # Extract coordinates and reflectivity data
        lons = data_in.longitude.values
        lats = data_in.latitude.values
        refl = data_in.values

        if lons.ndim == 1 and lats.ndim == 1:
            lons, lats = np.meshgrid(lons, lats)

        # Plot single frame (no show)
        mesh = ax.pcolormesh(
            lons, lats, ma.masked_where(refl < 5, refl),
            cmap=refl_cmap,
            norm=refl_norm,
            transform=ccrs.PlateCarree()
        )

        ax.set_title(f"MRMS Low-Level Reflectivity (dBZ) – {ts[:4]}-{ts[4:6]}-{ts[6:8]} {ts[9:11]}:{ts[11:13]} UTC")

        # Save mesh to animation frame
        frames_six.append([mesh])

    except Exception as e:
        print(f"Skipped {ts} → {e}")
        continue

# Create and display animation
anim = ArtistAnimation(fig, frames_six, interval=500, blit=True)
HTML(anim.to_jshtml())
from matplotlib.animation import PillowWriter

# Save animation as a .gif
anim.save("mrms_reflectivity_animation.gif", writer=PillowWriter(fps=2))

print("Animation saved as 'mrms_reflectivity_animation.gif'")

Animation of the Texas flood event using MRMS

Another content subsection

Keep up the good work! A note, try to avoid using code comments as narrative, and instead let them only exist as brief clarifications where necessary.

Your second content section

Here we can move on to our second objective, and we can demonstrate...

A subsection to the second section

a quick demonstration

of further and further
header levels

as well as m=at/hm = a * t / h text! Similarly, you have access to other LaTeX\LaTeX equation functionality via MathJax:

x˙=σ(yx)y˙=ρxyxzz˙=βz+xy\begin{align} \dot{x} & = \sigma(y-x) \\ \dot{y} & = \rho x - y - xz \\ \dot{z} & = -\beta z + xy \end{align}

Check out any number of helpful Markdown resources for further customizing your notebooks and the MyST Syntax Overview for MyST-specific formatting information. Don’t hesitate to ask questions if you have problems getting it to look just right.

Last Section

You can add admonitions using MyST syntax:

Some other admonitions you can put in (there are 10 total):

We also suggest checking out Jupyter Book’s brief demonstration on adding cell tags to your cells in Jupyter Notebook, Lab, or manually. Using these cell tags can allow you to customize how your code content is displayed and even demonstrate errors without altogether crashing our loyal army of machines!


Summary

Add one final --- marking the end of your body of content, and then conclude with a brief single paragraph summarizing at a high level the key pieces that were learned and how they tied to your objectives. Look to reiterate what the most important takeaways were.

What’s next?

Let Jupyter book tie this to the next (sequential) piece of content that people could move on to down below and in the sidebar. However, if this page uniquely enables your reader to tackle other nonsequential concepts throughout this book, or even external content, link to it here!

Resources and references

Finally, be rigorous in your citations and references as necessary. Give credit where credit is due. Also, feel free to link to relevant external material, further reading, documentation, etc. Then you’re done! Give yourself a quick review, a high five, and send us a pull request. A few final notes:

  • Kernel > Restart Kernel and Run All Cells... to confirm that your notebook will cleanly run from start to finish
  • Kernel > Restart Kernel and Clear All Outputs... before committing your notebook, our machines will do the heavy lifting
  • Take credit! Provide author contact information if you’d like; if so, consider adding information here at the bottom of your notebook
  • Give credit! Attribute appropriate authorship for referenced code, information, images, etc.
  • Only include what you’re legally allowed: no copyright infringement or plagiarism

Thank you for your contribution!