Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reclass: Addition of New Radar Echo Classification Function #1495

Merged
merged 59 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
e9af16a
ADD: _echo_class_wt.py: RadarEchoClass module with all methods/code f…
RBhupi Dec 4, 2023
4cb9a98
ADD:wrapper function for wt_reclass
RBhupi Dec 4, 2023
75a7cbc
REFORMAT: Function names comply with PyART & PEP8 standards.
RBhupi Dec 5, 2023
6ce7cc8
REFORMAT: Function names comply with PyART & PEP8 standards.
RBhupi Dec 5, 2023
ad60ced
Revert "REFORMAT: Function names comply with PyART & PEP8 standards."
RBhupi Dec 5, 2023
cb59422
REFORMAT: Function names comply with PyART & PEP8 standards.
RBhupi Dec 5, 2023
8e727bf
ADD: masked array, detailed func arguments
RBhupi Dec 8, 2023
8397073
MOD:func user arguments used,class num changed.
RBhupi Dec 8, 2023
8cbf8fc
DOC:function and user arguments.
RBhupi Dec 8, 2023
e7b9b0c
MOD: added metadata to field dictinery.
RBhupi Dec 8, 2023
77de7a1
Merge branch 'ARM-DOE:main' into reclass
RBhupi Dec 8, 2023
fb64b64
order of classes changed
RBhupi Dec 8, 2023
fa3e339
merged changes from pyart main branch
RBhupi Dec 8, 2023
b58541e
cappi level added;better description of calsses
RBhupi Dec 11, 2023
368fc6c
REFORMAT: PEP8 style
RBhupi Dec 12, 2023
a02ed10
FORMAT:PEP8
RBhupi Dec 12, 2023
87b85ac
MOD:sanity check for conv core threshold
RBhupi Dec 12, 2023
691dfdc
MOD:More sanity check for thresholds
RBhupi Dec 12, 2023
5435665
MOD:Added override to sanity check+documentation
RBhupi Dec 12, 2023
b5aa79d
MOD:minor
RBhupi Dec 12, 2023
71bda53
invalid grid raises exception
RBhupi Dec 12, 2023
1d7f71e
first unit test added
RBhupi Dec 12, 2023
5b04a81
ADD:func make_gaussian_storm_grid
RBhupi Dec 12, 2023
19c6cb0
ADD: test for output validity
RBhupi Dec 12, 2023
83155a7
ADD:utest for results of classification
RBhupi Dec 12, 2023
0977cd4
ENH: test documentation and apply black formatting
RBhupi Dec 12, 2023
be97289
ENH:small test func merged;name change
RBhupi Dec 12, 2023
d1f7aa0
ADD: Stat-based tests for Gaussian storm grid function
RBhupi Dec 12, 2023
4512f59
ADD: example
RBhupi Dec 13, 2023
5401b35
FORMAT:Black
RBhupi Dec 13, 2023
ba25c64
FIX: issues for #1495 the code review
RBhupi Dec 15, 2023
cdc28d7
FIX:unused imports andvariables removed
RBhupi Dec 15, 2023
ee0ea00
Removed two line func sum_conv_wavelets();
RBhupi Dec 15, 2023
edec4bf
minor
RBhupi Dec 15, 2023
09ec054
Removed reflectivity_to_rainrate()
RBhupi Dec 15, 2023
4d78ca6
Test PAssed reflectivity_to_rainrate():
RBhupi Dec 15, 2023
3eec39f
minor:autosummary
RBhupi Dec 15, 2023
76fb441
ADD:conv_wavelet_sum()
RBhupi Dec 15, 2023
c3e03fa
Func Renamed: get_reclass -> wavelet_reclas
RBhupi Dec 15, 2023
c0bc43d
Refined documentation and Descriptive user arguments
RBhupi Dec 16, 2023
7064af1
FMT: Black
RBhupi Dec 16, 2023
e4efd93
ENH:Docstring
RBhupi Dec 16, 2023
b491bc7
DOC:Better description & ordering of classification categories
RBhupi Dec 16, 2023
114e99c
REFACTOR:scale_break now computed in calling function
RBhupi Dec 18, 2023
07be2d1
ENH:scale_break is adaptive to resolution
RBhupi Dec 18, 2023
9fff87e
ENH:atol added, tests default function arguments
RBhupi Dec 18, 2023
649e3fb
FROMAT:black
RBhupi Dec 18, 2023
6b77843
MINOR:corrected tab in docstring
RBhupi Dec 18, 2023
b789192
used new scaling and added description
RBhupi Dec 18, 2023
8c11c5d
MINOR: alignment in docstring
RBhupi Dec 18, 2023
48f66a9
FORAMT:linting error corrected
RBhupi Dec 18, 2023
f3915fe
'LINTING ERR:import-block sorted;extra'()' removed'
RBhupi Dec 26, 2023
54b3436
FIX: Fix linting error
mgrover1 Jan 4, 2024
9e2578d
Merge branch 'main' into reclass
mgrover1 Jan 4, 2024
5ce8e8e
FIX: Fix more linting errors
mgrover1 Jan 4, 2024
6abcc76
Merge branch 'reclass' of https://github.com/RBhupi/pyart into reclass
mgrover1 Jan 4, 2024
2c93f6b
DOCS:minor docstrig chages from Zach
RBhupi Jan 10, 2024
bac411b
DOCS: testing precommit
RBhupi Jan 10, 2024
18d7901
Merge branch 'main' into reclass
RBhupi Jan 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
605 changes: 605 additions & 0 deletions examples/retrieve/wavelet_echo_class_example.ipynb

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions pyart/retrieve/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from .echo_class import get_freq_band # noqa
from .echo_class import hydroclass_semisupervised # noqa
from .echo_class import steiner_conv_strat # noqa
from .echo_class import conv_strat_raut # noqa
from .gate_id import fetch_radar_time_profile, map_profile_to_gates # noqa
from .kdp_proc import kdp_maesaka, kdp_schneebeli, kdp_vulpiani # noqa
from .qpe import est_rain_rate_a # noqa
Expand Down
313 changes: 313 additions & 0 deletions pyart/retrieve/_echo_class_wt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,313 @@
"""
Classification of Precipitation Echoes in Radar Data.

Created on Thu Oct 12 23:12:19 2017
@author: Bhupendra Raut
@modifed: 11/19/2023
@references: 10.1109/TGRS.2020.2965649

.. autosummary::
wavelet_reclass
label_classes
calc_scale_break
atwt2d
zssherman marked this conversation as resolved.
Show resolved Hide resolved
"""


import numpy as np


def wavelet_reclass(
grid,
refl_field,
level,
zr_a,
zr_b,
core_wt_threshold,
conv_wt_threshold,
scale_break,
min_reflectivity,
conv_min_refl,
conv_core_threshold,
):
"""
Compute ATWT described as Raut et al (2008) and classify radar echoes using scheme of Raut et al (2020).
First, convert dBZ to rain rates using standard Z-R relationship or user given coefficients. This is to
transform the normally distributed dBZ to gamma-like distribution, enhancing the structure of the field.

Parameters
----------
dbz_data : ndarray
2D array containing radar data. Last dimension should be levels.
res_km : float
Resolution of the radar data in km
scale_break : int
Calculated scale break between convective and stratiform scales. Dyadically spaced in grid pixels.

Returns
-------
wt_class : ndarray
Precipitation type classification: 0. N/A 1. stratiform/non-convective,
2. convective cores and 3. moderate+transitional (mix) convective
regions.
"""

# Extract grid data, save mask and get the resolution in km
try:
dbz_data = grid.fields[refl_field]["data"][level, :, :]
except:
dbz_data = grid.fields[refl_field]["data"][:, :]

# save the radar original mask for missing data.
radar_mask = np.ma.getmask(dbz_data)

wt_sum = conv_wavelet_sum(dbz_data, zr_a, zr_b, scale_break)

wt_class = label_classes(
wt_sum,
dbz_data,
core_wt_threshold,
conv_wt_threshold,
min_reflectivity,
conv_min_refl,
conv_core_threshold,
)

wt_class_ma = np.ma.masked_where(radar_mask, wt_class) # add mask back
wt_class_ma = wt_class_ma.squeeze()

return wt_class_ma


def conv_wavelet_sum(dbz_data, zr_a, zr_b, scale_break):
"""
Computes the sum of wavelet transform components for convective scales from dBZ data.

Parameters
------------
dbz_data : ndarray
2D array containing radar dBZ data.
zr_a, zr_b : float
Coefficients for the Z-R relationship.
res_km : float
Resolution of the radar data in km.
scale_break : int
Calculated scale break (in pixels) between convective and stratiform scales

Returns
---------
wt_sum : ndarray
Sum of convective scale wavelet transform components.
"""
try:
dbz_data = dbz_data.filled(0)
except Exception:
pass

dbz_data[np.isnan(dbz_data)] = 0
rr_data = ((10.0 ** (dbz_data / 10.0)) / zr_a) ** (1.0 / zr_b)

wt, _ = atwt2d(rr_data, max_scale=scale_break)
wt_sum = np.sum(wt, axis=(0))

return wt_sum


def label_classes(
wt_sum,
dbz_data,
core_wt_threshold,
conv_wt_threshold,
min_reflectivity,
conv_min_refl,
conv_core_threshold,
):
"""
Labels classes using given thresholds:
- 0: No precipitation or unclassified
- 1: Stratiform/non-convective regions
- 2: Transitional and mixed convective regions
- 3: Convective cores

Following hard coded values are optimized and validated using C-band radars
over Darwin, Australia (2.5 km grid spacing) and tested for Solapur, India (1km grid spacing) [Raut et al. 2020].
core_wt_threshold = 5 # WT value more than this is strong convection
conv_wt_threshold = 2 # WT value for moderate convection
min_reflectivity = 10 # pixels below this value are not classified.
conv_min_refl = 30 # pixel below this value are not convective. This works for most cases.

Parameters
-----------
wt_sum : ndarray
Integrated wavelet transform
vol_data : ndarray
Array, vector or matrix of data

Returns
---------
wt_class : ndarray
Precipitation type classification.
"""

# I first used negative numbers to annotate the categories. Then multiply it by -1.
wt_class = np.where(
(wt_sum >= conv_wt_threshold) & (dbz_data >= conv_core_threshold), -3, 0
)
wt_class = np.where(
(wt_sum >= core_wt_threshold) & (dbz_data >= conv_min_refl), -3, 0
)
wt_class = np.where(
(wt_sum < core_wt_threshold)
& (wt_sum >= conv_wt_threshold)
& (dbz_data >= conv_min_refl),
-2,
wt_class,
)
wt_class = np.where((wt_class == 0) & (dbz_data >= min_reflectivity), -1, wt_class)

wt_class = -1 * wt_class
wt_class = np.where((wt_class == 0), np.nan, wt_class)

return wt_class.astype(np.int32)


def calc_scale_break(res_meters, conv_scale_km):
"""
Compute scale break for convection and stratiform regions. WT will be
computed upto this scale and features will be designated as convection.

Parameters
-----------
res_meters : float
resolution of the image.
conv_scale_km : float
expected size of spatial variations due to convection.

Returns
--------
dyadic scale break : int
integer scale break in dyadic scale.
"""
res_km = res_meters / 1000
scale_break = np.log(conv_scale_km / res_km) / np.log(2) + 1

return int(round(scale_break))


def atwt2d(data2d, max_scale=-1):
"""
Computes a trous wavelet transform (ATWT). Computes ATWT of the 2D array
up to max_scale. If max_scale is outside the boundaries, number of scales
will be reduced.

Data is mirrored at the boundaries. 'Negative WT are removed. Not tested
for non-square data.

@authors: Bhupendra A. Raut and Dileep M. Puranik
@references: Press et al. (1992) Numerical Recipes in C.

Parameters
-----------
data2d : ndarray
2D image as array or matrix.
max_scale :
Computes wavelets up to max_scale. Leave blank for maximum possible
scales.

Returns
---------
tuple of ndarray
ATWT of input image and the final smoothed image or background image.
"""

if not isinstance(data2d, np.ndarray):
raise TypeError("The input data2d must be a numpy array.")

data2d = data2d.squeeze()

dims = data2d.shape
min_dims = np.min(dims)
max_possible_scales = int(np.floor(np.log(min_dims) / np.log(2)))

if max_scale < 0 or max_possible_scales <= max_scale:
max_scale = max_possible_scales - 1

ny = dims[0]
nx = dims[1]

# For saving wt components
wt = np.zeros((max_scale, ny, nx))

temp1 = np.zeros(dims)
temp2 = np.zeros(dims)

sf = (0.0625, 0.25, 0.375) # scaling function

# start wavelet loop
for scale in range(1, max_scale + 1):
# print(scale)
x1 = 2 ** (scale - 1)
x2 = 2 * x1

# Row-wise smoothing
for i in range(0, nx):
# find the indices for prev and next points on the line
prev2 = abs(i - x2)
prev1 = abs(i - x1)
next1 = i + x1
next2 = i + x2

# If these indices are outside the image, "mirror" them
# Sometime this causes issues at higher scales.
if next1 > nx - 1:
next1 = 2 * (nx - 1) - next1

if next2 > nx - 1:
next2 = 2 * (nx - 1) - next2

if prev1 < 0 or prev2 < 0:
prev1 = next1
prev2 = next2

for j in range(0, ny):
left2 = data2d[j, prev2]
left1 = data2d[j, prev1]
right1 = data2d[j, next1]
right2 = data2d[j, next2]
temp1[j, i] = (
sf[0] * (left2 + right2)
+ sf[1] * (left1 + right1)
+ sf[2] * data2d[j, i]
)

# Column-wise smoothing
for i in range(0, ny):
prev2 = abs(i - x2)
prev1 = abs(i - x1)
next1 = i + x1
next2 = i + x2

# If these indices are outside the image use next values
if next1 > ny - 1:
next1 = 2 * (ny - 1) - next1
if next2 > ny - 1:
next2 = 2 * (ny - 1) - next2
if prev1 < 0 or prev2 < 0:
prev1 = next1
prev2 = next2

for j in range(0, nx):
top2 = temp1[prev2, j]
top1 = temp1[prev1, j]
bottom1 = temp1[next1, j]
bottom2 = temp1[next2, j]
temp2[i, j] = (
sf[0] * (top2 + bottom2)
+ sf[1] * (top1 + bottom1)
+ sf[2] * temp1[i, j]
)

wt[scale - 1, :, :] = data2d - temp2
data2d[:] = temp2

return wt, data2d
Loading
Loading