
Py-ART Corrections¶
Overview¶
Within this notebook, we will cover:
Intro to radar aliasing.
Calculation of velocity texture using Py-ART
Dealiasing the velocity field
Prerequisites¶
| Concepts | Importance | Notes |
|---|---|---|
| Py-ART Basics | Helpful | Basic features |
| Weather Radar Basics | Helpful | Background Information |
| Matplotlib Basics | Helpful | Basic plotting |
| NumPy Basics | Helpful | Basic arrays |
| Xarray Basics | Helpful | Multi-dimensional arrays |
Time to learn: 15 minutes
Imports¶
import os
import warnings
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import numpy as np
import xarray as xr
import pyart
from pyart.testing import get_test_data
import xradar as xd
warnings.filterwarnings('ignore')Read in the Data¶
Read in a sample file from the University of Alabama Huntsville (UAH) ARMOR Site¶
Our data is formatted as an sigmet file, which is a vendor-specific format, produced by Vaisala radars.
Inspect the xradar.io documentation for the iris/sigment reader for this specific format
file = "../../data/uah-armor/RAW_NA_000_125_20080411182229"radar = pyart.io.read(file)radar.fields{'total_power': {'units': 'dBZ',
'standard_name': 'equivalent_reflectivity_factor',
'long_name': 'Total power',
'coordinates': 'elevation azimuth range',
'data': masked_array(
data=[[--, --, 47.0, ..., --, --, --],
[--, --, 46.5, ..., --, --, --],
[--, --, 45.0, ..., --, --, --],
...,
[--, --, 10.5, ..., --, --, --],
[--, --, --, ..., --, --, --],
[--, --, 11.0, ..., --, --, --]],
mask=[[ True, True, False, ..., True, True, True],
[ True, True, False, ..., True, True, True],
[ True, True, False, ..., True, True, True],
...,
[ True, True, False, ..., True, True, True],
[ True, True, True, ..., True, True, True],
[ True, True, False, ..., True, True, True]],
fill_value=np.float64(1e+20),
dtype=float32),
'_FillValue': -9999.0},
'reflectivity': {'units': 'dBZ',
'standard_name': 'equivalent_reflectivity_factor',
'long_name': 'Reflectivity',
'coordinates': 'elevation azimuth range',
'data': masked_array(
data=[[--, --, --, ..., --, --, --],
[--, --, --, ..., --, --, --],
[--, --, --, ..., --, --, --],
...,
[--, --, -12.5, ..., --, --, --],
[--, --, --, ..., --, --, --],
[--, --, -6.0, ..., --, --, --]],
mask=[[ True, True, True, ..., True, True, True],
[ True, True, True, ..., True, True, True],
[ True, True, True, ..., True, True, True],
...,
[ True, True, False, ..., True, True, True],
[ True, True, True, ..., True, True, True],
[ True, True, False, ..., True, True, True]],
fill_value=np.float64(1e+20),
dtype=float32),
'_FillValue': -9999.0},
'velocity': {'units': 'meters_per_second',
'standard_name': 'radial_velocity_of_scatterers_away_from_instrument',
'long_name': 'Mean dopper velocity',
'coordinates': 'elevation azimuth range',
'data': masked_array(
data=[[--, --, --, ..., --, --, --],
[--, --, --, ..., --, --, --],
[--, --, --, ..., --, --, --],
...,
[--, --, 12.154960632324219, ..., --, --, --],
[--, --, --, ..., --, --, --],
[--, --, 13.927558898925781, ..., --, --, --]],
mask=[[ True, True, True, ..., True, True, True],
[ True, True, True, ..., True, True, True],
[ True, True, True, ..., True, True, True],
...,
[ True, True, False, ..., True, True, True],
[ True, True, True, ..., True, True, True],
[ True, True, False, ..., True, True, True]],
fill_value=np.float64(1e+20),
dtype=float32),
'_FillValue': -9999.0},
'spectrum_width': {'units': 'meters_per_second',
'standard_name': 'doppler_spectrum_width',
'long_name': 'Doppler spectrum width',
'coordinates': 'elevation azimuth range',
'data': masked_array(
data=[[--, --, --, ..., --, --, --],
[0.06281249970197678, --, --, ..., --, --, --],
[--, --, --, ..., --, --, --],
...,
[0.06281249970197678, 0.06281249970197678, 0.06281249970197678,
..., --, --, --],
[0.06281249970197678, 0.06281249970197678, 0.06281249970197678,
..., --, --, --],
[0.06281249970197678, 1.9471875429153442, 0.43968749046325684,
..., --, --, --]],
mask=[[ True, True, True, ..., True, True, True],
[False, True, True, ..., True, True, True],
[ True, True, True, ..., True, True, True],
...,
[False, False, False, ..., True, True, True],
[False, False, False, ..., True, True, True],
[False, False, False, ..., True, True, True]],
fill_value=np.float64(1e+20),
dtype=float32),
'_FillValue': -9999.0},
'differential_reflectivity': {'units': 'dB',
'standard_name': 'log_differential_reflectivity_hv',
'long_name': 'Differential reflectivity',
'coordinates': 'elevation azimuth range',
'data': masked_array(
data=[[--, --, --, ..., --, --, --],
[--, --, --, ..., --, --, --],
[--, --, --, ..., --, --, --],
...,
[--, --, -0.0625, ..., --, --, --],
[--, --, --, ..., --, --, --],
[--, --, 2.3125, ..., --, --, --]],
mask=[[ True, True, True, ..., True, True, True],
[ True, True, True, ..., True, True, True],
[ True, True, True, ..., True, True, True],
...,
[ True, True, False, ..., True, True, True],
[ True, True, True, ..., True, True, True],
[ True, True, False, ..., True, True, True]],
fill_value=np.float64(1e+20),
dtype=float32),
'_FillValue': -9999.0},
'specific_differential_phase': {'units': 'degrees/km',
'standard_name': 'specific_differential_phase_hv',
'long_name': 'Specific differential phase (KDP)',
'coordinates': 'elevation azimuth range',
'data': masked_array(
data=[[--, --, --, ..., --, --, --],
[--, --, --, ..., --, --, --],
[--, --, --, ..., --, --, --],
...,
[--, --, --, ..., --, --, --],
[--, --, --, ..., --, --, --],
[--, --, --, ..., --, --, --]],
mask=[[ True, True, True, ..., True, True, True],
[ True, True, True, ..., True, True, True],
[ True, True, True, ..., True, True, True],
...,
[ True, True, True, ..., True, True, True],
[ True, True, True, ..., True, True, True],
[ True, True, True, ..., True, True, True]],
fill_value=np.float64(1e+20),
dtype=float32),
'_FillValue': -9999.0},
'differential_phase': {'units': 'degrees',
'standard_name': 'differential_phase_hv',
'long_name': 'Differential phase (PhiDP)',
'valid_max': 180.0,
'valid_min': -180.0,
'coordinates': 'elevation azimuth range',
'data': masked_array(
data=[[--, --, --, ..., --, --, --],
[--, --, --, ..., --, --, --],
[--, --, --, ..., --, --, --],
...,
[--, --, 17.716535568237305, ..., --, --, --],
[--, --, --, ..., --, --, --],
[--, --, 179.2913360595703, ..., --, --, --]],
mask=[[ True, True, True, ..., True, True, True],
[ True, True, True, ..., True, True, True],
[ True, True, True, ..., True, True, True],
...,
[ True, True, False, ..., True, True, True],
[ True, True, True, ..., True, True, True],
[ True, True, False, ..., True, True, True]],
fill_value=np.float64(1e+20),
dtype=float32),
'_FillValue': -9999.0},
'cross_correlation_ratio': {'units': 'ratio',
'standard_name': 'cross_correlation_ratio_hv',
'long_name': 'Cross correlation ratio (RHOHV)',
'valid_max': 1.0,
'valid_min': 0.0,
'coordinates': 'elevation azimuth range',
'data': masked_array(
data=[[--, --, --, ..., --, --, --],
[--, --, --, ..., --, --, --],
[--, --, --, ..., --, --, --],
...,
[--, --, 0.8935429453849792, ..., --, --, --],
[--, --, --, ..., --, --, --],
[--, --, 0.9513875842094421, ..., --, --, --]],
mask=[[ True, True, True, ..., True, True, True],
[ True, True, True, ..., True, True, True],
[ True, True, True, ..., True, True, True],
...,
[ True, True, False, ..., True, True, True],
[ True, True, True, ..., True, True, True],
[ True, True, False, ..., True, True, True]],
fill_value=np.float64(1e+20),
dtype=float32),
'_FillValue': -9999.0}}Plot a quick-look of reflectivity and velocity¶
We can start by taking a quick look at the reflectivity and velocity fields. Notice how the velocity field is rather messy, indicated by the speckles and high/low values directly next to each other
fig = plt.figure(figsize=[8, 10])
ax = plt.subplot(211, projection=ccrs.PlateCarree())
display = pyart.graph.RadarMapDisplay(radar)
display.plot_ppi_map('reflectivity',
ax=ax,
sweep=0,
resolution='50m',
vmin=0,
vmax=60,
projection=ccrs.PlateCarree(),
cmap='HomeyerRainbow')
ax2 = plt.subplot(2,1,2,projection=ccrs.PlateCarree())
display = pyart.graph.RadarMapDisplay(radar)
display.plot_ppi_map('velocity',
ax=ax2,
sweep=0,
resolution='50m',
vmin=-17,
vmax=17,
projection=ccrs.PlateCarree(),
cmap='balance')
plt.show()
The radial velocity measured by the radar is mesasured by detecting the phase shift between the transmitted pulse and the pulse recieved by the radar. However, using this methodology, it is only possible to detect phase shifts within due to the periodicity of the transmitted wave. Therefore, for example, a phase shift of would erroneously be detected as a phase shift of and give an incorrect value of velocity when retrieved by the radar. This phenomena is called aliasing. The maximium unambious velocity that can be detected by the radar before aliasing occurs is called the Nyquist velocity.
In the next example, you will see an example of aliasing occurring, where the values of +15 m/s abruptly transition into a region of -15 m/s, with -5 m/s in the middle of the region around 37 N, 97 W.
Calculate Velocity Texture¶
First, for dealiasing to work efficiently, we need to use a GateFilter. Notice that, this time, the data shown does not give us a nice gate_id. This is what raw data looks like, and we need to do some preprocessing on the data to remove noise and clutter. Thankfully, Py-ART gives us the capability to do this. As a simple filter in this example, we will first calculate the velocity texture using Py-ART’s calculate_velocity_texture function. The velocity texture is the standard deviation of velocity surrounding a gate. This will be higher in the presence of noise.
Let’s investigate this function first...
pyart.retrieve.calculate_velocity_texture?Signature:
pyart.retrieve.calculate_velocity_texture(
radar,
vel_field=None,
wind_size=3,
nyq=None,
check_nyq_uniform=True,
)
Docstring:
Derive the texture of the velocity field.
Parameters
----------
radar: Radar
Radar object from which velocity texture field will be made.
vel_field : str, optional
Name of the velocity field. A value of None will force Py-ART to
automatically determine the name of the velocity field.
wind_size : int or 2-element tuple, optional
The size of the window to calculate texture from.
If an integer, the window is defined to be a square of size wind_size
by wind_size. If tuple, defines the m x n dimensions of the filter
window.
nyq : float, optional
The nyquist velocity of the radar. A value of None will force Py-ART
to try and determine this automatically.
check_nyquist_uniform : bool, optional
True to check if the Nyquist velocities are uniform for all rays
within a sweep, False will skip this check. This parameter is ignored
when the nyq parameter is not None.
Returns
-------
vel_dict: dict
A dictionary containing the field entries for the radial velocity
texture.
File: ~/micromamba/envs/arm-summer-school-2025-dev/lib/python3.11/site-packages/pyart/retrieve/simple_moment_calculations.py
Type: functionDetermining the Right Parameters¶
You’ll notice that we need:
Our radar object
The name of our velocity field
The number of gates within our window to use to calculate the texture
The nyquist velocity
We can retrieve the nyquest velocity from our instrument parameters fortunately - using the following syntax!
nyquist_velocity = radar.instrument_parameters["nyquist_velocity"]["data"]
nyquist_velocityarray([16.08, 16.08, 16.08, ..., 16.08, 16.08, 16.08],
shape=(4319,), dtype=float32)While the nyquist velocity is stored as an array, we see that these are all the same value...
np.unique(nyquist_velocity)array([16.08], dtype=float32)Let’s save this single value to a float to use later...
nyquist_value = np.unique(nyquist_velocity)[0]Calculate Velocity Texture and Filter our Data¶
Now that we have an ide?a of which parameters to pass in, let’s calculate velocity texture!
vel_texture = pyart.retrieve.calculate_velocity_texture(radar,
vel_field='velocity',
nyq=nyquist_value)
vel_texture{'units': 'meters_per_second',
'standard_name': 'texture_of_radial_velocity_of_scatters_away_from_instrument',
'long_name': 'Doppler velocity texture',
'coordinates': 'elevation azimuth range',
'data': array([[3.72598136e-04, 3.72598136e-04, 3.72598136e-04, ...,
3.72598136e-04, 3.72598136e-04, 3.72598136e-04],
[3.72598136e-04, 3.72598136e-04, 3.72598136e-04, ...,
3.72598136e-04, 3.72598136e-04, 3.72598136e-04],
[3.72598136e-04, 3.72598136e-04, 3.72598136e-04, ...,
3.72598136e-04, 3.72598136e-04, 3.72598136e-04],
...,
[3.72598136e-04, 5.02686857e+00, 6.77448194e+00, ...,
3.71708422e-03, 3.71708422e-03, 3.71708422e-03],
[3.72598136e-04, 5.32500044e+00, 6.94682877e+00, ...,
3.71708422e-03, 3.71708422e-03, 3.71708422e-03],
[3.72598136e-04, 5.48428694e+00, 8.19507727e+00, ...,
3.71708422e-03, 3.71708422e-03, 3.71708422e-03]],
shape=(4319, 992))}The pyart.retrieve.calculate_velocity_texture function results in a data dictionary, including the actual data, as well as metadata. We can add this to our radar object, by using the radar.add_field method, passing the name of our new field ("texture"), the data dictionary (vel_texture), and a clarification that we want to replace the existing velocity texture field if it already exists in our radar object (replace_existing=True)
radar.add_field('texture', vel_texture, replace_existing=True)Plot our Velocity Texture Field¶
Now that we have our velocity texture field added to our radar object, let’s plot it!
fig = plt.figure(figsize=[8, 8])
display = pyart.graph.RadarMapDisplay(radar)
display.plot_ppi_map('texture',
sweep=0,
resolution='50m',
vmin=0,
vmax=10,
projection=ccrs.PlateCarree(),
cmap='balance')
plt.show()
Determine a Suitable Velocity Texture Threshold¶
Plot a histogram of velocity texture to get a better idea of what values correspond to hydrometeors and what values of texture correspond to artifacts.
In the below example, a threshold of 3 would eliminate most of the peak corresponding to noise around 6 while preserving most of the values in the peak of ~0.5 corresponding to hydrometeors.
hist, bins = np.histogram(radar.fields['texture']['data'],
bins=150)
bins = (bins[1:]+bins[:-1])/2.0
plt.plot(bins,
hist,
label='Velocity Texture Frequency')
plt.axvline(3,
color='r',
label='Proposed Velocity Texture Threshold')
plt.xlabel('Velocity texture')
plt.ylabel('Count')
plt.legend()
Setup a Gatefilter Object and Apply our Threshold¶
Now we can set up our GateFilter (pyart.filters.GateFilter), which allows us to easily apply masks and filters to our radar object.
gatefilter = pyart.filters.GateFilter(radar)
gatefilter<pyart.filters.gatefilter.GateFilter at 0x3510cba90>We discovered that a velocity texture threshold of only including values below 3 would be suitable for this dataset, we use the .exclude_above method, specifying we want to exclude texture values above 3.
gatefilter.exclude_above('texture', 3)Plot our Filtered Data¶
Now that we have created a gatefilter, filtering our data using the velocity texture, let’s plot our data!
We need to pass our gatefilter to the plot_ppi_map to apply it to our plot.
# Plot our Unfiltered Data
fig = plt.figure(figsize=[8, 10])
ax = plt.subplot(211, projection=ccrs.PlateCarree())
display = pyart.graph.RadarMapDisplay(radar)
display.plot_ppi_map('velocity',
title='Raw Radial Velocity (no filter)',
ax=ax,
sweep=0,
resolution='50m',
vmin=-17,
vmax=17,
projection=ccrs.PlateCarree(),
colorbar_label='Radial Velocity (m/s)',
cmap='balance')
ax2 = plt.subplot(2,1,2,projection=ccrs.PlateCarree())
# Plot our filtered data
display = pyart.graph.RadarMapDisplay(radar)
display.plot_ppi_map('velocity',
title='Radial Velocity with Velocity Texture Filter',
ax=ax2,
sweep=0,
resolution='50m',
vmin=-17,
vmax=17,
projection=ccrs.PlateCarree(),
colorbar_label='Radial Velocity (m/s)',
gatefilter=gatefilter,
cmap='balance')
plt.show()
Dealias the Velocity Using the Region-Based Method¶
At this point, we can use the dealias_region_based to dealias the velocities and then add the new field to the radar!
velocity_dealiased = pyart.correct.dealias_region_based(radar,
vel_field='velocity',
nyquist_vel=nyquist_value,
centered=True,
gatefilter=gatefilter)
# Add our data dictionary to the radar object
radar.add_field('corrected_velocity', velocity_dealiased, replace_existing=True)Plot our Cleaned, Dealiased Velocities¶
Plot the new velocities, which now look much more realistic.
fig = plt.figure(figsize=[8, 8])
display = pyart.graph.RadarMapDisplay(radar)
display.plot_ppi_map('corrected_velocity',
sweep=0,
resolution='50m',
vmin=-30,
vmax=30,
projection=ccrs.PlateCarree(),
colorbar_label='Radial Velocity (m/s)',
cmap='balance',
gatefilter=gatefilter)
plt.show()
Compare our Raw Velocity Field to our Dealiased, Cleaned Velocity Field¶
As a last comparison, let’s compare our raw, uncorrected velocities with our cleaned velocities, after applying the velocity texture threshold and dealiasing algorithm
# Plot our Unfiltered Data
fig = plt.figure(figsize=[8, 10])
ax = plt.subplot(211, projection=ccrs.PlateCarree())
display = pyart.graph.RadarMapDisplay(radar)
display.plot_ppi_map('velocity',
title='Raw Radial Velocity (no filter)',
ax=ax,
sweep=0,
resolution='50m',
vmin=-30,
vmax=30,
projection=ccrs.PlateCarree(),
colorbar_label='Radial Velocity (m/s)',
cmap='balance')
ax2 = plt.subplot(2,1,2,projection=ccrs.PlateCarree())
# Plot our filtered, dealiased data
display = pyart.graph.RadarMapDisplay(radar)
display.plot_ppi_map('corrected_velocity',
title='Radial Velocity with Velocity Texture Filter and Dealiasing',
ax=ax2,
sweep=0,
resolution='50m',
vmin=-30,
vmax=30,
projection=ccrs.PlateCarree(),
gatefilter=gatefilter,
colorbar_label='Radial Velocity (m/s)',
cmap='balance')
plt.show()
Conclusions¶
Within this lesson, we walked through how to apply radial velocity corrections to a dataset, filtering based on the velocity texture and using a regional dealiasing algorithm.
What’s Next¶
In the next few notebooks, we walk through retrieval development and advanced visualization methods!
Resources and References¶
Py-ART essentials links: