Skip to content

Commit

Permalink
Merge branch 'ARM-DOE:main' into hidx
Browse files Browse the repository at this point in the history
  • Loading branch information
RBhupi committed Jun 6, 2024
2 parents 1186a31 + 94e8ba4 commit 45f7a65
Show file tree
Hide file tree
Showing 11 changed files with 123 additions and 33 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ jobs:
python -m pytest -v --cov=./ --cov-report=xml
- name: Upload code coverage to Codecov
uses: codecov/codecov-action@v4.3.1
uses: codecov/codecov-action@v4.4.1
with:
file: ./coverage.xml
flags: unittests
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/wheels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
git fetch --prune --unshallow
- name: Build wheels for CPython
uses: pypa/cibuildwheel@v2.17.0
uses: pypa/cibuildwheel@v2.18.1
with:
output-dir: dist
env:
Expand Down
37 changes: 26 additions & 11 deletions pyart/core/grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,26 +170,33 @@ def __setstate__(self, state):
def projection_proj(self):
# Proj instance as specified by the projection attribute.
# Raises a ValueError if the pyart_aeqd projection is specified.
projparams = self.get_projparams()
if projparams["proj"] == "pyart_aeqd":
raise ValueError(
"Proj instance can not be made for the pyart_aeqd projection"
)
if not _PYPROJ_AVAILABLE:
raise MissingOptionalDependency(
"PyProj is required to create a Proj instance but it "
+ "is not installed"
)
projparams = self.get_projparams()

# Check if projparams is dictionary and check for pyart_aeqd
if isinstance(projparams, dict):
if projparams["proj"] == "pyart_aeqd":
raise ValueError(
"Proj instance can not be made for the pyart_aeqd projection"
)
# Get proj instance from a proj str or dict
proj = pyproj.Proj(projparams)
return proj

def get_projparams(self):
"""Return a projparam dict from the projection attribute."""
projparams = self.projection.copy()
if projparams.pop("_include_lon_0_lat_0", False):
projparams["lon_0"] = self.origin_longitude["data"][0]
projparams["lat_0"] = self.origin_latitude["data"][0]
return projparams
"""Return a projparam dict or str from the projection attribute."""
if isinstance(self.projection, dict):
projparams = self.projection.copy()
if projparams.pop("_include_lon_0_lat_0", False):
projparams["lon_0"] = self.origin_longitude["data"][0]
projparams["lat_0"] = self.origin_latitude["data"][0]
return projparams
else:
return self.projection

def _find_and_check_nradar(self):
"""
Expand Down Expand Up @@ -366,6 +373,14 @@ def to_xarray(self):
ds.z.encoding["_FillValue"] = None
ds.lat.encoding["_FillValue"] = None
ds.lon.encoding["_FillValue"] = None

# Grab original radar(s) name and number of radars used to make grid
ds.attrs["nradar"] = self.nradar
ds.attrs["radar_name"] = self.radar_name

# Grab all metadata
ds.attrs.update(self.metadata)

ds.close()
return ds

Expand Down
3 changes: 3 additions & 0 deletions pyart/correct/attenuation.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ def calculate_attenuation_zphi(
or up to where temperatures in a temperature field are positive.
The coefficients are either user-defined or radar frequency dependent.
NOTE: The base 10 forumulation that uses attenuated radar reflectivity and
differential phase that is used in this function can be found in Gu et al (2011).
Parameters
----------
radar : Radar
Expand Down
3 changes: 3 additions & 0 deletions pyart/default_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -1072,6 +1072,9 @@
134: None, # High Resolution VIL
135: None, # Enhanced Echo Tops
138: radar_estimated_rain_rate, # Digital Storm Total Precipitation
153: reflectivity, # Super Resolution Base Reflectivity Data Array
154: velocity, # Super Resolution Base Velocity Data Array
155: spectrum_width, # Super Resolution Base Spectrum Width Data Array
159: differential_reflectivity, # Digital Differential Reflectivity
161: cross_correlation_ratio, # Digital Correlation Coefficient
163: specific_differential_phase, # Digital Specific Differential Phase
Expand Down
9 changes: 9 additions & 0 deletions pyart/filters/gatefilter.py
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,15 @@ def exclude_invalid(self, field, exclude_masked=True, op="or"):
marked = ~np.isfinite(self._get_fdata(field))
return self._merge(marked, op, exclude_masked)

def exclude_last_gates(self, field, n_gates=10, exclude_masked=True, op="or"):
"""
Excludes a number of gates at the end of each ray. This is useful
for when trying to exclude "ring artifacts" in some datasets.
"""
marked = np.full(self._get_fdata(field).shape, False)
marked[:, -n_gates:] = True
return self._merge(marked, op, exclude_masked)

def exclude_gates(self, mask, exclude_masked=True, op="or"):
"""
Exclude gates where a given mask is equal True.
Expand Down
40 changes: 26 additions & 14 deletions pyart/io/cfradial.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ def read_cfradial(
exclude_fields=None,
include_fields=None,
delay_field_loading=False,
use_arm_scan_name=False,
**kwargs,
):
"""
Expand Down Expand Up @@ -92,6 +93,10 @@ def read_cfradial(
LazyLoadDict objects not dict objects. Delayed field loading will not
provide any speedup in file where the number of gates vary between
rays (ngates_vary=True) and is not recommended.
use_arm_scan_name : bool
Whether or not to get the sweep_mode information from the ARM scan_name
attribute. Default is False and will use the sweep_mode information as
defined in the cfradial standards.
Returns
-------
Expand Down Expand Up @@ -126,7 +131,7 @@ def read_cfradial(
if "volume_number" in ncvars:
if np.ma.isMaskedArray(ncvars["volume_number"][:]):
metadata["volume_number"] = int(
np.ma.getdata(ncvars["volume_number"][:].flatten())
np.ma.getdata(ncvars["volume_number"][:].flatten())[0]
)
else:
metadata["volume_number"] = int(ncvars["volume_number"][:])
Expand Down Expand Up @@ -191,20 +196,27 @@ def read_cfradial(
else:
ray_angle_res = None

# Uses ARM scan name if present.
if not hasattr(ncobj, "scan_name"):
scan_name = ""
else:
scan_name = ncobj.scan_name
if len(scan_name) > 0:
mode = scan_name
else:
# first sweep mode determines scan_type
try:
mode = netCDF4.chartostring(sweep_mode["data"][0])[()].decode("utf-8")
except AttributeError:
# Python 3, all strings are already unicode.
# Use ARM scan_name if chosen by the user
if use_arm_scan_name:
# Check if attribute actually exists
if not hasattr(ncobj, "scan_name"):
warnings.warn(
UserWarning,
"No scan_name attribute present in dataset, using sweep_mode instead.",
)
mode = netCDF4.chartostring(sweep_mode["data"][0])[()]
# Check if attribute is invalid of length 0
elif len(ncobj.scan_name) < 0 or ncobj.scan_name is None:
warnings.warn(
UserWarning,
"Scan name contains no sweep information, using sweep_mode instead.",
)
mode = netCDF4.chartostring(sweep_mode["data"][0])[()]
else:
mode = ncobj.scan_name
else:
# Use sweep_mode if arm_scan_name isn't used. This is default
mode = netCDF4.chartostring(sweep_mode["data"][0])[()]

mode = mode.strip()

Expand Down
34 changes: 29 additions & 5 deletions pyart/io/nexrad_level3.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ def get_data(self):
elif msg_code in [134]:
mdata = self._get_data_msg_134()

elif msg_code in [94, 99, 182, 186]:
elif msg_code in [94, 99, 153, 154, 155, 182, 186]:
hw31, hw32 = np.frombuffer(threshold_data[:4], ">i2")
data = (self.raw_data - 2) * (hw32 / 10.0) + hw31 / 10.0
mdata = np.ma.array(data, mask=self.raw_data < 2)
Expand Down Expand Up @@ -598,7 +598,24 @@ def _int16_to_float16(val):

# List of product numbers for which Halfword 30 corresponds to sweep elev angle
# Per Table V of the ICD
ELEVATION_ANGLE = [19, 20, 25, 27, 28, 30, 56, 94, 99, 159, 161, 163, 165]
ELEVATION_ANGLE = [
19,
20,
25,
27,
28,
30,
56,
94,
99,
153,
154,
155,
159,
161,
163,
165,
]

PRODUCT_RANGE_RESOLUTION = {
19: 1.0, # 124 nm
Expand All @@ -618,6 +635,9 @@ def _int16_to_float16(val):
134: 1000.0,
135: 1000.0,
138: 1.0,
153: 0.25,
154: 0.25,
155: 0.25,
159: 0.25,
161: 0.25,
163: 0.25,
Expand Down Expand Up @@ -655,6 +675,9 @@ def _int16_to_float16(val):
134: 1,
135: 0,
138: 2,
153: 0,
154: 0,
155: 0,
159: 0,
161: 0,
163: 0,
Expand Down Expand Up @@ -803,6 +826,10 @@ def _int16_to_float16(val):
134, # High Resolution VIL
135, # Enhanced Echo Tops
138, # Digital Storm Total
# Super Resolution
153, # Super Resolution Base Reflectivity Data Array
154, # Super Resolution Base Velocity Data Array
155, # Super Resolution Base Spectrum Width Data Array
# Precipitation
159, # Digital Differential
# Reflectivity
Expand Down Expand Up @@ -861,9 +888,6 @@ def _int16_to_float16(val):
# 147, # Storm Total Snow Depth
# 150, # User Selectable Snow Water Equivalent
# 151, # User Selectable Snow Depth
# 153, # Super Resolution Reflectivity Data Array
# 154, # Super Resolution Velocity Data Array
# 155, # Super Resolution Spectrum Width Data Array
# 158, # Differential Reflectivity
# 160, # Correlation Coefficient
# 162, # Specific Differential Phase
Expand Down
2 changes: 1 addition & 1 deletion pyart/util/columnsect.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def column_vertical_profile(
total_moment.update({"height": [], "time_offset": []})

# Define the start of the radar volume
base_time = pd.to_datetime(radar.time["units"][14:]).to_numpy()
base_time = np.datetime64(datetime_from_radar(radar).isoformat(), "ns")

# call the sphere_distance function
dis = sphere_distance(
Expand Down
10 changes: 10 additions & 0 deletions tests/core/test_grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ def test_grid_to_xarray():
assert_equal(ds.lat.data, lat)
assert_equal(ds.time.data, time)

assert ds.attrs["nradar"] == 1
assert ds.attrs["radar_name"]["data"][0] == "ExampleRadar"


def _check_dicts_similar(dic1, dic2):
for k, v in dic1.items():
Expand Down Expand Up @@ -304,6 +307,13 @@ def test_projection_proj():
assert isinstance(grid.projection_proj, pyproj.Proj)


@pytest.mark.skipif(not _PYPROJ_AVAILABLE, reason="PyProj is not installed.")
def test_projection_proj_str():
grid = pyart.testing.make_target_grid()
grid.projection = "+proj=aeqd"
assert isinstance(grid.projection_proj, pyproj.Proj)


def test_projection_proj_raised():
grid = pyart.testing.make_target_grid()

Expand Down
14 changes: 14 additions & 0 deletions tests/filters/test_gatefilter.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,20 @@ def test_gatefilter_ops():
assert gfilter.gate_excluded[0, 9] is np.True_


def test_gatefilter_exclude_last_gates():
gfilter = pyart.correct.GateFilter(radar)
gfilter.exclude_last_gates("test_field", n_gates=2)
assert gfilter.gate_excluded[0, 0] is np.False_
assert gfilter.gate_excluded[0, 8] is np.True_
assert gfilter.gate_excluded[0, 9] is np.True_

gfilter = pyart.correct.GateFilter(radar)
gfilter.exclude_last_gates("test_field", n_gates=1)
assert gfilter.gate_excluded[0, 0] is np.False_
assert gfilter.gate_excluded[0, 8] is np.False_
assert gfilter.gate_excluded[0, 9] is np.True_


def test_gatefilter_raises():
gfilter = pyart.correct.GateFilter(radar)
pytest.raises(ValueError, gfilter.exclude_below, "test_field", 0.5, op="fuzz")
Expand Down

0 comments on commit 45f7a65

Please sign in to comment.